318 lines
12 KiB
C
318 lines
12 KiB
C
// 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.
|
|
//
|
|
// The seven star sprites are replaced by a static starfield whose
|
|
// colours are cycled -- see ST_STAR_RING_FIRST in stSim.h for why and
|
|
// how. The cab still flies, and it is still what ends the intro, so the
|
|
// sequence keeps its length and its cue; only the warp changes.
|
|
|
|
#include <string.h>
|
|
|
|
#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[] = "DEMO, USE JOYSTICK TO EXIT";
|
|
static const uint8_t kTextScreens[] = "THIS IS 1 OF 25 DIFFERENT SCREENS!";
|
|
|
|
#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_CAB_END_ROW 0x94u
|
|
#define ST_INTRO_CAB_DX_RELOAD 0x28u
|
|
|
|
// The palette-cycled starfield. Seven stars per ring so the bright band
|
|
// stays the same weight all the way out, placed at fixed pseudo-random
|
|
// angles and clear of the three lines the intro screen writes (the level
|
|
// name on row 12, and the demo hints on rows 3 and 24).
|
|
typedef struct {
|
|
uint8_t col;
|
|
uint8_t row;
|
|
uint8_t ring;
|
|
} StStarT;
|
|
|
|
static const StStarT kIntroStar[] = {
|
|
{ 18u, 10u, 0u }, { 19u, 10u, 0u }, { 17u, 11u, 0u }, { 18u, 13u, 0u },
|
|
{ 21u, 13u, 0u }, { 22u, 14u, 0u }, { 18u, 15u, 0u }, { 18u, 8u, 1u },
|
|
{ 19u, 8u, 1u }, { 14u, 10u, 1u }, { 25u, 14u, 1u }, { 21u, 15u, 1u },
|
|
{ 25u, 15u, 1u }, { 20u, 17u, 1u }, { 26u, 7u, 2u }, { 12u, 10u, 2u },
|
|
{ 29u, 11u, 2u }, { 11u, 13u, 2u }, { 29u, 14u, 2u }, { 21u, 17u, 2u },
|
|
{ 23u, 18u, 2u }, { 21u, 4u, 3u }, { 24u, 4u, 3u }, { 11u, 8u, 3u },
|
|
{ 12u, 8u, 3u }, { 10u, 10u, 3u }, { 30u, 16u, 3u }, { 14u, 17u, 3u },
|
|
{ 15u, 2u, 4u }, { 8u, 8u, 4u }, { 32u, 9u, 4u }, { 9u, 17u, 4u },
|
|
{ 32u, 17u, 4u }, { 32u, 18u, 4u }, { 24u, 22u, 4u }, { 16u, 1u, 5u },
|
|
{ 28u, 1u, 5u }, { 31u, 2u, 5u }, { 36u, 13u, 5u }, { 6u, 15u, 5u },
|
|
{ 4u, 16u, 5u }, { 5u, 19u, 5u }, { 8u, 0u, 6u }, { 30u, 0u, 6u },
|
|
{ 1u, 8u, 6u }, { 2u, 12u, 6u }, { 37u, 16u, 6u }, { 7u, 21u, 6u },
|
|
{ 32u, 22u, 6u }, { 0u, 4u, 7u }, { 39u, 7u, 7u }, { 0u, 10u, 7u },
|
|
{ 38u, 20u, 7u }, { 38u, 21u, 7u }, { 4u, 22u, 7u }, { 35u, 22u, 7u },
|
|
};
|
|
#define ST_INTRO_STAR_COUNT (sizeof(kIntroStar) / sizeof(kIntroStar[0]))
|
|
|
|
|
|
static void addToRow(StSimT *sim, uint8_t idx, uint8_t delta);
|
|
static void logoColorCycle(StSimT *sim);
|
|
static void logoFlip(StSimT *sim);
|
|
static void paintStarfield(StSimT *sim);
|
|
|
|
|
|
// $4113
|
|
static void addToRow(StSimT *sim, uint8_t idx, uint8_t delta) {
|
|
sim->spr[idx].row = (uint8_t)(sim->spr[idx].row + delta);
|
|
}
|
|
|
|
|
|
// $4861 -- the next logo colour, onto sprites 3..7 and onto the logo
|
|
// itself. The C64 writes the colour into all 407 cells of rows 1..11,
|
|
// columns 1..37; only the cells showing the logo character take any
|
|
// colour from it (the rest are blank), so the colour is published in
|
|
// logoColor instead and the renderer applies it to the logo's palette
|
|
// slot. Nothing reads the band's colour RAM.
|
|
static void logoColorCycle(StSimT *sim) {
|
|
uint8_t color;
|
|
uint8_t k;
|
|
|
|
sim->logoColorIdx = (uint8_t)((sim->logoColorIdx + 1u) & 7u);
|
|
color = kLogoColor[sim->logoColorIdx];
|
|
sim->logoColor = 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(stSimGlyphMut(sim, ST_LOGO_CHAR),
|
|
stSimGlyph(sim, (uint8_t)(ST_LOGO_CHAR + kLogoFlip[sim->logoCycleAux])), 8u);
|
|
stSimMarkChar(sim, ST_LOGO_CHAR);
|
|
sim->logoCycleAux++;
|
|
if (sim->logoCycleAux == 6u) {
|
|
sim->logoCycleAux = 0u;
|
|
return;
|
|
}
|
|
if (sim->logoCycleAux == 4u) {
|
|
logoColorCycle(sim);
|
|
}
|
|
}
|
|
|
|
|
|
// Paint the level-intro starfield into screen RAM, once. A star's cell
|
|
// is coloured ST_STAR_RING_FIRST + its ring and never touched again --
|
|
// the renderer animates the warp by cycling those colours, which costs
|
|
// no dirty rows at all. See ST_STAR_RING_FIRST in stSim.h.
|
|
static void paintStarfield(StSimT *sim) {
|
|
const StStarT *star;
|
|
uint8_t one[2];
|
|
uint8_t k;
|
|
|
|
one[1] = 0u; // stSimDrawText stops below screen code 6
|
|
for (k = 0u; k < (uint8_t)ST_INTRO_STAR_COUNT; k++) {
|
|
star = &kIntroStar[k];
|
|
one[0] = (star->ring < ST_STAR_FAR_RINGS) ? ST_STAR_GLYPH_FAR : ST_STAR_GLYPH_NEAR;
|
|
stSimDrawText(sim, star->col, star->row, one, (uint8_t)(ST_STAR_RING_FIRST + star->ring));
|
|
}
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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;
|
|
// $44F8/$4500 held each star's spawn timer and reload. Nothing
|
|
// reads them now that the warp is painted rather than flown, but
|
|
// the seven RNG draws MUST stay: they advance the shared RNG and
|
|
// the level that follows draws from it, so dropping them would
|
|
// change the game that comes after the intro.
|
|
(void)stSimRng(sim, 0x20u);
|
|
}
|
|
sim->borderColor = 0u;
|
|
sim->bgColor = 0u;
|
|
sim->multiColorMask = 0x80u;
|
|
stSimMarshal(sim);
|
|
// $40CA: blank screen.
|
|
memset(sim->screen, ST_CHAR_SPACE, ST_SCREEN_CELLS);
|
|
memset(sim->color, 0, ST_SCREEN_CELLS);
|
|
stSimDirtyAll(sim);
|
|
stSimGlyphReset(sim);
|
|
sim->charDirtyAll = true;
|
|
sim->charDirtyCount = 0u;
|
|
stSimDrawText(sim, 10u, 12u, level->name, 3u);
|
|
paintStarfield(sim);
|
|
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;
|
|
|
|
stSimMarshal(sim);
|
|
sim->introLoopCount = (uint8_t)((sim->introLoopCount + 1u) & 3u);
|
|
addToRow(sim, 7u, (uint8_t)kIntroCabDy[sim->introLoopCount]);
|
|
if ((sim->introLoopCount & 1u) == 0u) {
|
|
stSimSpriteAddX(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;
|
|
}
|
|
stSimMarshal(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);
|
|
stSimGlyphReset(sim);
|
|
sim->charDirtyAll = true;
|
|
sim->charDirtyCount = 0u;
|
|
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;
|
|
stSimMarshal(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;
|
|
}
|
|
stSimMarshal(sim);
|
|
logoFlip(sim);
|
|
return sim->stage == ST_STAGE_DROP_IN;
|
|
}
|