1 /***************************************************************************
2     Process Outputs.
3 
4     - Cabinet Vibration & Hydraulic Movement
5     - Brake & Start Lamps
6     - Coin Chute Outputs
7 
8     The Deluxe Motor code is also used by the force-feedback haptic system.
9 
10     One thing to note is that this code was originally intended to drive
11     a moving hydraulic cabinet, not to be mapped to a haptic device.
12 
13     Therefore, it's not perfect when used in this way, but the results
14     aren't bad :)
15 
16     Copyright Chris White.
17     See license.txt for more details.
18 ***************************************************************************/
19 
20 #include <iostream>
21 #include <cstdlib> // abs
22 
23 #include "utils.hpp"
24 
25 #include "engine/outrun.hpp"
26 #include "engine/ocrash.hpp"
27 #include "engine/oferrari.hpp"
28 #include "engine/ohud.hpp"
29 #include "engine/oinputs.hpp"
30 #include "engine/ooutputs.hpp"
31 #include "directx/ffeedback.hpp"
32 
OOutputs(void)33 OOutputs::OOutputs(void)
34 {
35     mode = MODE_DISABLED;
36 
37     chute1.output_bit  = D_COIN1_SUCC;
38     chute2.output_bit  = D_COIN2_SUCC;
39 
40     col1               = 0;
41     col2               = 0;
42     limit_left         = 0;
43     limit_right        = 0;
44     motor_enabled      = true;
45 }
46 
~OOutputs(void)47 OOutputs::~OOutputs(void)
48 {
49 }
50 
51 
52 // Initalize Moving Cabinet Motor
53 // Source: 0xECE8
init()54 void OOutputs::init()
55 {
56     motor_state        = STATE_INIT;
57     hw_motor_control   = MOTOR_OFF;
58     dig_out            = 0;
59     dig_out_old        = -1;
60     motor_control      = 0;
61     motor_movement     = 0;
62     is_centered        = false;
63     motor_change_latch = 0;
64     speed              = 0;
65     curve              = 0;
66     counter            = 0;
67     vibrate_counter    = 0;
68     was_small_change   = false;
69     movement_adjust1   = 0;
70     movement_adjust2   = 0;
71     movement_adjust3   = 0;
72     chute1.counter[0]  = 0;
73     chute1.counter[1]  = 0;
74     chute1.counter[2]  = 0;
75     chute2.counter[0]  = 0;
76     chute2.counter[1]  = 0;
77     chute2.counter[2]  = 0;
78 }
79 
set_mode(int m)80 void OOutputs::set_mode(int m)
81 {
82     mode = m;
83 }
84 
tick(int16_t input_motor)85 void OOutputs::tick(int16_t input_motor)
86 {
87     switch (mode)
88     {
89         case MODE_DISABLED:
90             break;
91 
92         // Force Feedback Steering Wheels
93         case MODE_FFEEDBACK:
94             do_motors(mode, input_motor);   // Use X-Position of wheel instead of motor position
95             motor_output(hw_motor_control); // Force Feedback Handling
96             break;
97 
98         // SMARTYPI: Real Cabinet
99         case MODE_CABINET:
100             if (config.smartypi.cabinet == Config::CABINET_MOVING)
101             {
102                 do_motors(mode, input_motor);
103             }
104             else
105             {
106                 if (config.smartypi.cabinet == Config::CABINET_UPRIGHT)
107                     do_vibrate_upright();
108                 else if (config.smartypi.cabinet == Config::CABINET_MINI)
109                     do_vibrate_mini();
110             }
111             break;
112 
113         // GamePad: Basic Rumble
114         case MODE_RUMBLE:
115             do_vibrate_upright();
116             break;
117     }
118 }
119 
writeDigitalToConsole()120 void OOutputs::writeDigitalToConsole()
121 {
122     if (config.smartypi.enabled && config.smartypi.ouputs)
123     {
124         if ((dig_out & D_BRAKE_LAMP) != (dig_out_old & D_BRAKE_LAMP))
125             std::cout << "brake_lamp = " << is_set(D_BRAKE_LAMP) << std::endl;
126         if ((dig_out & D_START_LAMP) != (dig_out_old & D_START_LAMP))
127             std::cout << "start_lamp = " << is_set(D_START_LAMP) << std::endl;
128         if ((dig_out & D_MOTOR) != (dig_out_old & D_MOTOR))
129             std::cout << "wheel_motor = " << is_set(D_MOTOR) << std::endl;
130 
131         dig_out_old = dig_out;
132     }
133 }
134 
135 // ------------------------------------------------------------------------------------------------
136 // Digital Outputs
137 // ------------------------------------------------------------------------------------------------
138 
set_digital(uint8_t output)139 void OOutputs::set_digital(uint8_t output)
140 {
141     dig_out |= output;
142 }
143 
clear_digital(uint8_t output)144 void OOutputs::clear_digital(uint8_t output)
145 {
146     dig_out &= ~output;
147 }
148 
is_set(uint8_t output)149 int OOutputs::is_set(uint8_t output)
150 {
151     return (dig_out & output) ? 1 : 0;
152 }
153 
154 // ------------------------------------------------------------------------------------------------
155 // Motor Diagnostics
156 // Source: 0x1885E
157 // ------------------------------------------------------------------------------------------------
158 
diag_motor(int16_t input_motor,uint8_t hw_motor_limit,uint32_t packets)159 bool OOutputs::diag_motor(int16_t input_motor, uint8_t hw_motor_limit, uint32_t packets)
160 {
161     switch (motor_state)
162     {
163         // Initalize
164         case STATE_INIT:
165             col1 = 10;
166             col2 = 27;
167             ohud.blit_text_new(col1, 9, "LEFT LIMIT");
168             ohud.blit_text_new(col1, 11, "RIGHT LIMIT");
169             ohud.blit_text_new(col1, 13, "CENTRE");
170             ohud.blit_text_new(col1, 16, "MOTOR POSITION");
171             ohud.blit_text_new(col1, 18, "LIMIT B3 LEFT");
172             ohud.blit_text_new(col1, 19, "LIMIT B4 CENTRE");
173             ohud.blit_text_new(col1, 20, "LIMIT B5 RIGHT");
174             counter          = COUNTER_RESET;
175             motor_centre_pos = 0;
176             motor_enabled    = true;
177             motor_state = STATE_LEFT;
178             break;
179 
180         case STATE_LEFT:
181             diag_left(input_motor, hw_motor_limit);
182             break;
183 
184         case STATE_RIGHT:
185             diag_right(input_motor, hw_motor_limit);
186             break;
187 
188         case STATE_CENTRE:
189             diag_centre(input_motor, hw_motor_limit);
190             break;
191 
192         case STATE_DONE:
193             diag_done();
194             break;
195     }
196 
197     // Print Motor Position & Limit Switch
198     ohud.blit_text_new(col2, 16, "  H", 0x80);
199     ohud.blit_text_new(col2, 16, Utils::to_string(input_motor).c_str(), 0x80);
200     ohud.blit_text_new(col2, 18, (hw_motor_limit & BIT_3) ? "ON " : "OFF ", 0x80);
201     ohud.blit_text_new(col2, 19, (hw_motor_limit & BIT_4) ? "ON " : "OFF ", 0x80);
202     ohud.blit_text_new(col2, 20, (hw_motor_limit & BIT_5) ? "ON " : "OFF ", 0x80);
203     return motor_state == STATE_DONE;
204 }
205 
diag_left(int16_t input_motor,uint8_t hw_motor_limit)206 void OOutputs::diag_left(int16_t input_motor, uint8_t hw_motor_limit)
207 {
208     // If Right Limit Set, Move Left
209     if (hw_motor_limit & BIT_5)
210     {
211         if (--counter >= 0)
212         {
213             hw_motor_control = MOTOR_LEFT;
214             return;
215         }
216         // Counter Expired, Left Limit Still Not Reached
217         else
218             ohud.blit_text_new(col2, 9, "FAIL 1", 0x80);
219     }
220     // Left Limit Reached
221     else if (hw_motor_limit & BIT_3)
222     {
223         ohud.blit_text_new(col2, 9, "  H", 0x80);
224         ohud.blit_text_new(col2, 9, Utils::to_string(input_motor).c_str(), 0x80);
225     }
226     else
227         ohud.blit_text_new(col2, 9, "FAIL 2", 0x80);
228 
229     counter          = COUNTER_RESET;
230     motor_state      = STATE_RIGHT;
231 }
232 
233 
diag_right(int16_t input_motor,uint8_t hw_motor_limit)234 void OOutputs::diag_right(int16_t input_motor, uint8_t hw_motor_limit)
235 {
236     if (motor_centre_pos == 0 && ((hw_motor_limit & BIT_4) == 0))
237         motor_centre_pos = input_motor;
238 
239     // If Left Limit Set, Move Right
240     if (hw_motor_limit & BIT_3)
241     {
242         if (--counter >= 0)
243         {
244             hw_motor_control = MOTOR_RIGHT; // Move Right
245             return;
246         }
247         // Counter Expired, Right Limit Still Not Reached
248         else
249             ohud.blit_text_new(col2, 11, "FAIL 1", 0x80);
250     }
251     // Right Limit Reached
252     else if (hw_motor_limit & BIT_5)
253     {
254         ohud.blit_text_new(col2, 11, "  H", 0x80);
255         ohud.blit_text_new(col2, 11, Utils::to_string(input_motor).c_str(), 0x80);
256     }
257     else
258     {
259         ohud.blit_text_new(col2, 11, "FAIL 2", 0x80);
260         motor_enabled = false;
261         motor_state   = STATE_DONE;
262         return;
263     }
264 
265     motor_state  = STATE_CENTRE;
266     counter      = COUNTER_RESET;
267 }
268 
269 
diag_centre(int16_t input_motor,uint8_t hw_motor_limit)270 void OOutputs::diag_centre(int16_t input_motor, uint8_t hw_motor_limit)
271 {
272     if (hw_motor_limit & BIT_4)
273     {
274         if (--counter >= 0)
275         {
276             hw_motor_control = (counter <= COUNTER_RESET/2) ? MOTOR_RIGHT : MOTOR_LEFT; // Move Right
277             return;
278         }
279         else
280         {
281             ohud.blit_text_new(col2, 13, "FAIL", 0x80);
282         }
283     }
284     else
285     {
286         ohud.blit_text_new(col2, 13, "  H", 0x80);
287         ohud.blit_text_new(col2, 13, Utils::to_string((input_motor + motor_centre_pos) >> 1).c_str(), 0x86);
288         hw_motor_control = MOTOR_OFF; // switch off
289         counter          = 32;
290         motor_state      = STATE_DONE;
291     }
292 }
293 
diag_done()294 void OOutputs::diag_done()
295 {
296     if (counter > 0)
297         counter--;
298 
299     if (counter == 0)
300         hw_motor_control = MOTOR_CENTRE;
301 }
302 
303 // ------------------------------------------------------------------------------------------------
304 // Calibrate Motors
305 // ------------------------------------------------------------------------------------------------
306 
calibrate_motor(int16_t input_motor,uint8_t hw_motor_limit,uint32_t packets)307 bool OOutputs::calibrate_motor(int16_t input_motor, uint8_t hw_motor_limit, uint32_t packets)
308 {
309     switch (motor_state)
310     {
311         // Initalize
312         case STATE_INIT:
313             col1 = 11;
314             col2 = 25;
315             ohud.blit_text_big(      2,  "MOTOR CALIBRATION");
316             ohud.blit_text_new(col1, 10, "MOVE LEFT   -");
317             ohud.blit_text_new(col1, 12, "MOVE RIGHT  -");
318             ohud.blit_text_new(col1, 14, "MOVE CENTRE -");
319             counter          = 25;
320             motor_centre_pos = 0;
321             motor_enabled    = true;
322             motor_state++;
323             break;
324 
325         // Just a delay to wait for the serial for safety
326         case STATE_DELAY:
327             if (--counter == 0 || packets > 60)
328             {
329                 counter = COUNTER_RESET;
330                 motor_state++;
331             }
332             break;
333 
334         // Calibrate Left Limit
335         case STATE_LEFT:
336             calibrate_left(input_motor, hw_motor_limit);
337             break;
338 
339         // Calibrate Right Limit
340         case STATE_RIGHT:
341             calibrate_right(input_motor, hw_motor_limit);
342             break;
343 
344         // Return to Centre
345         case STATE_CENTRE:
346             calibrate_centre(input_motor, hw_motor_limit);
347             break;
348 
349         // Clear Screen & Exit Calibration
350         case STATE_DONE:
351             calibrate_done();
352             break;
353 
354         case STATE_EXIT:
355             return true;
356     }
357 
358     return false;
359 }
360 
calibrate_left(int16_t input_motor,uint8_t hw_motor_limit)361 void OOutputs::calibrate_left(int16_t input_motor, uint8_t hw_motor_limit)
362 {
363     // If Right Limit Set, Move Left
364     if (hw_motor_limit & BIT_5)
365     {
366         if (--counter >= 0)
367         {
368             hw_motor_control = MOTOR_LEFT;
369             return;
370         }
371         // Counter Expired, Left Limit Still Not Reached
372         else
373         {
374             ohud.blit_text_new(col2, 10, "FAIL 1");
375             motor_centre_pos = 0;
376             limit_left       = input_motor; // Set Left Limit
377             hw_motor_control = MOTOR_LEFT;  // Move Left
378             counter          = COUNTER_RESET;
379             motor_state      = STATE_RIGHT;
380         }
381     }
382     // Left Limit Reached
383     else if (hw_motor_limit & BIT_3)
384     {
385         ohud.blit_text_new(col2, 10, Utils::to_hex_string(input_motor).c_str(), 0x80);
386         motor_centre_pos = 0;
387         limit_left       = input_motor; // Set Left Limit
388         hw_motor_control = MOTOR_LEFT;  // Move Left
389         counter          = COUNTER_RESET;
390         motor_state      = STATE_RIGHT;
391     }
392     else
393     {
394         ohud.blit_text_new(col2, 10, "FAIL 2");
395         ohud.blit_text_new(col2, 12, "FAIL 2");
396         motor_enabled = false;
397         counter       = COUNTER_RESET;
398         motor_state   = STATE_CENTRE;
399     }
400 }
401 
calibrate_right(int16_t input_motor,uint8_t hw_motor_limit)402 void OOutputs::calibrate_right(int16_t input_motor, uint8_t hw_motor_limit)
403 {
404     if (motor_centre_pos == 0 && ((hw_motor_limit & BIT_4) == 0))
405     {
406         motor_centre_pos = input_motor;
407     }
408 
409     // If Left Limit Set, Move Right
410     if (hw_motor_limit & BIT_3)
411     {
412         if (--counter >= 0)
413         {
414             hw_motor_control = MOTOR_RIGHT; // Move Right
415             return;
416         }
417         // Counter Expired, Right Limit Still Not Reached
418         else
419         {
420             ohud.blit_text_new(col2, 12, "FAIL 1");
421             limit_right  = input_motor;
422             motor_state  = STATE_CENTRE;
423             counter      = COUNTER_RESET;
424         }
425     }
426     // Right Limit Reached
427     else if (hw_motor_limit & BIT_5)
428     {
429         ohud.blit_text_new(col2, 12, Utils::to_hex_string(input_motor).c_str(), 0x80);
430         limit_right   = input_motor; // Set Right Limit
431         motor_state   = STATE_CENTRE;
432         counter       = COUNTER_RESET;
433     }
434     else
435     {
436         ohud.blit_text_new(col2, 12, "FAIL 2");
437         motor_enabled = false;
438         motor_state   = STATE_CENTRE;
439         counter       = COUNTER_RESET;
440     }
441 }
442 
calibrate_centre(int16_t input_motor,uint8_t hw_motor_limit)443 void OOutputs::calibrate_centre(int16_t input_motor, uint8_t hw_motor_limit)
444 {
445     bool fail = false;
446 
447     if (hw_motor_limit & BIT_4)
448     {
449         if (--counter >= 0)
450         {
451             hw_motor_control = (counter <= COUNTER_RESET/2) ? MOTOR_RIGHT : MOTOR_LEFT; // Move Right
452             return;
453         }
454         else
455         {
456             ohud.blit_text_new(col2, 14, "FAIL SW");
457             fail = true;
458             // Fall through to EEB6
459         }
460     }
461 
462     // 0xEEB6:
463     motor_centre_pos = (input_motor + motor_centre_pos) >> 1;
464 
465     int16_t d0 = limit_right - motor_centre_pos;
466     int16_t d1 = motor_centre_pos  - limit_left;
467 
468     // set both to left limit
469     if (d0 > d1)
470         d1 = d0;
471 
472     d0 = d1;
473 
474     limit_left  = d0 - 6;
475     limit_right = -d1 + 6;
476 
477     if (std::abs(motor_centre_pos - 0x80) > 0x20)
478     {
479         ohud.blit_text_new(col2, 14, "FAIL DIST");
480         motor_enabled = false;
481     }
482     else if (!fail)
483     {
484         ohud.blit_text_new(col2, 14, Utils::to_hex_string(motor_centre_pos).c_str(), 0x80);
485     }
486 
487     ohud.blit_text_new(13, 17, "TESTS COMPLETE!", 0x82);
488 
489     hw_motor_control = MOTOR_OFF; // switch off
490     counter          = 90;
491     motor_state      = STATE_DONE;
492 }
493 
calibrate_done()494 void OOutputs::calibrate_done()
495 {
496     if (counter > 0)
497         counter--;
498     else
499         motor_state = STATE_EXIT;
500 }
501 
502 // ------------------------------------------------------------------------------------------------
503 // Moving Cabinet Code
504 // ------------------------------------------------------------------------------------------------
505 
506 const static uint8_t MOTOR_VALUES[] =
507 {
508     2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3,
509     2, 2, 3, 3, 4, 4, 5, 5, 2, 3, 4, 5, 6, 7, 7, 7
510 };
511 
512 const static uint8_t MOTOR_VALUES_STATIONARY[] =
513 {
514     2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4
515 };
516 
517 const static uint8_t MOTOR_VALUES_OFFROAD1[] =
518 {
519     0x8, 0x8, 0x5, 0x5, 0x8, 0x8, 0xB, 0xB, 0x8, 0x8, 0x4, 0x4, 0x8, 0x8, 0xC, 0xC,
520     0x8, 0x8, 0x3, 0x3, 0x8, 0x8, 0xD, 0xD, 0x8, 0x8, 0x2, 0x2, 0x8, 0x8, 0xE, 0xE,
521 };
522 
523 const static uint8_t MOTOR_VALUES_OFFROAD2[] =
524 {
525     0x8, 0x5, 0x8, 0xB, 0x8, 0x5, 0x8, 0xB, 0x8, 0x4, 0x8, 0xC, 0x8, 0x4, 0x8, 0xC,
526     0x8, 0x3, 0x8, 0xD, 0x8, 0x3, 0x8, 0xD, 0x8, 0x2, 0x8, 0xE, 0x8, 0x2, 0x8, 0xE,
527 };
528 
529 const static uint8_t MOTOR_VALUES_OFFROAD3[] =
530 {
531     0x8, 0x5, 0x5, 0x8, 0xB, 0x8, 0x0, 0x8, 0x8, 0x4, 0x4, 0x8, 0xC, 0x8, 0x0, 0x8,
532     0x8, 0x3, 0x3, 0x8, 0xD, 0x8, 0x0, 0x8, 0x8, 0x2, 0x2, 0x8, 0xE, 0x8, 0x0, 0x8,
533 };
534 
535 const static uint8_t MOTOR_VALUES_OFFROAD4[] =
536 {
537     0x8, 0xB, 0xB, 0x8, 0x5, 0x8, 0x0, 0x8, 0x8, 0xC, 0xC, 0x8, 0x4, 0x8, 0x0, 0x8,
538     0x8, 0xD, 0xD, 0x8, 0x3, 0x8, 0x0, 0x8, 0x8, 0xE, 0xE, 0x8, 0x2, 0x8, 0x0, 0x8,
539 };
540 
541 
542 // Process Motor Code.
543 // Note, that only the Deluxe Moving Motor Code is ported for now.
544 // Source: 0xE644
do_motors(int MODE,int16_t input_motor)545 void OOutputs::do_motors(int MODE, int16_t input_motor)
546 {
547     motor_x_change = -(input_motor - (MODE == MODE_FFEEDBACK ? CENTRE_POS : motor_centre_pos));
548 
549     if (!motor_enabled)
550     {
551         done();
552         return;
553     }
554 
555     // In-Game: Test for crash, skidding, whether car is moving
556     if (outrun.game_state == GS_INGAME)
557     {
558         if (ocrash.crash_counter)
559         {
560             if ((oinitengine.car_increment >> 16) <= 0x14)
561                 car_stationary();
562             else
563                 do_motor_crash();
564         }
565         else if (ocrash.skid_counter)
566         {
567             do_motor_crash();
568         }
569         else
570         {
571             if ((oinitengine.car_increment >> 16) <= 0x14)
572             {
573                 if (!was_small_change)
574                     done();
575                 else
576                     car_stationary();
577             }
578             else
579                 car_moving(MODE);
580         }
581     }
582     // Not In-Game: Act as though car is stationary / moving slow
583     else
584     {
585         car_stationary();
586     }
587 }
588 
589 // Source: 0xE6DA
car_moving(const int MODE)590 void OOutputs::car_moving(const int MODE)
591 {
592     // Motor is currently moving
593     if (motor_movement)
594     {
595         hw_motor_control = motor_control;
596         adjust_motor();
597         return;
598     }
599 
600     // Motor is not currently moving. Setup new movement as necessary.
601     if (oferrari.wheel_state != OFerrari::WHEELS_ON)
602     {
603         do_motor_offroad();
604         return;
605     }
606 
607     const uint16_t car_inc = oinitengine.car_increment >> 16;
608     if (car_inc <= 0x64)                    speed = 0;
609     else if (car_inc <= 0xA0)               speed = 1 << 3;
610     else if (car_inc <= 0xDC)               speed = 2 << 3;
611     else                                    speed = 3 << 3;
612 
613     if (oinitengine.road_curve == 0)         curve = 0;
614     else if (oinitengine.road_curve <= 0x3C) curve = 2; // sharp curve
615     else if (oinitengine.road_curve <= 0x5A) curve = 1; // gentle curve
616     else                                     curve = 0;
617 
618     int16_t steering = oinputs.steering_adjust;
619     steering += (movement_adjust1 + movement_adjust2 + movement_adjust3);
620     steering >>= 2;
621     movement_adjust3 = movement_adjust2;                   // Trickle down values
622     movement_adjust2 = movement_adjust1;
623     movement_adjust1 = oinputs.steering_adjust;
624 
625     // Veer Left
626     if (steering >= 0)
627     {
628         steering = (steering >> 4) - 1;
629         if (steering < 0)
630         {
631             car_stationary();
632             return;
633         }
634 
635         if (steering > 0)
636             steering--;
637 
638         uint8_t motor_value = MOTOR_VALUES[speed + curve];
639 
640         if (MODE == MODE_FFEEDBACK)
641         {
642             hw_motor_control = motor_value + 8;
643         }
644         else
645         {
646             int16_t change = motor_x_change + (motor_value << 1);
647             // Latch left movement
648             if (change >= limit_left)
649             {
650                 hw_motor_control   = MOTOR_CENTRE;
651                 motor_movement     = 1;
652                 motor_control      = 7;
653                 motor_change_latch = motor_x_change;
654             }
655             else
656             {
657                 hw_motor_control = motor_value + 8;
658             }
659         }
660 
661         done();
662     }
663     // Veer Right
664     else
665     {
666         steering = -steering;
667         steering = (steering >> 4) - 1;
668         if (steering < 0)
669         {
670             car_stationary();
671             return;
672         }
673 
674         if (steering > 0)
675             steering--;
676 
677         uint8_t motor_value = MOTOR_VALUES[speed + curve];
678 
679         if (MODE == MODE_FFEEDBACK)
680         {
681             hw_motor_control = -motor_value + 8;
682         }
683         else
684         {
685             int16_t change = motor_x_change - (motor_value << 1);
686             // Latch right movement
687             if (change <= limit_right)
688             {
689                 hw_motor_control   = MOTOR_CENTRE;
690                 motor_movement     = -1;
691                 motor_control      = 9;
692                 motor_change_latch = motor_x_change;
693             }
694             else
695             {
696                 hw_motor_control = -motor_value + 8;
697             }
698         }
699 
700         done();
701     }
702 }
703 
704 // Source: 0xE822
car_stationary()705 void OOutputs::car_stationary()
706 {
707     int16_t change = std::abs(motor_x_change);
708 
709     if (change <= 8)
710     {
711         if (!is_centered)
712         {
713             hw_motor_control = MOTOR_CENTRE;
714             is_centered      = true;
715         }
716         else
717         {
718             hw_motor_control = MOTOR_OFF;
719             is_centered      = false;
720             done();
721         }
722     }
723     else
724     {
725         int8_t motor_value = MOTOR_VALUES_STATIONARY[change >> 3];
726 
727         if (motor_x_change >= 0)
728             motor_value = -motor_value;
729 
730         hw_motor_control = motor_value + 8;
731 
732         done();
733     }
734 }
735 
736 // Source: 0xE8DA
adjust_motor()737 void OOutputs::adjust_motor()
738 {
739     int16_t change = motor_change_latch; // d1
740     motor_change_latch = motor_x_change;
741     change -= motor_x_change;
742     if (change < 0)
743         change = -change;
744 
745     // no movement
746     if (change <= 2)
747     {
748         motor_movement = 0;
749         is_centered    = true;
750     }
751     // moving right
752     else if (motor_movement < 0)
753     {
754         if (++motor_control > 10)
755             motor_control = 10;
756     }
757     // moving left
758     else
759     {
760         if (--motor_control < 6)
761             motor_control = 6;
762     }
763 
764     done();
765 }
766 
767 // Adjust motor during crash/skid state
768 // Source: 0xE994
do_motor_crash()769 void OOutputs::do_motor_crash()
770 {
771     if (oferrari.car_x_diff == 0)
772         set_value(MOTOR_VALUES_OFFROAD1, 3);
773     else if (oferrari.car_x_diff < 0)
774         set_value(MOTOR_VALUES_OFFROAD4, 3);
775     else
776         set_value(MOTOR_VALUES_OFFROAD3, 3);
777 }
778 
779 // Adjust motor when wheels are off-road
780 // Source: 0xE9BE
do_motor_offroad()781 void OOutputs::do_motor_offroad()
782 {
783     const uint8_t* table = (oferrari.wheel_state != OFerrari::WHEELS_OFF) ? MOTOR_VALUES_OFFROAD2 : MOTOR_VALUES_OFFROAD1;
784 
785     const uint16_t car_inc = oinitengine.car_increment >> 16;
786     uint8_t index;
787     if (car_inc <= 0x32)      index = 0;
788     else if (car_inc <= 0x50) index = 1;
789     else if (car_inc <= 0x6E) index = 2;
790     else                      index = 3;
791 
792     set_value(table, index);
793 }
794 
set_value(const uint8_t * table,uint8_t index)795 void OOutputs::set_value(const uint8_t* table, uint8_t index)
796 {
797     hw_motor_control = table[(index << 3) + (counter & 7)];
798     counter++;
799     done();
800 }
801 
802 // Source: 0xE94E
done()803 void OOutputs::done()
804 {
805     if (std::abs(motor_x_change) <= 8)
806     {
807         was_small_change = true;
808         motor_control    = MOTOR_CENTRE;
809     }
810     else
811     {
812         was_small_change = false;
813     }
814 }
815 
816 // Send output commands to motor hardware
817 // This is the equivalent to writing to register 0x140003
motor_output(uint8_t cmd)818 void OOutputs::motor_output(uint8_t cmd)
819 {
820     if (cmd == MOTOR_OFF || cmd == MOTOR_CENTRE)
821         return;
822 
823     int8_t force = 0;
824 
825     if (cmd < MOTOR_CENTRE)      // left
826         force = cmd - 1;
827     else if (cmd > MOTOR_CENTRE) // right
828         force = 15 - cmd;
829 
830     forcefeedback::set(cmd, force);
831 }
832 
833 // ------------------------------------------------------------------------------------------------
834 // Deluxe Upright: Steering Wheel Movement
835 // ------------------------------------------------------------------------------------------------
836 
837 // Deluxe Upright: Vibration Enable Table. 4 Groups of vibration values.
838 const static uint8_t VIBRATE_LOOKUP[] =
839 {
840     // SLOW SPEED --------   // MEDIUM SPEED ------
841     1, 0, 0, 0, 1, 0, 0, 0,  1, 1, 0, 0, 1, 1, 0, 0,
842     // FAST SPEED --------   // VERY FAST SPEED ---
843     1, 1, 1, 0, 1, 1, 1, 0,  1, 1, 1, 1, 1, 1, 1, 1,
844 };
845 
846 // Source: 0xEAAA
do_vibrate_upright()847 void OOutputs::do_vibrate_upright()
848 {
849     if (outrun.game_state != GS_INGAME)
850     {
851         clear_digital(D_MOTOR);
852         return;
853     }
854 
855     const uint16_t speed = oinitengine.car_increment >> 16;
856     uint16_t index = 0;
857 
858     // Car Crashing: Diable Motor once speed below 10
859     if (ocrash.crash_counter)
860     {
861         if (speed <= 10)
862         {
863             clear_digital(D_MOTOR);
864             return;
865         }
866     }
867     // Car Normal
868     else if (!ocrash.skid_counter)
869     {
870         // 0xEAE2: Disable Vibration once speed below 30 or wheels on-road
871         if (speed < 30 || oferrari.wheel_state == OFerrari::WHEELS_ON)
872         {
873             clear_digital(D_MOTOR);
874             return;
875         }
876 
877         // 0xEAFC: Both wheels off-road. Faster the car speed, greater the chance of vibrating
878         if (oferrari.wheel_state == OFerrari::WHEELS_OFF)
879         {
880             if (speed > 220)      index = 3;
881             else if (speed > 170) index = 2;
882             else if (speed > 120) index = 1;
883         }
884         // 0xEB38: One wheel off-road. Faster the car speed, greater the chance of vibrating
885         else
886         {
887             if (speed > 270)      index = 3;
888             else if (speed > 210) index = 2;
889             else if (speed > 150) index = 1;
890         }
891 
892         if (VIBRATE_LOOKUP[ (vibrate_counter & 7) + (index << 3) ])
893             set_digital(D_MOTOR);
894         else
895             clear_digital(D_MOTOR);
896 
897         vibrate_counter++;
898         return;
899     }
900     // 0xEB68: Car Crashing or Skidding
901     if (speed > 140)      index = 3;
902     else if (speed > 100) index = 2;
903     else if (speed > 60)  index = 1;
904 
905     if (VIBRATE_LOOKUP[ (vibrate_counter & 7) + (index << 3) ])
906         set_digital(D_MOTOR);
907     else
908         clear_digital(D_MOTOR);
909 
910     vibrate_counter++;
911 }
912 
913 // ------------------------------------------------------------------------------------------------
914 // Mini Upright: Steering Wheel Movement
915 // ------------------------------------------------------------------------------------------------
916 
do_vibrate_mini()917 void OOutputs::do_vibrate_mini()
918 {
919     if (outrun.game_state != GS_INGAME)
920     {
921         clear_digital(D_MOTOR);
922         return;
923     }
924 
925     const uint16_t speed = oinitengine.car_increment >> 16;
926     uint16_t index = 0;
927 
928     // Car Crashing: Disable Motor once speed below 10
929     if (ocrash.crash_counter)
930     {
931         if (speed <= 10)
932         {
933             clear_digital(D_MOTOR);
934             return;
935         }
936     }
937     // Car Normal
938     else if (!ocrash.skid_counter)
939     {
940         if (speed < 10 || oferrari.wheel_state == OFerrari::WHEELS_ON)
941         {
942             clear_digital(D_MOTOR);
943             return;
944         }
945 
946         if (speed > 140)      index = 5;
947         else if (speed > 100) index = 4;
948         else if (speed > 60)  index = 3;
949         else if (speed > 20)  index = 2;
950         else                  index = 1;
951 
952         if (index > vibrate_counter)
953         {
954             vibrate_counter = 0;
955             clear_digital(D_MOTOR);
956         }
957         else
958         {
959             vibrate_counter++;
960             set_digital(D_MOTOR);
961         }
962         return;
963     }
964 
965     // 0xEC7A calc_crash_skid:
966     if (speed > 90)      index = 4;
967     else if (speed > 70) index = 3;
968     else if (speed > 50) index = 2;
969     else if (speed > 30) index = 1;
970     if (index > vibrate_counter)
971     {
972         vibrate_counter = 0;
973         clear_digital(D_MOTOR);
974     }
975     else
976     {
977         vibrate_counter++;
978         set_digital(D_MOTOR);
979     }
980 }
981 
982 // ------------------------------------------------------------------------------------------------
983 // Coin Chute Output
984 // Source: 0x6F8C
985 // ------------------------------------------------------------------------------------------------
986 
coin_chute_out(CoinChute * chute,bool insert)987 void OOutputs::coin_chute_out(CoinChute* chute, bool insert)
988 {
989     // Initalize counter if coin inserted
990     chute->counter[2] = insert ? 1 : 0;
991 
992     if (chute->counter[0])
993     {
994         if (--chute->counter[0] != 0)
995             return;
996         chute->counter[1] = 6;
997         clear_digital(chute->output_bit);
998     }
999     else if (chute->counter[1])
1000     {
1001         chute->counter[1]--;
1002     }
1003     // Coin first inserted. Called Once.
1004     else if (chute->counter[2])
1005     {
1006         chute->counter[2]--;
1007         chute->counter[0] = 6;
1008         set_digital(chute->output_bit);
1009     }
1010 }