1 /***************************************************************************
2     Course Map Logic & Rendering.
3 
4     This is the full-screen map that is displayed at the end of the game.
5 
6     The logo is built from multiple sprite components.
7 
8     The course map itself is made up of sprites and pieced together.
9     It's not a tilemap.
10 
11     Copyright Chris White.
12     See license.txt for more details.
13 ***************************************************************************/
14 
15 #include "engine/oferrari.hpp"
16 #include "engine/omap.hpp"
17 #include "engine/otiles.hpp"
18 #include "engine/otraffic.hpp"
19 #include "engine/ostats.hpp"
20 
21 OMap omap;
22 
23 // Position of Ferrari in Jump Table
24 const uint8_t SPRITE_FERRARI = 25;
25 
OMap(void)26 OMap::OMap(void)
27 {
28 }
29 
30 
~OMap(void)31 OMap::~OMap(void)
32 {
33 }
34 
init()35 void OMap::init()
36 {
37     oferrari.car_ctrl_active = false; // -1
38     video.clear_text_ram();
39     osprites.disable_sprites();
40     otraffic.disable_traffic();
41     osprites.clear_palette_data();
42     oinitengine.car_increment = 0;
43     oferrari.car_inc_old      = 0;
44     osprites.spr_cnt_main     = 0;
45     osprites.spr_cnt_shadow   = 0;
46     oroad.road_ctrl           = ORoad::ROAD_BOTH_P0;
47     oroad.horizon_base        = ORoad::HORIZON_OFF;
48     otiles.fill_tilemap_color(0xABD); //  Paint pinkish colour on tilemap 16
49     init_sprites = true;
50 }
51 
52 // Process route through levels
53 // Process end position on final level
54 // Source: 0x345E
tick()55 void OMap::tick()
56 {
57     // 60 FPS Code to simply render sprites
58     if (!outrun.tick_frame)
59     {
60         blit();
61         return;
62     }
63 
64     // Initialize Course Map Sprites if necessary
65     if (init_sprites)
66     {
67         load_sprites();
68         init_sprites = false;
69         return;
70     }
71 
72     switch (map_state)
73     {
74         // Initialise Route Info
75         case MAP_INIT:
76             video.sprite_layer->set_x_clip(false); // Don't clip the area in wide-screen mode
77             map_route  = roms.rom0.read8(MAP_ROUTE_LOOKUP + ostats.routes[1]);
78             map_pos    = 0;
79             map_stage1 = 0;
80             map_stage2 = ostats.cur_stage;
81             if (map_stage2 > 0)
82                 map_state = MAP_ROUTE;
83             else
84             {
85                 map_state = MAP_ROUTE_FINAL;
86                 do_route_final();
87                 break;
88             }
89 
90         // Do Route [Note map is displayed from this point on]
91         case MAP_ROUTE:
92             if (++map_pos > 0x1B)
93             {
94                 if (--map_stage2 <= 0)
95                 {   //map_end_route
96                     map_pos = 0;
97                     map_stage1++;
98                     uint16_t route_info = ostats.routes[1 + map_stage1];
99                     if (route_info)
100                     {
101                         map_route = roms.rom0.read8(MAP_ROUTE_LOOKUP + route_info);
102                     }
103                     else
104                     {
105                         map_route = roms.rom0.read8(MAP_ROUTE_LOOKUP + ostats.routes[0 + map_stage1] + 0x10);
106                     }
107 
108                     map_state = MAP_ROUTE_FINAL;
109                     do_route_final();
110                 }
111                 else
112                 {
113                     map_pos = 0;
114                     map_stage1++;
115                     map_route = roms.rom0.read8(MAP_ROUTE_LOOKUP + ostats.routes[1 + map_stage1]);
116                 }
117             }
118             break;
119 
120         // Do Final Segment Of Route [Car still moving]
121         case MAP_ROUTE_FINAL:
122             do_route_final();
123             break;
124 
125         // Route Concluded
126         case MAP_ROUTE_DONE:
127             end_route();
128             break;
129 
130         // Init Delay Counter For Map Display
131         case MAP_INIT_DELAY:
132             init_map_delay();
133             break;
134 
135         // Display Map
136         case MAP_DISPLAY:
137             map_display();
138             break;
139 
140         // Clear Course Map
141         case MAP_CLEAR:
142             outrun.init_best_outrunners();
143             return;
144     }
145 
146     draw_course_map();
147 }
148 
149 // Render sprites only. No Logic
blit()150 void OMap::blit()
151 {
152     for (uint8_t i = 0; i <= MAP_PIECES; i++)
153     {
154         oentry* sprite = &osprites.jump_table[i];
155         if (sprite->control & OSprites::ENABLE)
156             osprites.do_spr_order_shadows(sprite);
157     }
158 }
159 
draw_course_map()160 void OMap::draw_course_map()
161 {
162     oentry* sprite = osprites.jump_table;
163 
164     // Draw Road Components
165     draw_vert_bottom(sprite++);
166     draw_vert_top   (sprite++);
167     draw_vert_bottom(sprite++);
168     draw_vert_top   (sprite++);
169     draw_vert_bottom(sprite++);
170     draw_vert_top   (sprite++);
171     draw_vert_bottom(sprite++);
172     draw_vert_top   (sprite++);
173     draw_vert_bottom(sprite++);
174     draw_vert_top   (sprite++);
175     draw_vert_bottom(sprite++);
176     draw_vert_top   (sprite++);
177     draw_vert_bottom(sprite++);
178     draw_vert_top   (sprite++);
179     draw_vert_bottom(sprite++);
180     draw_vert_top   (sprite++);
181     draw_vert_bottom(sprite++);
182     draw_vert_top   (sprite++);
183     draw_vert_bottom(sprite++);
184     draw_vert_top   (sprite++);
185     draw_horiz_end  (sprite++);
186     draw_horiz_end  (sprite++);
187     draw_horiz_end  (sprite++);
188     draw_horiz_end  (sprite++);
189     draw_horiz_end  (sprite++);
190 
191     // Draw Mini Car
192     move_mini_car   (sprite++);
193 
194     // Draw Backdrop Map Pieces
195     for (uint8_t i = 26; i <= MAP_PIECES; i++)
196     {
197         if (sprite->control & OSprites::ENABLE)
198             osprites.do_spr_order_shadows(sprite++);
199     }
200 }
201 
202 
position_ferrari(uint8_t index)203 void OMap::position_ferrari(uint8_t index)
204 {
205     oentry* segment = &osprites.jump_table[index];
206     osprites.jump_table[SPRITE_FERRARI].x = segment->x - 8;
207     osprites.jump_table[SPRITE_FERRARI].y = segment->y;
208 }
209 
210 // Initalize Course Map Sprites
211 //
212 // Notes: Index 26 is start of water that needs to be changed for widescreen
213 //
214 // Source: 0x33F4
load_sprites()215 void OMap::load_sprites()
216 {
217     // hacks
218     /*ostats.cur_stage = 4;
219     ostats.routes[0] = 4;
220     ostats.routes[1] = 0x08;
221     ostats.routes[2] = 0x18;
222     ostats.routes[3] = 0x28;
223     ostats.routes[4] = 0x38;
224     oinitengine.rd_split_state = 0x16;
225     oroad.road_pos = 0x192 << 16;*/
226     // end hacks
227 
228     uint32_t adr = outrun.adr.sprite_coursemap;
229 
230     for (uint8_t i = 0; i <= MAP_PIECES; i++)
231     {
232         oentry* sprite     = &osprites.jump_table[i];
233         sprite->id         = i+1;
234         sprite->control    = roms.rom0p->read8(&adr);
235         sprite->draw_props = roms.rom0p->read8(&adr);
236         sprite->shadow     = roms.rom0p->read8(&adr);
237         sprite->zoom       = roms.rom0p->read8(&adr);
238         sprite->pal_src    = (uint8_t) roms.rom0p->read16(&adr);
239         sprite->priority   = sprite->road_priority = roms.rom0p->read16(&adr);
240         sprite->x          = roms.rom0p->read16(&adr);
241         sprite->y          = roms.rom0p->read16(&adr);
242         sprite->addr       = roms.rom0p->read32(&adr);
243         sprite->counter    = 0;
244 
245         adr += 4; // throw this address away
246 
247         osprites.map_palette(sprite);
248     }
249 
250     // Wide-screen hack to extend sea to edge of screen.
251     if (config.s16_x_off != 0 || config.engine.fix_bugs)
252     {
253         for (uint8_t i = 26; i <= 30; i++)
254         {
255             oentry* sprite = &osprites.jump_table[i];
256             sprite->addr   = osprites.jump_table[31].addr;
257             sprite->x      -= 64;
258             sprite->zoom   = 0x7F;
259         }
260     }
261 
262     // Minicar initalization moved here
263     minicar_enable = 0;
264     osprites.jump_table[SPRITE_FERRARI].x = -0x80;
265     osprites.jump_table[SPRITE_FERRARI].y = 0x78;
266     map_state = MAP_INIT;
267 }
268 
269 // Source: 0x355A
do_route_final()270 void OMap::do_route_final()
271 {
272     int16_t pos = oroad.road_pos >> 16;
273     if (oinitengine.rd_split_state)
274         pos += 0x79C;
275 
276     pos = (pos * 0x1B) / 0x94D;
277     map_pos_final = pos;
278 
279     map_state = MAP_ROUTE_DONE;
280     end_route();
281 }
282 
283 // Source: 0x3584
end_route()284 void OMap::end_route()
285 {
286     map_pos++;
287 
288     if (map_pos_final < map_pos)
289     {
290         // 359C
291         map_pos = map_pos_final;
292         minicar_enable = 1;
293         map_state = MAP_INIT_DELAY;
294         init_map_delay();
295     }
296 }
297 
298 // Source: 0x35B6
init_map_delay()299 void OMap::init_map_delay()
300 {
301     map_route = 0;
302     map_delay = 0x80;
303     map_state = MAP_DISPLAY;
304     map_display();
305 }
306 
307 // Source: 0x35CC
map_display()308 void OMap::map_display()
309 {
310     // Init Best OutRunners
311     if (--map_delay <= 0)
312     {
313         map_state = MAP_CLEAR;
314         outrun.init_best_outrunners();
315     }
316 }
317 
318 // ------------------------------------------------------------------------------------------------
319 // Colour sprite based road as car moves over it on mini-map
320 // ------------------------------------------------------------------------------------------------
321 
322 // Source: 0x3740
draw_vert_top(oentry * sprite)323 void OMap::draw_vert_top(oentry* sprite)
324 {
325     if (sprite->control & OSprites::ENABLE)
326         draw_piece(sprite, outrun.adr.sprite_coursemap_top);
327 }
328 
329 // Source: 0x3736
draw_vert_bottom(oentry * sprite)330 void OMap::draw_vert_bottom(oentry* sprite)
331 {
332     if (sprite->control & OSprites::ENABLE)
333         draw_piece(sprite, outrun.adr.sprite_coursemap_bot);
334 }
335 
336 // Source: 0x372C
draw_horiz_end(oentry * sprite)337 void OMap::draw_horiz_end(oentry* sprite)
338 {
339     if (sprite->control & OSprites::ENABLE)
340         draw_piece(sprite, outrun.adr.sprite_coursemap_end);
341 }
342 
343 // Source: 0x3746
draw_piece(oentry * sprite,uint32_t adr)344 void OMap::draw_piece(oentry* sprite, uint32_t adr)
345 {
346     // Update palette of background piece, to highlight route as minicar passes over it
347     if (map_route == sprite->id)
348     {
349         sprite->priority = 0x102;
350         sprite->road_priority = 0x102;
351 
352         adr += (map_pos << 3);
353 
354         sprite->addr    = roms.rom0p->read32(adr);
355         sprite->pal_src = roms.rom0p->read8(4 + adr);
356         osprites.map_palette(sprite);
357     }
358 
359     osprites.do_spr_order_shadows(sprite);
360 }
361 
362 // Move mini car sprite on Course Map Screen
363 // Source: 0x3696
move_mini_car(oentry * sprite)364 void OMap::move_mini_car(oentry* sprite)
365 {
366     // Move Mini Car
367     if (!minicar_enable)
368     {
369         // Remember that the minimap is angled, so we still need to adjust both the x and y positions
370         uint32_t movement_table = (map_route & 1) ? MAP_MOVEMENT_RIGHT : MAP_MOVEMENT_LEFT;
371 
372         int16_t pos = (map_stage1 < 4) ? map_pos : map_pos >> 1;
373         pos <<= 1; // do not try to merge with previous line
374 
375         sprite->x += roms.rom0.read16(movement_table + pos);
376         int16_t y_change = roms.rom0.read16(movement_table + pos + 0x40);
377         sprite->y -= y_change;
378 
379         if (y_change == 0)
380             sprite->addr = outrun.adr.sprite_minicar_right;
381         else if (y_change < 0)
382             sprite->addr = outrun.adr.sprite_minicar_down;
383         else
384             sprite->addr = outrun.adr.sprite_minicar_up;
385     }
386 
387     osprites.do_spr_order_shadows(sprite);
388 }