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