// Space Taxi -- the title screen intro and the level-name intro // screen, run on the same simulation state as the game so everything // that carries over (flame parity, wave index, walk parity, the // sprite shadows) behaves as on the C64. // // Title ($477A + $47B0..$4A6C): the logo flip-book and colour cycle, // a passenger who beams in, walks to the parked cab and boards, then // the cab lifts off under thrust. When it has left, the attract demo // starts. // // Level intro ($4523..$4727): a black screen with the level name, the // cab rising from the bottom and seven star sprites streaming out of // the centre, until the cab reaches mid-screen. #include #include "stSim.h" // Title sprite init tables ($4994 / $499C / $49A4 / $49AC / $49B4). static const uint8_t kTitleCol[8] = { 0x28, 0x32, 0x00, 0x86, 0x9C, 0xA2, 0xBA, 0xD4 }; static const uint8_t kTitleMsb[8] = { 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static const uint8_t kTitleColor[8] = { 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07 }; static const uint8_t kTitleRow[8] = { 0x84, 0x84, 0x00, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2 }; static const uint8_t kTitlePtr[8] = { 0xC1, 0xC7, 0x00, 0xDB, 0xDE, 0xDF, 0xE0, 0xE1 }; // Logo flip-book frame order ($48A7) and colour cycle ($489C). static const uint8_t kLogoFlip[6] = { 1u, 2u, 3u, 4u, 3u, 2u }; static const uint8_t kLogoColor[8] = { 0x02, 0x08, 0x07, 0x05, 0x06, 0x0E, 0x03, 0x04 }; // Intro cab drift per loop count ($470D). static const int8_t kIntroCabDy[4] = { -2, 1, -1, 1 }; // Intro texts ($4624 / $463F). static const uint8_t kTextDemoExit[] = { 0x44, 0x45, 0x4D, 0x4F, 0x2C, 0x20, 0x55, 0x53, 0x45, 0x20, 0x4A, 0x4F, 0x59, 0x53, 0x54, 0x49, 0x43, 0x4B, 0x20, 0x54, 0x4F, 0x20, 0x45, 0x58, 0x49, 0x54, 0 }; static const uint8_t kTextScreens[] = { 0x54, 0x48, 0x49, 0x53, 0x20, 0x49, 0x53, 0x20, 0x31, 0x20, 0x4F, 0x46, 0x20, 0x32, 0x35, 0x20, 0x44, 0x49, 0x46, 0x46, 0x45, 0x52, 0x45, 0x4E, 0x54, 0x20, 0x53, 0x43, 0x52, 0x45, 0x45, 0x4E, 0x53, 0x21, 0 }; #define ST_LOGO_CHAR 0x84u #define ST_TITLE_CAB_LIFT_ROW 0x14u #define ST_TITLE_SPARKLE_TICKS 0x5Au #define ST_TITLE_HOVER_COL 0x28u #define ST_INTRO_STAR_PTR 0xDAu #define ST_INTRO_STAR_RELOAD 0x20u #define ST_INTRO_CAB_END_ROW 0x94u #define ST_INTRO_CAB_DX_RELOAD 0x28u static void addToRow(StSimT *sim, uint8_t idx, uint8_t delta); static void addToSpriteX(StSimT *sim, uint8_t idx, uint8_t delta); static void logoColorCycle(StSimT *sim); static void logoFlip(StSimT *sim); static void marshalFrame(StSimT *sim); static void starRespawn(StSimT *sim, uint8_t idx); // $4113 static void addToRow(StSimT *sim, uint8_t idx, uint8_t delta) { sim->spr[idx].row = (uint8_t)(sim->spr[idx].row + delta); } // $411B static void addToSpriteX(StSimT *sim, uint8_t idx, uint8_t delta) { uint16_t x = (uint16_t)(((uint16_t)sim->spr[idx].msb << 8) | sim->spr[idx].col); x = (uint16_t)(x + (uint16_t)(int16_t)(int8_t)delta); sim->spr[idx].col = (uint8_t)x; sim->spr[idx].msb = (uint8_t)(x >> 8); } // $4861 -- next logo colour over rows 1..11, columns 1..37, and onto // sprites 3..7. static void logoColorCycle(StSimT *sim) { uint8_t color; uint8_t row; uint8_t col; uint8_t k; sim->logoColorIdx = (uint8_t)((sim->logoColorIdx + 1u) & 7u); color = kLogoColor[sim->logoColorIdx]; for (row = 1u; row <= 11u; row++) { for (col = 1u; col <= 37u; col++) { stSimPutColor(sim, col, row, color); } } for (k = 3u; k < ST_HW_SPRITES; k++) { sim->spr[k].color = color; } } // $4827 -- every fourth tick copy the next flip frame ($85..$88) over // the logo glyph; the colour advances when the ping-pong hits 4. static void logoFlip(StSimT *sim) { sim->logoDiv = (uint8_t)((sim->logoDiv + 1u) & 3u); if (sim->logoDiv != 0u) { return; } memcpy(sim->charset[ST_LOGO_CHAR], sim->charset[ST_LOGO_CHAR + kLogoFlip[sim->logoCycleAux]], 8u); sim->charDirty[ST_LOGO_CHAR] = 1u; sim->logoCycleAux++; if (sim->logoCycleAux == 6u) { sim->logoCycleAux = 0u; return; } if (sim->logoCycleAux == 4u) { logoColorCycle(sim); } } // $4253 + $4293 as the title and intro loops call them. static void marshalFrame(StSimT *sim) { uint8_t i; uint8_t enable = 0u; for (i = 0u; i < ST_HW_SPRITES; i++) { sim->frame.x[i] = (uint16_t)(((uint16_t)(sim->spr[i].msb != 0u ? 1u : 0u) << 8) | sim->spr[i].col); sim->frame.y[i] = sim->spr[i].row; sim->frame.ptr[i] = sim->spr[i].ptr; sim->frame.color[i] = sim->spr[i].color; if (sim->spr[i].enable != 0u) { enable |= (uint8_t)(1u << i); } } sim->frame.enableMask = enable; sim->frame.multiMask = sim->multiColorMask; } // $4675 -- a star reappears near the centre, seeded by star 0's row. static void starRespawn(StSimT *sim, uint8_t idx) { sim->spr[idx].enable = 1u; sim->spr[idx].msb = 0u; sim->spr[idx].col = (uint8_t)((sim->spr[0].row & 7u) + 0xA6u); sim->spr[idx].row = (uint8_t)(((sim->spr[0].row & 0x30u) >> 4) + 0x89u); sim->starReload[idx] = ST_INTRO_STAR_RELOAD; } // --------------------------------------------------------------------------- // Public // --------------------------------------------------------------------------- // $4523..$4665 -- set up the level-name screen. void stIntroEnter(StSimT *sim, const StLevelT *level) { uint8_t k; sim->level = level; sim->spr[7].ptr = 0xC0u; sim->spriteMc1 = 0x07u; sim->spriteMc0 = 0x02u; sim->spr[7].enable = 1u; sim->spr[7].col = 0xAAu; sim->spr[7].msb = 0u; sim->spr[7].color = 0x06u; sim->spr[7].row = 0xE4u; sim->introCabDxTimer = 0x14u; sim->introCabDx = 0x01u; for (k = 0u; k < 7u; k++) { uint8_t idx = (uint8_t)(6u - k); sim->spr[idx].row = 0x8Cu; sim->spr[idx].msb = 0u; sim->spr[idx].col = 0xAAu; sim->spr[idx].color = 0x07u; sim->spr[idx].ptr = ST_INTRO_STAR_PTR; sim->spr[idx].enable = 0u; sim->starTimer[idx] = stSimRng(sim, 0x20u); sim->starReload[idx] = 0u; } sim->borderColor = 0u; sim->bgColor = 0u; sim->multiColorMask = 0x80u; marshalFrame(sim); // $40CA: blank screen. memset(sim->screen, ST_CHAR_SPACE, ST_SCREEN_CELLS); memset(sim->color, 0, ST_SCREEN_CELLS); stSimDirtyAll(sim); memcpy(sim->charset, stC64Charset(), sizeof(sim->charset)); memset(sim->charDirty, 1, ST_CHARSET_CHARS); stSimDrawText(sim, 10u, 12u, level->name, 3u); if (sim->demoMode != 0u && sim->postMortem == 0u) { stSimDrawText(sim, 7u, 3u, kTextDemoExit, 5u); stSimDrawText(sim, 3u, 24u, kTextScreens, 4u); } sim->introLoopCount = 0u; } // $4666 -- one frame of the star field; true when the cab has risen // to mid-screen and everything is hidden again. bool stIntroStep(StSimT *sim) { uint8_t k; for (k = 0u; k < 7u; k++) { uint8_t idx = (uint8_t)(6u - k); if (sim->starTimer[idx] == 0u) { addToSpriteX(sim, idx, (uint8_t)stC64IntroStarDx(idx)); addToRow(sim, idx, (uint8_t)stC64IntroStarDy(idx)); sim->starReload[idx]--; if (sim->starReload[idx] == 0u) { starRespawn(sim, idx); } continue; } sim->starTimer[idx]--; if (sim->starTimer[idx] == 0u) { starRespawn(sim, idx); } } marshalFrame(sim); sim->introLoopCount = (uint8_t)((sim->introLoopCount + 1u) & 3u); addToRow(sim, 7u, (uint8_t)kIntroCabDy[sim->introLoopCount]); if ((sim->introLoopCount & 1u) == 0u) { addToSpriteX(sim, 7u, sim->introCabDx); sim->introCabDxTimer--; if (sim->introCabDxTimer == 0u) { sim->introCabDxTimer = ST_INTRO_CAB_DX_RELOAD; sim->spr[7].ptr ^= 0x1Cu; sim->introCabDx ^= 0xFEu; } } if (sim->spr[7].row != ST_INTRO_CAB_END_ROW) { return false; } // $4728: everything off; $44E1 then resets the SID and the RNG. for (k = 0u; k < ST_HW_SPRITES; k++) { sim->spr[k].enable = 0u; } marshalFrame(sim); sim->rngT1 = 0x06u; sim->rngT2 = 0x17u; return true; } // $477A + $47B0..$47E1 -- show the title and start the intro. void stTitleEnter(StSimT *sim, const StLevelT *title) { uint8_t k; sim->level = title; memcpy(sim->screen, title->screen, ST_SCREEN_CELLS); memcpy(sim->color, title->color, ST_SCREEN_CELLS); stSimDirtyAll(sim); memcpy(sim->charset, stC64Charset(), sizeof(sim->charset)); memset(sim->charDirty, 1, ST_CHARSET_CHARS); logoColorCycle(sim); for (k = 0u; k < ST_HW_SPRITES; k++) { sim->spr[k].enable = 0u; } sim->logoCycleAux = 0u; sim->borderColor = 0u; sim->bgColor = 0u; // $49BC: the cab parked on the pad, the passenger far right, the // decoration sprites along the bottom. for (k = 0u; k < ST_HW_SPRITES; k++) { sim->spr[k].enable = 1u; sim->spr[k].col = kTitleCol[k]; sim->spr[k].msb = kTitleMsb[k]; sim->spr[k].row = kTitleRow[k]; sim->spr[k].ptr = kTitlePtr[k]; sim->spr[k].color = kTitleColor[k]; } sim->multiColorMask = 0x07u; sim->spriteMc1 = 0x07u; sim->spriteMc0 = 0x02u; marshalFrame(sim); sim->stage = ST_STAGE_WAIT; sim->introFrameCount = ST_TITLE_SPARKLE_TICKS; sim->hoverXFrac = 0u; sim->hoverXCol = ST_TITLE_HOVER_COL; sim->decayTimer = sim->decayReload; sim->collisionPhase = 0u; sim->activePad = 0u; sim->dirMask = 0u; } // $47E4 loop body: the intro stage machine ($4A03), the sprite flush // and the logo animation. True once the cab has lifted off. bool stTitleTick(StSimT *sim) { switch (sim->stage) { case ST_STAGE_WAIT: // $4A17: sparkle while the countdown runs. stFareWaveStep(sim); sim->introFrameCount--; if (sim->introFrameCount == 0u) { sim->stage++; } break; case ST_STAGE_WALK_TO_CAB: // $4A24: walk in until parked at column $28. sim->decayTimer--; if (sim->decayTimer == 0u) { sim->decayTimer = sim->decayReload; stFareWalkStep(sim); if (sim->spr[1].col == ST_TITLE_HOVER_COL && sim->spr[1].msb == 0u) { sim->stage++; } } break; case ST_STAGE_BOARD: // $4A45 -> $67A6: shrink into the cab. stFareLeaveStep(sim); break; case ST_STAGE_RIDING: // $4A48: lift off under UP thrust until above row $14. addToRow(sim, 0u, 0xFEu); sim->dirMask = 0x01u; sim->spr[0].ptr = 0xC0u; stSimFlameUpdate(sim); if (sim->spr[0].row < ST_TITLE_CAB_LIFT_ROW) { sim->stage++; stAudioNoise(false); } break; default: break; } marshalFrame(sim); logoFlip(sim); return sim->stage == ST_STAGE_DROP_IN; }