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