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)26OMap::OMap(void) 27 { 28 } 29 30 ~OMap(void)31OMap::~OMap(void) 32 { 33 } 34 init()35void 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()55void 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()150void 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()160void 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)203void 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()215void 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()270void 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()284void 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()299void 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()308void 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)323void 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)330void 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)337void 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)344void 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)364void 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 }