1 /***************************************************************************
2 Music Selection Screen.
3
4 This is a combination of a tilemap and overlayed sprites.
5
6 Copyright Chris White.
7 See license.txt for more details.
8 ***************************************************************************/
9
10 #include "main.hpp"
11 #include "engine/oferrari.hpp"
12 #include "engine/ohud.hpp"
13 #include "engine/oinputs.hpp"
14 #include "engine/ologo.hpp"
15 #include "engine/omusic.hpp"
16 #include "engine/otiles.hpp"
17 #include "engine/otraffic.hpp"
18 #include "engine/ostats.hpp"
19
20 OMusic omusic;
21
OMusic(void)22 OMusic::OMusic(void)
23 {
24 tilemap = NULL;
25 tile_patch = NULL;
26 }
27
28
~OMusic(void)29 OMusic::~OMusic(void)
30 {
31 if (tilemap) delete tilemap;
32 if (tile_patch) delete tile_patch;
33 }
34
35 // Load Modified Widescreen version of tilemap
load_widescreen_map(std::string path)36 bool OMusic::load_widescreen_map(std::string path)
37 {
38 int status = 0;
39
40 if (tilemap == NULL)
41 {
42 tilemap = new RomLoader();
43 status += tilemap->load_binary(std::string(path + "tilemap.bin").c_str());
44 }
45
46 if (tile_patch == NULL)
47 {
48 tile_patch = new RomLoader();
49 status += tile_patch->load_binary(std::string(path + "tilepatch.bin").c_str());
50 }
51
52 return status == 0;
53 }
54
55 // Initialize Music Selection Screen
56 //
57 // Source: 0xB342
enable()58 void OMusic::enable()
59 {
60 oferrari.car_ctrl_active = false;
61 video.clear_text_ram();
62 osprites.disable_sprites();
63 otraffic.disable_traffic();
64 //edit jump table 3
65 oinitengine.car_increment = 0;
66 oferrari.car_inc_old = 0;
67 osprites.spr_cnt_main = 0;
68 osprites.spr_cnt_shadow = 0;
69 oroad.road_ctrl = ORoad::ROAD_BOTH_P0;
70 oroad.horizon_base = ORoad::HORIZON_OFF;
71 last_music_selected = -1;
72 preview_counter = -20; // Delay before playing music
73 ostats.time_counter = config.sound.music_timer; // Move 30 seconds to timer countdown (note on the original roms this is 15 seconds)
74 ostats.frame_counter = ostats.frame_reset;
75
76 blit_music_select();
77 ohud.blit_text2(TEXT2_SELECT_MUSIC); // Select Music By Steering
78
79 osoundint.queue_sound(sound::RESET);
80 if (!config.sound.preview)
81 osoundint.queue_sound(sound::PCM_WAVE); // Wave Noises
82
83 // Enable block of sprites
84 entry_start = OSprites::SPRITE_ENTRIES - 0x10;
85 for (int i = entry_start; i < entry_start + 5; i++)
86 {
87 osprites.jump_table[i].init(i);
88 }
89
90 setup_sprite1();
91 setup_sprite2();
92 setup_sprite3();
93 setup_sprite4();
94 setup_sprite5();
95
96 // Widescreen tiles need additional palette information copied over
97 if (tile_patch->loaded && config.s16_x_off > 0)
98 {
99 video.tile_layer->patch_tiles(tile_patch);
100 otiles.setup_palette_widescreen();
101 }
102
103 video.tile_layer->set_x_clamp(video.tile_layer->CENTRE);
104 cursor_pos = 1;
105 total_tracks = (int)config.sound.music.size();
106 }
107
disable()108 void OMusic::disable()
109 {
110 // Disable block of sprites
111 for (int i = entry_start; i < entry_start + 5; i++)
112 {
113 osprites.jump_table[i].control &= ~OSprites::ENABLE;
114 }
115
116 video.tile_layer->set_x_clamp(video.tile_layer->RIGHT);
117
118 // Restore original palette for widescreen tiles.
119 if (config.s16_x_off > 0)
120 {
121 video.tile_layer->restore_tiles();
122 otiles.setup_palette_tilemap();
123 }
124
125 video.enabled = false; // Turn screen off
126 }
127
128 // Music Selection Screen: Setup Radio Sprite
129 // Source: 0xCAF0
setup_sprite1()130 void OMusic::setup_sprite1()
131 {
132 oentry *e = &osprites.jump_table[entry_start + 0];
133 e->x = 28;
134 e->y = 180;
135 e->road_priority = 0xFF;
136 e->priority = 0x1FE;
137 e->zoom = 0x7F;
138 e->pal_src = 0xB0;
139 e->addr = outrun.adr.sprite_radio;
140 osprites.map_palette(e);
141 }
142
143 // Music Selection Screen: Setup Equalizer Sprite
144 // Source: 0xCB2A
setup_sprite2()145 void OMusic::setup_sprite2()
146 {
147 oentry *e = &osprites.jump_table[entry_start + 1];
148 e->x = 4;
149 e->y = 189;
150 e->road_priority = 0xFF;
151 e->priority = 0x1FE;
152 e->zoom = 0x7F;
153 e->pal_src = 0xA7;
154 e->addr = outrun.adr.sprite_eq;
155 osprites.map_palette(e);
156 }
157
158 // Music Selection Screen: Setup FM Radio Readout
159 // Source: 0xCB64
setup_sprite3()160 void OMusic::setup_sprite3()
161 {
162 oentry *e = &osprites.jump_table[entry_start + 2];
163 e->x = -8;
164 e->y = 176;
165 e->road_priority = 0xFF;
166 e->priority = 0x1FE;
167 e->zoom = 0x7F;
168 e->pal_src = 0x87;
169 e->addr = outrun.adr.sprite_fm_left;
170 osprites.map_palette(e);
171 }
172
173 // Music Selection Screen: Setup FM Radio Dial
174 // Source: 0xCB9E
setup_sprite4()175 void OMusic::setup_sprite4()
176 {
177 oentry *e = &osprites.jump_table[entry_start + 3];
178 e->x = 68;
179 e->y = 181;
180 e->road_priority = 0xFF;
181 e->priority = 0x1FE;
182 e->zoom = 0x7F;
183 e->pal_src = 0x89;
184 e->addr = outrun.adr.sprite_dial_left;
185 osprites.map_palette(e);
186 }
187
188 // Music Selection Screen: Setup Hand Sprite
189 // Source: 0xCBD8
setup_sprite5()190 void OMusic::setup_sprite5()
191 {
192 oentry *e = &osprites.jump_table[entry_start + 4];
193 e->x = 21;
194 e->y = 196;
195 e->road_priority = 0xFF;
196 e->priority = 0x1FE;
197 e->zoom = 0x7F;
198 e->pal_src = 0xAF;
199 e->addr = outrun.adr.sprite_hand_left;
200 osprites.map_palette(e);
201 }
202
203 // Check for start button during music selection screen
204 //
205 // Source: 0xB768
check_start()206 void OMusic::check_start()
207 {
208 if (ostats.credits && input.has_pressed(Input::START))
209 {
210 outrun.game_state = GS_INIT_GAME;
211 ologo.disable();
212 disable();
213 }
214 }
215
216 // Tick and Blit
tick()217 void OMusic::tick()
218 {
219 // Radio Sprite
220 osprites.do_spr_order_shadows(&osprites.jump_table[entry_start + 0]);
221
222 // Animated EQ Sprite (Cycle the graphical equalizer on the radio)
223 oentry *e = &osprites.jump_table[entry_start + 1];
224 e->reload++; // Increment palette entry
225 e->pal_src = roms.rom0.read8((e->reload & 0x3E) >> 1 | MUSIC_EQ_PAL);
226 osprites.map_palette(e);
227 osprites.do_spr_order_shadows(e);
228
229 // Draw appropriate FM station on radio, depending on steering setting
230 // Draw Dial on radio, depending on steering setting
231 e = &osprites.jump_table[entry_start + 2];
232 oentry *dial = &osprites.jump_table[entry_start + 3];
233 oentry *hand = &osprites.jump_table[entry_start + 4];
234
235 // Determine Track Selection Logic
236 if (total_tracks < 3) tick_original(e, dial, hand);
237 else tick_enhanced(e, dial, hand);
238
239 osprites.do_spr_order_shadows(e);
240 osprites.do_spr_order_shadows(dial);
241 osprites.do_spr_order_shadows(hand);;
242
243 // Enhancement: Preview Music On Sound Selection Screen
244 if (config.sound.preview)
245 {
246 if (music_selected != last_music_selected)
247 {
248 if (preview_counter == 0 && last_music_selected != -1)
249 osoundint.queue_sound(sound::FM_RESET);
250
251 if (++preview_counter >= 10)
252 {
253 play_music();
254 preview_counter = 0;
255 }
256 }
257 }
258 }
259
play_music(int index)260 void OMusic::play_music(int index)
261 {
262 if (index == -1) index = music_selected;
263
264 next_track = &config.sound.music.at(index);
265
266 switch (next_track->type)
267 {
268 case music_t::IS_YM_INT:
269 cannonball::audio.clear_wav();
270 osoundint.queue_sound(next_track->cmd);
271 break;
272
273 case music_t::IS_YM_EXT:
274 cannonball::audio.clear_wav();
275 roms.load_ym_data((config.data.res_path + next_track->filename).c_str());
276 osoundint.queue_sound(next_track->cmd);
277 break;
278
279 case music_t::IS_WAV:
280 cannonball::audio.load_wav((config.data.res_path + next_track->filename).c_str());
281 break;
282 }
283
284 last_music_selected = index;
285 }
286
287 // Cycle music in continuous mode
cycle_music()288 void OMusic::cycle_music()
289 {
290 if (++music_selected > 2) music_selected = 0;
291 play_music();
292 }
293
294 // Original Version of Music Selection Screen With 3 Tracks.
295 // Wheel Left = Track 0, Wheel Centre = Track 1, Wheel Right = Track 2
tick_original(oentry * fm,oentry * dial,oentry * hand)296 void OMusic::tick_original(oentry* fm, oentry* dial, oentry* hand)
297 {
298 // Note tiles to append to left side of text
299 const uint32_t NOTE_TILES1 = 0x8A7A8A7B;
300 const uint32_t NOTE_TILES2 = 0x8A7C8A7D;
301
302 // Steer Left
303 if (oinputs.steering_adjust + 0x80 <= 0x55)
304 {
305 set_hand(HAND_LEFT, fm, dial, hand);
306 ohud.blit_text2(TEXT2_MAGICAL);
307 video.write_text32(0x1105C0, NOTE_TILES1);
308 video.write_text32(0x110640, NOTE_TILES2);
309 music_selected = 0;
310 }
311 // Centre
312 else if (oinputs.steering_adjust + 0x80 <= 0xAA)
313 {
314 set_hand(HAND_CENTRE, fm, dial, hand);
315 ohud.blit_text2(TEXT2_BREEZE);
316 video.write_text32(0x1105C6, NOTE_TILES1);
317 video.write_text32(0x110646, NOTE_TILES2);
318 music_selected = 1;
319 }
320 // Steer Right
321 else
322 {
323 set_hand(HAND_RIGHT, fm, dial, hand);
324 ohud.blit_text2(TEXT2_SPLASH);
325 video.write_text32(0x1105C8, NOTE_TILES1);
326 video.write_text32(0x110648, NOTE_TILES2);
327 music_selected = 2;
328 }
329 }
330
331 // Enhanced Version of music selection with infinite tracks.
tick_enhanced(oentry * fm,oentry * dial,oentry * hand)332 void OMusic::tick_enhanced(oentry* fm, oentry* dial, oentry* hand)
333 {
334 if (input.has_pressed(Input::LEFT) || oinputs.is_analog_l())
335 if (--cursor_pos < 0) cursor_pos = total_tracks - 1;
336 if (input.has_pressed(Input::RIGHT) || oinputs.is_analog_r())
337 if (++cursor_pos >= total_tracks) cursor_pos = 0;
338
339 if (oinputs.steering_adjust + 0x80 <= 0x70)
340 set_hand(HAND_LEFT, fm, dial, hand);
341 else if (oinputs.steering_adjust + 0x80 <= 0x90)
342 set_hand(HAND_CENTRE, fm, dial, hand);
343 else
344 set_hand(HAND_RIGHT, fm, dial, hand);
345
346 music_selected = cursor_pos;
347 ohud.blit_text_big(11, config.sound.music.at(music_selected).title.c_str(), true);
348 }
349
set_hand(short direction,oentry * fm,oentry * dial,oentry * hand)350 void OMusic::set_hand(short direction, oentry* fm, oentry* dial, oentry* hand)
351 {
352 if (direction == HAND_LEFT)
353 {
354 hand->x = 17;
355 fm->addr = outrun.adr.sprite_fm_left;
356 dial->addr = outrun.adr.sprite_dial_left;
357 hand->addr = outrun.adr.sprite_hand_left;
358 }
359 // Centre
360 else if (direction == HAND_CENTRE)
361 {
362 hand->x = 21;
363 fm->addr = outrun.adr.sprite_fm_centre;
364 dial->addr = outrun.adr.sprite_dial_centre;
365 hand->addr = outrun.adr.sprite_hand_centre;
366 }
367 // Steer Right
368 else if (direction == HAND_RIGHT)
369 {
370 hand->x = 21;
371 fm->addr = outrun.adr.sprite_fm_right;
372 dial->addr = outrun.adr.sprite_dial_right;
373 hand->addr = outrun.adr.sprite_hand_right;
374 }
375 }
376
377 // Blit Only: Used when frame skipping
blit()378 void OMusic::blit()
379 {
380 for (int i = 0; i < 5; i++)
381 osprites.do_spr_order_shadows(&osprites.jump_table[entry_start + i]);
382 }
383
384 // Blit Music Selection Tiles to text ram layer (Double Row)
385 //
386 // Source Address: 0xE0DC
387 // Input: Destination address into tile ram
388 // Output: None
389 //
390 // Tilemap data is stored in the ROM as a series of words.
391 //
392 // A basic compression format is used:
393 //
394 // 1/ If a word is not '0000', copy it directly to tileram
395 // 2/ If a word is '0000' a long follows which details the compression.
396 // The upper word of the long is the value to copy.
397 // The lower word of the long is the number of times to copy that value.
398 //
399 // Tile structure:
400 //
401 // MSB LSB
402 // ---nnnnnnnnnnnnn Tile index (0-8191)
403 // ---ccccccc------ Palette (0-127)
404 // p--------------- Priority flag
405 // -??------------- Unknown
406
blit_music_select()407 void OMusic::blit_music_select()
408 {
409 const uint32_t TILEMAP_RAM_16 = 0x10F030;
410
411 // Palette Ram: 1F Long Entries For Sky Shade On Horizon, For Colour Change Effect
412 const uint32_t PAL_RAM_SKY = 0x120F00;
413
414 uint32_t src_addr = PAL_MUSIC_SELECT;
415 uint32_t dst_addr = PAL_RAM_SKY;
416
417 // Write 32 Palette Longs to Palette RAM
418 for (int i = 0; i < 32; i++)
419 video.write_pal32(&dst_addr, roms.rom0.read32(&src_addr));
420
421 // Set Tilemap Scroll
422 otiles.set_scroll(config.s16_x_off);
423
424 // --------------------------------------------------------------------------------------------
425 // Blit to Tilemap 16: Widescreen Version. Uses Custom Tilemap.
426 // --------------------------------------------------------------------------------------------
427 if (tilemap->loaded && config.s16_x_off > 0)
428 {
429 uint32_t tilemap16 = TILEMAP_RAM_16 - 20;
430 src_addr = 0;
431
432 const uint16_t rows = tilemap->read16(&src_addr);
433 const uint16_t cols = tilemap->read16(&src_addr);
434
435 for (int y = 0; y < rows; y++)
436 {
437 dst_addr = tilemap16;
438 for (int x = 0; x < cols; x++)
439 video.write_tile16(&dst_addr, tilemap->read16(&src_addr));
440 tilemap16 += 0x80; // next line of tiles
441 }
442 }
443 // --------------------------------------------------------------------------------------------
444 // Blit to Tilemap 16: Original 4:3 Version.
445 // --------------------------------------------------------------------------------------------
446 else
447 {
448 uint32_t tilemap16 = TILEMAP_RAM_16;
449 src_addr = TILEMAP_MUSIC_SELECT;
450
451 for (int y = 0; y < 28; y++)
452 {
453 dst_addr = tilemap16;
454 for (int x = 0; x < 40;)
455 {
456 // get next tile
457 uint32_t data = roms.rom0.read16(&src_addr);
458 // No Compression: write tile directly to tile ram
459 if (data != 0)
460 {
461 video.write_tile16(&dst_addr, data);
462 x++;
463 }
464 // Compression
465 else
466 {
467 uint16_t value = roms.rom0.read16(&src_addr); // tile index to copy
468 uint16_t count = roms.rom0.read16(&src_addr); // number of times to copy value
469
470 for (uint16_t i = 0; i <= count; i++)
471 {
472 video.write_tile16(&dst_addr, value);
473 x++;
474 }
475 }
476 }
477 tilemap16 += 0x80; // next line of tiles
478 } // end for
479
480 // Fix Misplaced tile on music select screen (above steering wheel)
481 if (config.engine.fix_bugs)
482 video.write_tile16(0x10F730, 0x0C80);
483 }
484 }
485