1 /***************************************************************************
2     In-Game Statistics.
3     - Stage Timers
4     - Route Info
5     - Speed to Score Conversion
6     - Bonus Time Increment
7 
8     Copyright Chris White.
9     See license.txt for more details.
10 ***************************************************************************/
11 
12 #include "engine/ohud.hpp"
13 #include "engine/omusic.hpp"
14 #include "engine/outils.hpp"
15 #include "engine/ostats.hpp"
16 #include "engine/otraffic.hpp"
17 
18 OStats ostats;
19 
20 // Original buggy millisecond lookup table (Used when 64 frames = 1 second)
21 // Conversion table from 0 to 64 -> Millisecond value
22 const static uint8_t LAP_MS_64[] =
23 {
24     0x00, 0x01, 0x03, 0x04, 0x06, 0x07, 0x09, 0x10, 0x12, 0x14, 0x15, 0x17, 0x18, 0x20, 0x21, 0x23,
25     0x25, 0x26, 0x28, 0x29, 0x31, 0x32, 0x34, 0x35, 0x37, 0x39, 0x40, 0x42, 0x43, 0x45, 0x46, 0x48,
26     0x50, 0x51, 0x53, 0x54, 0x56, 0x57, 0x59, 0x60, 0x62, 0x64, 0x65, 0x67, 0x68, 0x70, 0x71, 0x73,
27     0x75, 0x76, 0x78, 0x79, 0x81, 0x82, 0x84, 0x85, 0x87, 0x89, 0x90, 0x92, 0x93, 0x95, 0x96, 0x98,
28 };
29 
30 // Bug fixed millisecond lookup table  (Used when 60 frames = 1 second)
31 // Conversion table from 0 to 60 -> Millisecond value
32 const static uint8_t LAP_MS_60[] =
33 {
34     0x00, 0x01, 0x03, 0x05, 0x06, 0x08, 0x10, 0x11, 0x13, 0x15, 0x16, 0x18, 0x20, 0x21, 0x23, 0x25,
35     0x26, 0x28, 0x30, 0x31, 0x33, 0x35, 0x36, 0x38, 0x40, 0x41, 0x43, 0x45, 0x46, 0x48,
36     0x50, 0x51, 0x53, 0x55, 0x56, 0x58, 0x60, 0x61, 0x63, 0x65, 0x66, 0x68, 0x70, 0x71, 0x73, 0x75,
37     0x76, 0x78, 0x80, 0x81, 0x83, 0x85, 0x86, 0x88, 0x90, 0x91, 0x93, 0x95, 0x96, 0x98
38 };
39 
OStats(void)40 OStats::OStats(void)
41 {
42 }
43 
~OStats(void)44 OStats::~OStats(void)
45 {
46 }
47 
init(bool ttrial)48 void OStats::init(bool ttrial)
49 {
50     credits = ttrial ? 1 : 0;
51     // Choose correct lookup table if timing bugs fixed
52     lap_ms = config.engine.fix_timer ? LAP_MS_60 : LAP_MS_64;
53 }
54 
clear_stage_times()55 void OStats::clear_stage_times()
56 {
57     for (int i = 0; i < 15; i++)
58     {
59         stage_counters[i] = 0;
60 
61         for (int j = 0; j < 3; j++)
62             stage_times[i][j] = 0;
63     }
64 }
65 
clear_route_info()66 void OStats::clear_route_info()
67 {
68     route_info = 0;
69     routes[0] = routes[1] = routes[2] = routes[3] =
70     routes[4] = routes[5] = routes[6] = routes[7] = 0;
71 }
72 
73 // Increment Counters, Stage Timers & Print Stage Timers
74 //
75 // Source: 0x7F12
do_timers()76 void OStats::do_timers()
77 {
78     if (outrun.game_state != GS_INGAME) return;
79 
80     inc_lap_timer();
81 
82     if (outrun.cannonball_mode == Outrun::MODE_ORIGINAL || outrun.cannonball_mode == Outrun::MODE_CONT)
83     {
84         // Each stage has a standard counter that just increments. Do this here.
85         stage_counters[cur_stage]++;
86         ohud.draw_lap_timer(0x11016C, stage_times[cur_stage], ms_value);
87     }
88 
89     else if (outrun.cannonball_mode == Outrun::MODE_TTRIAL)
90     {
91         stage_counters[outrun.ttrial.current_lap]++;
92         ohud.draw_stage_number(ohud.translate(30, 2 + outrun.ttrial.current_lap), (outrun.ttrial.current_lap + 1), OHud::GREY);
93         ohud.draw_lap_timer(ohud.translate(32, 2 + outrun.ttrial.current_lap), stage_times[cur_stage], ms_value);
94     }
95 }
96 
97 // Increment and store lap timer for each stage.
98 //
99 // Source: 0x7F4C
inc_lap_timer()100 void OStats::inc_lap_timer()
101 {
102     // Add MS (Not actual milliseconds, as these are looked up from the table below)
103     if (++stage_times[cur_stage][2] >= (config.engine.fix_timer ? 0x3C : 0x40))
104     {
105         // Looped MS, so add a second
106         stage_times[cur_stage][2] = 0;
107         stage_times[cur_stage][1] = outils::bcd_add(stage_times[cur_stage][1], 1);
108 
109         // Loop seconds, so add a minute
110         if (stage_times[cur_stage][1] == 0x60)
111         {
112             stage_times[cur_stage][1] = 0;
113             stage_times[cur_stage][0] = outils::bcd_add(stage_times[cur_stage][0], 1);
114         }
115     }
116 
117     // Get MS Value
118     ms_value = lap_ms[stage_times[cur_stage][2]];
119 }
120 
121 // Source: 0xBE4E
convert_speed_score(uint16_t speed)122 void OStats::convert_speed_score(uint16_t speed)
123 {
124     // 0x960 is the last value in this table to be actively used
125     static const uint16_t CONVERT[] =
126     {
127         0x0,   0x10,  0x20,  0x30,  0x40,  0x50,  0x60,  0x80,  0x110, 0x150,
128         0x200, 0x260, 0x330, 0x410, 0x500, 0x600, 0x710, 0x830, 0x960, 0x1100,
129         0x1250,
130     };
131 
132     uint16_t score = CONVERT[(speed >> 4)];
133     update_score(score);
134 }
135 
136 // Update In-Game Score. Adds Value To Overall Score.
137 //
138 // Source: 0x7340
update_score(uint32_t value)139 void OStats::update_score(uint32_t value)
140 {
141     if (outrun.cannonball_mode == Outrun::MODE_TTRIAL)
142         return;
143 
144     score = outils::bcd_add(value, score);
145 
146     if (score > 0x99999999)
147         score = 0x99999999;
148 
149     ohud.draw_score_ingame(score);
150 }
151 
152 // Initialize Next Level
153 //
154 // In-Game Only:
155 //
156 // 1/ Show Extend Play Timer
157 // 2/ Add correct time extend for time adjustment setting from dips
158 // 3/ Setup next level with relevant number of enemies
159 // 4/ Blit some info to the screen
160 //
161 // Source: 0x8FAC
162 
init_next_level()163 void OStats::init_next_level()
164 {
165     if (extend_play_timer)
166     {
167         // End Extend Play: Clear Text From Screen
168         if (--extend_play_timer <= 0)
169         {
170             ohud.blit_text1(TEXT1_EXTEND_CLEAR1);
171             ohud.blit_text1(TEXT1_EXTEND_CLEAR2);
172             ohud.blit_text1(TEXT1_LAPTIME_CLEAR1);
173             ohud.blit_text1(TEXT1_LAPTIME_CLEAR2);
174         }
175         // Extend Play: Flash Text
176         else
177         {
178             int16_t do_blit = ((extend_play_timer - 1) ^ extend_play_timer) & BIT_3;
179 
180             if (do_blit)
181             {
182                 if (extend_play_timer & BIT_3)
183                 {
184                     if (outrun.cannonball_mode == Outrun::MODE_TTRIAL)
185                         ohud.blit_text_new(15, 8, "BEST LAP!", OHud::PINK);
186                     else
187                     {
188                         ohud.blit_text1(TEXT1_EXTEND1);
189                         ohud.blit_text1(TEXT1_EXTEND2);
190                     }
191                 }
192                 else
193                 {
194                     ohud.blit_text1(TEXT1_EXTEND_CLEAR1);
195                     ohud.blit_text1(TEXT1_EXTEND_CLEAR2);
196                 }
197             }
198         }
199     }
200     else if (outrun.game_state == GS_INGAME && oinitengine.checkpoint_marker)
201     {
202         oinitengine.checkpoint_marker = 0;
203         extend_play_timer             = 0x80;
204 
205         // Calculate Time To Add
206         uint16_t time_lookup = (config.engine.dip_time * 40) + oroad.stage_lookup_off;
207         if (!outrun.freeze_timer)
208         {
209             if (outrun.cannonball_mode == outrun.MODE_ORIGINAL)
210                 time_counter = outils::bcd_add(time_counter, TIME[time_lookup]);
211             else if (outrun.cannonball_mode == outrun.MODE_CONT)
212                 time_counter = outils::bcd_add(time_counter, 0x55);
213 
214             if (time_counter > 0x99) time_counter = 0x99;
215         }
216 
217         // Draw last laptime
218         // Note there is a bug in the original code here, where the current ms value is displayed, instead of the ms value from the last lap time
219         ohud.blit_text1(TEXT1_LAPTIME1);
220         ohud.blit_text1(TEXT1_LAPTIME2);
221         ohud.draw_lap_timer(0x110554, stage_times[cur_stage-1], config.engine.fix_bugs ? lap_ms[stage_times[cur_stage-1][2]] : ms_value);
222 
223         otraffic.set_max_traffic();
224         osoundint.queue_sound(sound::YM_CHECKPOINT);
225         osoundint.queue_sound(sound::VOICE_CHECKPOINT);
226 
227         // Update Stage Number on HUD
228         ohud.draw_stage_number(0x110d76, cur_stage+1);
229         // No need to redraw the stage info as that was a bug in the original game
230     }
231 }
232 
233 // Time Tables
234 //
235 // - Show how much time will be incremented to the counter at each stage
236 // - Rightmost routes first
237 // - Note there appears to be an error with the Stage 3a Normal entry
238 //
239 //         | Easy | Norm | Hard | VHar |
240 //         '------'------'------'------'
241 //Stage 1  |  80     75     72     70  |
242 //         '---------------------------'
243 //Stage 2a |  65     65     65     65  |
244 //Stage 2b |  62     62     62     62  |
245 //         '---------------------------'
246 //Stage 3a |  57     55     57     57  |
247 //Stage 3b |  62     60     60     60  |
248 //Stage 3c |  60     60     59     58  |
249 //         '---------------------------'
250 //Stage 4a |  66     65     64     62  |
251 //Stage 4b |  63     62     60     60  |
252 //Stage 4c |  61     60     58     58  |
253 //Stage 4d |  65     65     63     63  |
254 //         '---------------------------'
255 //Stage 5a |  58     56     54     54  |
256 //Stage 5b |  55     56     54     54  |
257 //Stage 5c |  56     56     54     54  |
258 //Stage 5d |  58     56     54     54  |
259 //Stage 5e |  56     56     56     56  |
260 //         '---------------------------'
261 
262 
263 const uint8_t OStats::TIME[] =
264 {
265     // Easy
266     0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
267     0x65, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
268     0x57, 0x62, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00,
269     0x66, 0x63, 0x61, 0x65, 0x00, 0x00, 0x00, 0x00,
270     0x58, 0x55, 0x56, 0x58, 0x56, 0x00, 0x00, 0x00,
271 
272     // Normal
273     0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
274     0x65, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
275     0x55, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00,
276     0x65, 0x62, 0x60, 0x65, 0x00, 0x00, 0x00, 0x00,
277     0x56, 0x56, 0x56, 0x56, 0x56, 0x00, 0x00, 0x00,
278 
279     // Hard
280     0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
281     0x65, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
282     0x57, 0x60, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00,
283     0x64, 0x60, 0x58, 0x63, 0x00, 0x00, 0x00, 0x00,
284     0x54, 0x54, 0x54, 0x54, 0x56, 0x00, 0x00, 0x00,
285 
286     // Hardest
287     0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
288     0x65, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
289     0x57, 0x60, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00,
290     0x62, 0x60, 0x58, 0x63, 0x00, 0x00, 0x00, 0x00,
291     0x54, 0x54, 0x54, 0x54, 0x56, 0x00, 0x00, 0x00,
292 };
293