joeylib2/examples/spacetaxi/stFare.c

424 lines
12 KiB
C

// Space Taxi -- the passenger / fare state machine ($65C1 dispatch on
// $7163, the $67E3 post-tick gate and the pad reservation table).
//
// One passenger at a time: they beam in on a free pad, wave, walk to a
// cab parked on their pad, shrink into it, ride, and reverse the whole
// sequence at the destination. Every step runs on the shared 3-tick
// decay timer. Pads get reserved as fares use them; once every fare
// pad has been used the destination becomes "UP PLEASE" and the top
// hatch opens.
#include "stSim.h"
// Screen-code strings ($6C1E..$6C7D). All ASCII-compatible glyphs.
static const uint8_t kTextPadPlease[] = "PAD PLEASE"; // $6C1E
static const uint8_t kTextUpPlease[] = " UP PLEASE! "; // $6C2B
static const uint8_t kTextHeyTaxi[] = " HEY, TAXI! "; // $6C45
static const uint8_t kTextThanks[] = " THANKS "; // $6C5F
// Speech cue strings fed to the sample player one character at a time.
static const uint8_t kSpeechHeyTaxi[] = "HT"; // $6C6C
static const uint8_t kSpeechUp[] = "U?"; // $6C6F
static const uint8_t kSpeechPad[] = "PX?"; // $6C72 (X = digit)
static const uint8_t kSpeechThanks[] = "!"; // $6C76
static const uint8_t kSpeechHey[] = "H"; // $6C78
// Waiting wave cycle ($66D6) and the base-fare blob ($43B9 = +5.00).
static const uint8_t kWaveCels[4] = { 0xC6, 0xC7, 0xD9, 0xC7 };
static const uint8_t kFareBlob[7] = { 0x66, 0x66, 0x66, 0x6F, 0x77, 0x74, 0x74 };
static void beamIn(StSimT *sim);
static bool decayStep(StSimT *sim);
static void idleTicker(StSimT *sim);
static void lightOnAndLatch(StSimT *sim);
static void newFare(StSimT *sim);
static void stageBoard(StSimT *sim);
static void stageCleanup(StSimT *sim);
static void stageDropIn(StSimT *sim);
static void walkToTarget(StSimT *sim);
// $665F -- the passenger grows in ($CB down to $C7); a crash during
// it cancels the fare.
static void beamIn(StSimT *sim) {
if (!decayStep(sim)) {
return;
}
sim->spr[1].ptr--;
if (sim->spr[1].ptr != ST_SPRITE_PASS_STAND) {
return;
}
if (sim->collisionPhase != 0u) {
sim->stage = ST_STAGE_LEAVE;
sim->spr[1].ptr = ST_SPRITE_PASS_STAND;
sim->spriteSlots[sim->activeSpriteIdx] = 0u;
sim->activeSpriteIdx = 0u;
sim->fareSlotCount--;
return;
}
sim->stage++;
stSimDrawText(sim, 14u, 24u, kTextHeyTaxi, 1u);
stFareBoardingSound(sim, kSpeechHeyTaxi);
// $43D1: the meter starts at $10.00..$19.00.
stSimHudInit(sim);
stSimPutChar(sim, 35u, 24u, (uint8_t)(ST_CHAR_DIGIT_BASE + stSimRng(sim, 10u)));
stSimPutChar(sim, 34u, 24u, (uint8_t)(ST_CHAR_DIGIT_BASE + 1u));
}
// The shared 3-tick cadence: true on the tick a step is due.
static bool decayStep(StSimT *sim) {
sim->decayTimer--;
if (sim->decayTimer != 0u) {
return false;
}
sim->decayTimer = sim->decayReload;
return true;
}
// $660B -- a new fare turns up on a 3% roll per tick or when the
// 100-tick timer runs out, never mid-crash.
static void idleTicker(StSimT *sim) {
if (stSimRng(sim, 100u) >= 3u) {
sim->stage0Rng--;
if (sim->stage0Rng != 0u) {
return;
}
}
if (sim->collisionPhase != 0u) {
return;
}
newFare(sim);
}
// $6808 -- the pad lights up and stays "busy".
static void lightOnAndLatch(StSimT *sim) {
sim->deathInProgress = 1u;
stSimPadLight(sim, true);
}
// $6620 -- pick a free pad, stand the passenger there, start beaming.
static void newFare(StSimT *sim) {
uint8_t x;
const StPadT *pad;
sim->stage0Rng = 0x64u;
x = stFareChooseDestination(sim);
// $6628: X-1 selects the slot; a garbage X (all pads used) reads
// past the table on the C64 -- clamp to the last pad here.
if (x == 0u || x > ST_MAX_PADS) {
x = ST_MAX_PADS;
}
pad = &sim->pads[x - 1u];
sim->spr[1].msb = pad->passMsb;
sim->spr[1].col = pad->passCol;
sim->spr[1].row = pad->row;
sim->spr[1].enable = 1u;
sim->stage = ST_STAGE_BEAM_IN;
sim->decayTimer = sim->decayReload;
sim->spr[1].ptr = ST_SPRITE_PASS_BEAM;
stAudioSpeech((uint8_t)(stSimRng(sim, 3u) + 2u));
}
// $66DD -- the passenger shrinks into the cab, then picks where to go.
static void stageBoard(StSimT *sim) {
if (!decayStep(sim)) {
return;
}
sim->spr[1].ptr++;
if (sim->spr[1].ptr != ST_SPRITE_PASS_GONE) {
return;
}
sim->spr[1].enable = 0u;
sim->stage++;
sim->activeDyingSlot = sim->activePad;
(void)stFareChooseDestination(sim);
if (sim->activeSpriteIdx == ST_FARE_DEST_UP) {
return;
}
stSimDrawText(sim, 14u, 24u, kTextPadPlease, 1u);
stSimPutChar(sim, 18u, 24u, (uint8_t)(sim->activeSpriteIdx + 0x30u));
{
uint8_t speech[4];
speech[0] = kSpeechPad[0];
speech[1] = (uint8_t)(sim->activeSpriteIdx + 0x30u);
speech[2] = kSpeechPad[2];
speech[3] = 0u;
stFareBoardingSound(sim, speech);
}
}
// $67C6 -- back to idle.
static void stageCleanup(StSimT *sim) {
sim->deathInProgress = 0u;
sim->stage = ST_STAGE_IDLE;
stSimPadLight(sim, false);
stSimClearMessage(sim);
}
// $6742 -- the passenger materialises beside the cab; the fare pays
// out: $5.00 plus whatever is left on the meter (twice the base when
// no pads are reserved).
static void stageDropIn(StSimT *sim) {
if (!decayStep(sim)) {
return;
}
sim->spr[1].ptr--;
if (sim->spr[1].ptr != ST_SPRITE_PASS_STAND) {
return;
}
sim->stage++;
stSimDrawText(sim, 14u, 24u, kTextThanks, 1u);
stFareBoardingSound(sim, kSpeechThanks);
stSimBcdAdd(sim, ST_CELL_SCORE, kFareBlob);
stSimBcdAdd(sim, ST_CELL_SCORE, &sim->screen[ST_CELL_FARE]);
stSimHudInit(sim);
if (sim->fareSlotCount != 0u) {
return;
}
stSimBcdAdd(sim, ST_CELL_SCORE, kFareBlob);
}
// $67A6 -- the passenger shrinks away at the stand (also intro stage 4).
void stFareLeaveStep(StSimT *sim) {
if (!decayStep(sim)) {
return;
}
sim->spr[1].ptr++;
if (sim->spr[1].ptr != ST_SPRITE_PASS_GONE) {
return;
}
sim->spr[1].enable = 0u;
sim->stage++;
}
// $66B7 -- waving on the pad (also the title's sparkle cycle).
void stFareWaveStep(StSimT *sim) {
if (!decayStep(sim)) {
return;
}
sim->waveIdx = (uint8_t)((sim->waveIdx + 1u) & 3u);
sim->spr[1].ptr = kWaveCels[sim->waveIdx];
}
// $6D0D -- one two-pixel stride toward the hover target, cel
// alternating each step (also the title's walk-in).
void stFareWalkStep(StSimT *sim) {
int16_t passX;
int16_t targetX;
sim->walkParity ^= 1u;
passX = (int16_t)stSimSpriteX(sim, 1u);
targetX = (int16_t)(((int16_t)sim->hoverXFrac << 8) | sim->hoverXCol);
if ((int16_t)(passX - targetX) < 0) {
// $6D30: walk right, cels $C2/$C3.
sim->spr[1].ptr = (uint8_t)(ST_SPRITE_PASS_WALK_R + sim->walkParity);
stSimSpriteAddX(sim, 1u, 2u);
return;
}
// $6D4B: walk left, cels $C4/$C5.
sim->spr[1].ptr = (uint8_t)(ST_SPRITE_PASS_WALK_L + sim->walkParity);
stSimSpriteAddX(sim, 1u, 0xFEu);
}
// $6CE8 -- stages 3 and 7: walk until the column matches (even
// pixels), then stand and advance.
static void walkToTarget(StSimT *sim) {
if (!decayStep(sim)) {
return;
}
if ((sim->spr[1].col & 0xFEu) == (sim->hoverXCol & 0xFEu)) {
sim->spr[1].ptr = ST_SPRITE_PASS_STAND;
sim->stage++;
return;
}
stFareWalkStep(sim);
}
// ---------------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------------
// $6C7F -- the passenger speaks: one sample per character.
void stFareBoardingSound(StSimT *sim, const uint8_t *speech) {
(void)sim;
stAudioSilence();
stAudioNoise(false);
stAudioSpeechPhrase(speech);
}
// $6537 -- reserve a free fare pad by RNG (linear probe on collision),
// or "UP PLEASE" once every fare pad is taken. Returns the pad number.
uint8_t stFareChooseDestination(StSimT *sim) {
uint8_t special = sim->specialPad;
uint8_t x;
if (sim->fareSlotCount == special) {
stFareUpPlease(sim);
return special;
}
x = stSimRng(sim, special);
while (sim->spriteSlots[x] != 0u) {
if (x == special) {
x = 1u;
} else {
x++;
}
}
sim->fareSlotCount++;
sim->activeSpriteIdx = x;
sim->spriteSlots[x] = 1u;
return x;
}
// $6EF0 -- while the passenger walks (to or from the cab) the pad stays
// lit only while the two sprites touch.
void stFarePadLightingGate(StSimT *sim) {
if (sim->stage != ST_STAGE_WALK_TO_CAB && sim->stage != ST_STAGE_WALK_TO_PAD && sim->stage != ST_STAGE_LEAVE) {
return;
}
if (sim->activePad == 0u) {
return;
}
sim->deathInProgress = (uint8_t)(sim->spriteSpriteColl & 2u);
stSimPadLight(sim, sim->deathInProgress != 0u);
}
// $67E3 -- landed on the fare's pad: hand the passenger over.
void stFarePostTickGate(StSimT *sim) {
if (sim->activePad == 0u) {
return;
}
if (sim->activePad != sim->activeSpriteIdx) {
return;
}
switch (sim->stage) {
case ST_STAGE_IDLE:
case ST_STAGE_BEAM_IN:
case ST_STAGE_WALK_TO_CAB:
case ST_STAGE_WALK_TO_PAD:
return;
case ST_STAGE_WAIT:
// $6811: remember where the cab parked.
sim->stage++;
sim->hoverXFrac = sim->spr[0].msb;
sim->hoverXCol = sim->spr[0].col;
lightOnAndLatch(sim);
return;
case ST_STAGE_RIDING: {
const StPadT *pad;
if (sim->bobTimer != 0u) {
return;
}
sim->stage++;
sim->spr[1].enable = 1u;
sim->spr[1].msb = sim->spr[0].msb;
sim->spr[1].col = sim->spr[0].col;
sim->spr[1].row = sim->spr[0].row;
sim->spr[1].ptr = ST_SPRITE_PASS_BEAM;
sim->decayTimer = sim->decayReload;
pad = &sim->pads[sim->activeSpriteIdx - 1u];
sim->hoverXFrac = pad->passMsb;
sim->hoverXCol = pad->passCol;
lightOnAndLatch(sim);
return;
}
default:
lightOnAndLatch(sim);
return;
}
}
// $69B4 -- the cab touched the passenger: he vanishes, the fare is
// lost and the meter is docked ten dollars.
void stFareSquashed(StSimT *sim) {
if (sim->stage >= ST_STAGE_LEAVE) {
return;
}
if (sim->stage < ST_STAGE_DROP_IN) {
sim->spriteSlots[sim->activeSpriteIdx] = 0u;
sim->activeSpriteIdx = 0u;
sim->fareSlotCount--;
stSimClearMessage(sim);
}
sim->stage = ST_STAGE_LEAVE;
stFareBoardingSound(sim, kSpeechHey);
// $43F3: a hundred dime decrements.
{
uint8_t k;
for (k = 0u; k < 100u; k++) {
(void)stSimDecrementNumber(sim, ST_CELL_SCORE, 5u);
}
// $43F3 counts down in the input-mask byte and leaves it zero.
sim->inputMask = 0u;
}
sim->spr[1].ptr = ST_SPRITE_PASS_STAND;
stSimHudInit(sim);
}
// $65C1 -- run the current stage.
void stFareStageDispatch(StSimT *sim) {
switch (sim->stage) {
case ST_STAGE_IDLE:
idleTicker(sim);
break;
case ST_STAGE_BEAM_IN:
beamIn(sim);
break;
case ST_STAGE_WAIT:
stFareWaveStep(sim);
break;
case ST_STAGE_WALK_TO_CAB:
case ST_STAGE_WALK_TO_PAD:
walkToTarget(sim);
break;
case ST_STAGE_BOARD:
stageBoard(sim);
break;
case ST_STAGE_RIDING:
sim->deathInProgress = 0u;
stSimPadLight(sim, false);
break;
case ST_STAGE_DROP_IN:
stageDropIn(sim);
break;
case ST_STAGE_LEAVE:
stFareLeaveStep(sim);
break;
case ST_STAGE_CLEANUP:
stageCleanup(sim);
break;
default:
sim->stage = ST_STAGE_IDLE;
break;
}
}
// $6CB8 -- no fare pad left: the passenger wants to leave the screen,
// so the top hatch opens.
void stFareUpPlease(StSimT *sim) {
uint8_t k;
sim->activeSpriteIdx = ST_FARE_DEST_UP;
stSimDrawText(sim, 14u, 24u, kTextUpPlease, 1u);
for (k = 0u; k < 4u; k++) {
stSimPutChar(sim, (uint8_t)(18u + k), 0u, ST_CHAR_SPACE);
}
stFareBoardingSound(sim, kSpeechUp);
}