Space Taxi work.
BIN
assets/font.png
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 129 B |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 129 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 129 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 129 B |
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 129 B |
15
spacetaxi.c
|
|
@ -22,11 +22,14 @@
|
||||||
#include "spacetaxi.h"
|
#include "spacetaxi.h"
|
||||||
#include "stDemoStreams.h"
|
#include "stDemoStreams.h"
|
||||||
|
|
||||||
// Diagnostics. On the IIgs the logger drags in vsnprintf and an 8 KB
|
// Diagnostics. The logger drags in vsnprintf (with newlib that pulls the
|
||||||
// ring buffer that the bank-0 BSS budget cannot spare, and the game has
|
// whole formatted-output and float-conversion family, ~45 KB) plus a
|
||||||
// no need of them there, so logging compiles to nothing on the 65816
|
// multi-KB ring buffer, and the game has no need of either. That is
|
||||||
// and stays on the other ports.
|
// unaffordable on the IIgs, whose bank-0 BSS budget cannot spare the
|
||||||
#if defined(__W65816__)
|
// ring, and on the X68000, which ships on a Human68k floppy with about
|
||||||
|
// 330 KB of usable space for the binary AND its levels. Both compile it
|
||||||
|
// out; the ports with room keep it.
|
||||||
|
#if defined(__W65816__) || defined(JOEYLIB_PLATFORM_X68000)
|
||||||
#define ST_LOG_RESET() ((void)0)
|
#define ST_LOG_RESET() ((void)0)
|
||||||
#define ST_LOG(...) ((void)0)
|
#define ST_LOG(...) ((void)0)
|
||||||
#else
|
#else
|
||||||
|
|
@ -165,6 +168,7 @@ static void enterTitle(void) {
|
||||||
}
|
}
|
||||||
stTitleEnter(&gSim, &gLevel);
|
stTitleEnter(&gSim, &gLevel);
|
||||||
stRenderSceneChanged(&gSim);
|
stRenderSceneChanged(&gSim);
|
||||||
|
stRenderTitleLogo(&gSim);
|
||||||
stAudioNoise(false);
|
stAudioNoise(false);
|
||||||
stAudioSilence();
|
stAudioSilence();
|
||||||
gGame.state = ST_STATE_TITLE;
|
gGame.state = ST_STATE_TITLE;
|
||||||
|
|
@ -617,6 +621,7 @@ int main(void) {
|
||||||
memset(&gGame, 0, sizeof(gGame));
|
memset(&gGame, 0, sizeof(gGame));
|
||||||
memset(&gSim, 0, sizeof(gSim));
|
memset(&gSim, 0, sizeof(gSim));
|
||||||
stRenderInit(stage);
|
stRenderInit(stage);
|
||||||
|
stRenderLoading(stage);
|
||||||
stAudioInit();
|
stAudioInit();
|
||||||
// Boot: the playback buffer as the C64 leaves it, and the state a
|
// Boot: the playback buffer as the C64 leaves it, and the state a
|
||||||
// fresh machine has when the first title intro starts ($4092 + the
|
// fresh machine has when the first title intro starts ($4092 + the
|
||||||
|
|
|
||||||
12
spacetaxi.h
|
|
@ -21,10 +21,20 @@ bool stLevelLoad(StLevelT *out, const char *path);
|
||||||
// Renderer: paints dirty screen cells with the live charset and draws
|
// Renderer: paints dirty screen cells with the live charset and draws
|
||||||
// the eight sprites of the last marshaled frame with save-under.
|
// the eight sprites of the last marshaled frame with save-under.
|
||||||
void stRenderInit(jlSurfaceT *stage);
|
void stRenderInit(jlSurfaceT *stage);
|
||||||
|
// A word on the stage before the slow part of startup: loading the cel
|
||||||
|
// bank and the first level takes long enough that the host's desktop
|
||||||
|
// would otherwise sit there with nothing to look at.
|
||||||
|
void stRenderLoading(jlSurfaceT *stage);
|
||||||
void stRenderShutdown(void);
|
void stRenderShutdown(void);
|
||||||
void stRenderFrame(jlSurfaceT *stage, StSimT *sim);
|
void stRenderFrame(jlSurfaceT *stage, StSimT *sim);
|
||||||
// Drop the sprite cache and force a full repaint (new scene).
|
// Drop the sprite cache and force a full repaint (new scene). Also
|
||||||
|
// leaves the title's logo mode (below).
|
||||||
void stRenderSceneChanged(StSimT *sim);
|
void stRenderSceneChanged(StSimT *sim);
|
||||||
|
// Title screen only: draw the logo's four flip-book frames from one
|
||||||
|
// tile held in reserved palette slots, so the flip and the colour
|
||||||
|
// cycle are palette writes instead of repainting 103 (and 407) cells.
|
||||||
|
// Call after stRenderSceneChanged, which turns it back off.
|
||||||
|
void stRenderTitleLogo(StSimT *sim);
|
||||||
void stRenderPrewarm(jlSurfaceT *stage, StSimT *sim);
|
void stRenderPrewarm(jlSurfaceT *stage, StSimT *sim);
|
||||||
|
|
||||||
// Audio sink setup and the per-tick release timers ($4320).
|
// Audio sink setup and the per-tick release timers ($4320).
|
||||||
|
|
|
||||||
11
stHooks.c
|
|
@ -236,6 +236,7 @@ static void hookHSegment(StSimT *sim, uint8_t id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Level T "FAST BREAK" $7D9D -- the barrier on row 3: `ch` into the
|
// Level T "FAST BREAK" $7D9D -- the barrier on row 3: `ch` into the
|
||||||
// centre opening (cols 18..21), its complement into the side gaps.
|
// centre opening (cols 18..21), its complement into the side gaps.
|
||||||
static void hookTGate(StSimT *sim, uint8_t ch) {
|
static void hookTGate(StSimT *sim, uint8_t ch) {
|
||||||
|
|
@ -306,8 +307,8 @@ static void hookWLaserUpdate(StSimT *sim) {
|
||||||
for (k = 0u; k < 16u; k++) {
|
for (k = 0u; k < 16u; k++) {
|
||||||
sim->charset[0x92u + (k >> 3)][k & 7u] = 0u;
|
sim->charset[0x92u + (k >> 3)][k & 7u] = 0u;
|
||||||
}
|
}
|
||||||
sim->charDirty[0x92u] = 1u;
|
stSimMarkChar(sim, 0x92u);
|
||||||
sim->charDirty[0x93u] = 1u;
|
stSimMarkChar(sim, 0x93u);
|
||||||
for (i = 0u; i < 8u; i++) {
|
for (i = 0u; i < 8u; i++) {
|
||||||
uint8_t state = HK(sim, 0x7DAC + i);
|
uint8_t state = HK(sim, 0x7DAC + i);
|
||||||
uint8_t col = HK(sim, 0x7DCD + i);
|
uint8_t col = HK(sim, 0x7DCD + i);
|
||||||
|
|
@ -376,8 +377,8 @@ static void hookWPerTick(StSimT *sim) {
|
||||||
// and $2C98 + (8 - c) (char $93 rows 7..1) get a beam bar.
|
// and $2C98 + (8 - c) (char $93 rows 7..1) get a beam bar.
|
||||||
sim->charset[0x91u + ((7u + c) >> 3)][(7u + c) & 7u] = 0x3Cu;
|
sim->charset[0x91u + ((7u + c) >> 3)][(7u + c) & 7u] = 0x3Cu;
|
||||||
sim->charset[0x93u][8u - c] = 0x3Cu;
|
sim->charset[0x93u][8u - c] = 0x3Cu;
|
||||||
sim->charDirty[0x92u] = 1u;
|
stSimMarkChar(sim, 0x92u);
|
||||||
sim->charDirty[0x93u] = 1u;
|
stSimMarkChar(sim, 0x93u);
|
||||||
if ((c & 1u) == 0u) {
|
if ((c & 1u) == 0u) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -628,7 +629,7 @@ static void hookOTramp0(StSimT *sim) {
|
||||||
uint8_t b = sim->charset[0x6Fu][k];
|
uint8_t b = sim->charset[0x6Fu][k];
|
||||||
sim->charset[0x6Fu][k] = (uint8_t)((b >> 1) | (b << 7));
|
sim->charset[0x6Fu][k] = (uint8_t)((b >> 1) | (b << 7));
|
||||||
}
|
}
|
||||||
sim->charDirty[0x6Fu] = 1u;
|
stSimMarkChar(sim, 0x6Fu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
405
stRender.c
|
|
@ -3,11 +3,11 @@
|
||||||
//
|
//
|
||||||
// The screen is 40x25 character cells painted from the live charset
|
// The screen is 40x25 character cells painted from the live charset
|
||||||
// (the game edits glyphs at runtime: the transporter hatch, the title
|
// (the game edits glyphs at runtime: the transporter hatch, the title
|
||||||
// logo flip-book, the laser beams). A glyph is one mono JoeyLib tile
|
// logo flip-book, the laser beams). A cell goes to the stage in one
|
||||||
// (set bits = non-zero pixels), cached by shape and coloured on the way
|
// call: jlTilePasteGlyph takes the character's eight 1bpp charset rows
|
||||||
// to the stage by jlTilePasteMono from the cell's colour RAM, so a
|
// as they stand and colours them from the cell's colour RAM, so an
|
||||||
// colour change never rebuilds a tile and an animated glyph is built
|
// edited glyph needs no rebuild and a colour change costs nothing.
|
||||||
// once per shape. Only the cells the simulation listed as dirty are
|
// Only the cells the simulation listed as dirty are
|
||||||
// repainted; a changed glyph dirties the cells showing it, found
|
// repainted; a changed glyph dirties the cells showing it, found
|
||||||
// inside the cell range each character was last painted in. The eight
|
// inside the cell range each character was last painted in. The eight
|
||||||
// VIC sprites are drawn from their bitmaps through a small cache of
|
// VIC sprites are drawn from their bitmaps through a small cache of
|
||||||
|
|
@ -17,6 +17,16 @@
|
||||||
// moved (and has nothing repainted under it) simply stays on the
|
// moved (and has nothing repainted under it) simply stays on the
|
||||||
// stage: only it and everything drawn after it are undrawn and redrawn
|
// stage: only it and everything drawn after it are undrawn and redrawn
|
||||||
// when it changes.
|
// when it changes.
|
||||||
|
//
|
||||||
|
// The title screen gets a special path (stRenderTitleLogo): its logo is
|
||||||
|
// 103 cells of one character that the flip-book animates by copying one
|
||||||
|
// of four frame glyphs over it, and a colour cycle that used to rewrite
|
||||||
|
// the colour of 407 cells. Both were pure repaint cost. Instead the four
|
||||||
|
// frames collapse into ONE tile whose pixels are drawn in reserved
|
||||||
|
// palette slots -- one slot per set of frames a pixel belongs to -- so a
|
||||||
|
// flip and a recolour are both a single palette write and no cell is
|
||||||
|
// ever repainted. The title's decoration sprites, which cycle colour
|
||||||
|
// with the logo, share one more reserved slot for the same reason.
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
|
@ -32,15 +42,23 @@
|
||||||
// by character, so the count is a power of two.
|
// by character, so the count is a power of two.
|
||||||
#if defined(__W65816__)
|
#if defined(__W65816__)
|
||||||
#define ST_SPRITE_CACHE 48u
|
#define ST_SPRITE_CACHE 48u
|
||||||
#define ST_GLYPH_CACHE 64u
|
|
||||||
#else
|
#else
|
||||||
#define ST_SPRITE_CACHE 72u
|
#define ST_SPRITE_CACHE 72u
|
||||||
#define ST_GLYPH_CACHE 256u
|
|
||||||
#endif
|
#endif
|
||||||
#define ST_SPRITE_BACKUP_BYTES JOEY_SPRITE_BACKUP_BYTES(ST_SPRITE_TILES, ST_SPRITE_TILES)
|
#define ST_SPRITE_BACKUP_BYTES JOEY_SPRITE_BACKUP_BYTES(ST_SPRITE_TILES, ST_SPRITE_TILES)
|
||||||
#define ST_SPRITE_PX (8 * ST_SPRITE_TILES)
|
#define ST_SPRITE_PX (8 * ST_SPRITE_TILES)
|
||||||
// glyphFirst[] value for a character shown in no cell.
|
// Palette slots the title logo animation owns. The title screen draws
|
||||||
#define ST_NO_CELL 0xFFFFu
|
// in C64 colours 0, 1, 2, 6, 7, 11 and 12 only (plus colour 5 for the
|
||||||
|
// "GET READY" banner that appears over it), so these are free while it
|
||||||
|
// is up and the whole trick needs no per-scanline SCB band.
|
||||||
|
#define ST_PAL_LOGO_SPR 3u // the decoration sprites' cycling colour
|
||||||
|
#define ST_LOGO_PAL_SLOTS 7u
|
||||||
|
// The first hardware sprite that cycles colour with the logo.
|
||||||
|
#define ST_LOGO_SPR_FIRST 3u
|
||||||
|
|
||||||
|
// Where the startup message sits, and its C64 colour (1 = white).
|
||||||
|
#define ST_LOADING_ROW 12u
|
||||||
|
#define ST_LOADING_COLOR 1u
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
jlSpriteT *sprite;
|
jlSpriteT *sprite;
|
||||||
|
|
@ -68,18 +86,27 @@ typedef struct {
|
||||||
} StDrawnT;
|
} StDrawnT;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
jlSurfaceT *stage;
|
||||||
jlSurfaceT *scratch;
|
jlSurfaceT *scratch;
|
||||||
// Glyphs as mono tiles (set bits = non-zero pixels, coloured per cell
|
// Title logo (stRenderTitleLogo): all four flip-book frames in one
|
||||||
// by jlTilePasteMono), direct-mapped by character and valid while
|
// tile, animated by rewriting logoSlotMask[]'s palette slots.
|
||||||
// the bits match. Parallel arrays: power-of-two element sizes keep
|
bool logoMode;
|
||||||
// the 65816 indexing to shifts.
|
bool logoTileOk; // the live glyph is one of the frames
|
||||||
uint8_t glyphBits[ST_GLYPH_CACHE][8];
|
uint8_t logoSlotCount;
|
||||||
jlTileT glyphTiles[ST_GLYPH_CACHE];
|
uint8_t logoSlotMask[ST_LOGO_PAL_SLOTS]; // frames each slot is lit in
|
||||||
bool glyphUsed[ST_GLYPH_CACHE];
|
uint8_t logoFrame; // frame the palette currently shows
|
||||||
|
uint8_t logoColor; // logo colour the palette currently shows
|
||||||
|
uint8_t logoSprColor; // decoration-sprite colour it shows
|
||||||
|
jlTileT logoTile;
|
||||||
// Cell range (inclusive) each character has been painted in since
|
// Cell range (inclusive) each character has been painted in since
|
||||||
// the last full repaint: where a changed glyph's cells can be.
|
// the last full repaint: where a changed glyph's cells can be.
|
||||||
uint16_t glyphFirst[ST_CHARSET_CHARS];
|
uint16_t glyphFirst[ST_CHARSET_CHARS];
|
||||||
uint16_t glyphLast[ST_CHARSET_CHARS];
|
uint16_t glyphLast[ST_CHARSET_CHARS];
|
||||||
|
// Column band of the cells about to be repainted, per character row
|
||||||
|
// (min > max = the row is clean), the same shape as the library's
|
||||||
|
// own per-row dirty bands.
|
||||||
|
uint8_t dirtyColMin[ST_SCREEN_ROWS];
|
||||||
|
uint8_t dirtyColMax[ST_SCREEN_ROWS];
|
||||||
uint32_t cacheStamp;
|
uint32_t cacheStamp;
|
||||||
StSpriteCacheT cache[ST_SPRITE_CACHE];
|
StSpriteCacheT cache[ST_SPRITE_CACHE];
|
||||||
StSpriteCacheT *lastHit[ST_HW_SPRITES]; // the entry each hardware sprite used last
|
StSpriteCacheT *lastHit[ST_HW_SPRITES]; // the entry each hardware sprite used last
|
||||||
|
|
@ -100,15 +127,24 @@ static const uint16_t kC64Palette[16] = {
|
||||||
0x0852, 0x0540, 0x0B66, 0x0555, 0x0777, 0x09E8, 0x076C, 0x09AA
|
0x0852, 0x0540, 0x0B66, 0x0555, 0x0777, 0x09E8, 0x076C, 0x09AA
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Palette slots the logo tile's pixels are drawn in, one per distinct
|
||||||
|
// set of flip-book frames a pixel belongs to. The four frames nest, so
|
||||||
|
// four of these are used; the rest are headroom if the frames change.
|
||||||
|
static const uint8_t kLogoPalSlot[ST_LOGO_PAL_SLOTS] = { 4u, 8u, 9u, 10u, 13u, 14u, 15u };
|
||||||
|
|
||||||
|
|
||||||
static void buildSpriteCel(const uint8_t *bm, uint8_t multi, uint8_t color, uint8_t mc0, uint8_t mc1);
|
static void buildSpriteCel(const uint8_t *bm, uint8_t multi, uint8_t color, uint8_t mc0, uint8_t mc1);
|
||||||
static jlSpriteT *cachedSprite(StSimT *sim, uint8_t idx);
|
static jlSpriteT *cachedSprite(StSimT *sim, uint8_t idx);
|
||||||
static bool loadPrecompiledCels(void);
|
|
||||||
static uint8_t cellRow(uint16_t cell);
|
static uint8_t cellRow(uint16_t cell);
|
||||||
static void collectGlyphs(StSimT *sim);
|
static void collectGlyphs(StSimT *sim);
|
||||||
static void dirtyRect(const StSimT *sim, int16_t *x0, int16_t *y0, int16_t *x1, int16_t *y1);
|
static void dirtyBands(const StSimT *sim);
|
||||||
|
static bool dirtyUnderSprite(int16_t px, int16_t py);
|
||||||
|
static uint8_t drawColor(const StSimT *sim, uint8_t idx);
|
||||||
static void dropCache(void);
|
static void dropCache(void);
|
||||||
static const jlTileT *glyphTile(const StSimT *sim, uint8_t chr);
|
static bool loadPrecompiledCels(void);
|
||||||
|
static bool logoBuildTile(const StSimT *sim);
|
||||||
|
static uint8_t logoFrameIndex(const StSimT *sim);
|
||||||
|
static void logoPaletteApply(const StSimT *sim, uint8_t frame);
|
||||||
static void paintCells(jlSurfaceT *stage, StSimT *sim);
|
static void paintCells(jlSurfaceT *stage, StSimT *sim);
|
||||||
static void pasteCell(jlSurfaceT *stage, const StSimT *sim, uint16_t cell, uint8_t bx, uint8_t by);
|
static void pasteCell(jlSurfaceT *stage, const StSimT *sim, uint16_t cell, uint8_t bx, uint8_t by);
|
||||||
static bool spriteOnScreen(int16_t px, int16_t py);
|
static bool spriteOnScreen(int16_t px, int16_t py);
|
||||||
|
|
@ -139,7 +175,7 @@ static void buildSpriteCel(const uint8_t *bm, uint8_t multi, uint8_t color, uint
|
||||||
// built on first use from its VIC bitmap and colours.
|
// built on first use from its VIC bitmap and colours.
|
||||||
static jlSpriteT *cachedSprite(StSimT *sim, uint8_t idx) {
|
static jlSpriteT *cachedSprite(StSimT *sim, uint8_t idx) {
|
||||||
uint8_t ptr = sim->frame.ptr[idx];
|
uint8_t ptr = sim->frame.ptr[idx];
|
||||||
uint8_t color = sim->frame.color[idx];
|
uint8_t color = drawColor(sim, idx);
|
||||||
uint8_t multi = (uint8_t)((sim->frame.multiMask >> idx) & 1u);
|
uint8_t multi = (uint8_t)((sim->frame.multiMask >> idx) & 1u);
|
||||||
uint8_t mc0 = multi ? sim->spriteMc0 : 0u;
|
uint8_t mc0 = multi ? sim->spriteMc0 : 0u;
|
||||||
uint8_t mc1 = multi ? sim->spriteMc1 : 0u;
|
uint8_t mc1 = multi ? sim->spriteMc1 : 0u;
|
||||||
|
|
@ -206,33 +242,50 @@ static jlSpriteT *cachedSprite(StSimT *sim, uint8_t idx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Block row of a screen cell without a division: cell / 40 is
|
// Block row of a screen cell: cell / 40, read from the 125 distinct
|
||||||
// (cell / 8) / 5, and x * 205 / 1024 equals x / 5 for every x below 125.
|
// results of (cell >> 3) / 5. The old (x * 205) >> 10 identity avoided a
|
||||||
|
// division but still called the 65816's software multiply, and this runs
|
||||||
|
// twice for every repainted cell.
|
||||||
|
static const uint8_t kCellRow[125] = {
|
||||||
|
0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4,
|
||||||
|
4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 9,
|
||||||
|
9, 9, 9, 9, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 12, 12, 12, 12,
|
||||||
|
12, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16,
|
||||||
|
16, 16, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 19, 19, 19,
|
||||||
|
19, 19, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22,
|
||||||
|
23, 23, 23, 23, 23, 24, 24, 24, 24, 24
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
static uint8_t cellRow(uint16_t cell) {
|
static uint8_t cellRow(uint16_t cell) {
|
||||||
return (uint8_t)(((uint16_t)(cell >> 3) * 205u) >> 10);
|
return kCellRow[cell >> 3];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// A changed glyph (the hatch, the logo flip, the laser beams) makes
|
// A changed glyph (the hatch, the logo flip, the laser beams) makes
|
||||||
// every cell that shows it dirty. The glyph cache self-invalidates on
|
// every cell that shows it dirty. The simulation hands over a short list
|
||||||
// the bitmap compare, so nothing is rebuilt here; the search covers
|
// of the characters it edited, so nothing scans the charset; the cell
|
||||||
// only the cell range the character was last painted in.
|
// search covers only the range the character was last painted in. The
|
||||||
|
// glyph itself needs no invalidation -- the paste reads the live charset
|
||||||
|
// every time.
|
||||||
static void collectGlyphs(StSimT *sim) {
|
static void collectGlyphs(StSimT *sim) {
|
||||||
uint16_t ch;
|
uint8_t k;
|
||||||
uint16_t cell;
|
uint16_t cell;
|
||||||
uint16_t last;
|
uint16_t last;
|
||||||
|
|
||||||
for (ch = 0u; ch < ST_CHARSET_CHARS; ch++) {
|
if (sim->charDirtyAll) {
|
||||||
if (sim->charDirty[ch] == 0u) {
|
sim->charDirtyAll = false;
|
||||||
continue;
|
sim->charDirtyCount = 0u;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
sim->charDirty[ch] = 0u;
|
for (k = 0u; k < sim->charDirtyCount; k++) {
|
||||||
|
uint8_t ch = sim->charDirtyList[k];
|
||||||
if (sim->dirtyAll) {
|
if (sim->dirtyAll) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
last = gRender.glyphLast[ch];
|
last = gRender.glyphLast[ch];
|
||||||
for (cell = gRender.glyphFirst[ch]; cell <= last; cell++) {
|
for (cell = gRender.glyphFirst[ch]; cell <= last; cell++) {
|
||||||
if (sim->screen[cell] != (uint8_t)ch || sim->cellDirty[cell] != 0u) {
|
if (sim->screen[cell] != ch || sim->cellDirty[cell] != 0u) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sim->cellDirty[cell] = 1u;
|
sim->cellDirty[cell] = 1u;
|
||||||
|
|
@ -244,46 +297,96 @@ static void collectGlyphs(StSimT *sim) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sim->charDirtyCount = 0u;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Bounding box (pixels, x1/y1 exclusive) of the cells about to be
|
// Note where the cells about to be repainted are, as one column band
|
||||||
// repainted. Empty when x1 <= x0.
|
// per character row. This used to be a single bounding box over every
|
||||||
static void dirtyRect(const StSimT *sim, int16_t *x0, int16_t *y0, int16_t *x1, int16_t *y1) {
|
// dirty cell, and that box was wildly too coarse: the status row's
|
||||||
|
// score and fare meters sit at opposite ends of the screen and one of
|
||||||
|
// them moves on nearly every tick, so the box spanned the picture and
|
||||||
|
// every sprite counted as sitting over repainted ground -- all eight
|
||||||
|
// were undrawn and redrawn on about a third of all frames while only
|
||||||
|
// the cab and its exhaust had actually moved. Per row the bands are
|
||||||
|
// tight, and the rows a sprite covers are the only ones it tests.
|
||||||
|
static void dirtyBands(const StSimT *sim) {
|
||||||
uint16_t k;
|
uint16_t k;
|
||||||
|
|
||||||
|
uint8_t row;
|
||||||
|
|
||||||
if (sim->dirtyAll) {
|
if (sim->dirtyAll) {
|
||||||
*x0 = 0;
|
for (row = 0u; row < ST_SCREEN_ROWS; row++) {
|
||||||
*y0 = 0;
|
gRender.dirtyColMin[row] = 0u;
|
||||||
*x1 = SURFACE_WIDTH;
|
gRender.dirtyColMax[row] = ST_SCREEN_COLS - 1u;
|
||||||
*y1 = SURFACE_HEIGHT;
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
*x0 = SURFACE_WIDTH;
|
// A plain loop, not memset: 25 entries do not pay for the 65816
|
||||||
*y0 = SURFACE_HEIGHT;
|
// libc's far-called byte loop.
|
||||||
*x1 = 0;
|
for (row = 0u; row < ST_SCREEN_ROWS; row++) {
|
||||||
*y1 = 0;
|
gRender.dirtyColMin[row] = 0xFFu;
|
||||||
|
gRender.dirtyColMax[row] = 0u;
|
||||||
|
}
|
||||||
for (k = 0u; k < sim->dirtyCount; k++) {
|
for (k = 0u; k < sim->dirtyCount; k++) {
|
||||||
uint16_t cell = sim->dirtyList[k];
|
uint16_t cell = sim->dirtyList[k];
|
||||||
uint8_t by = cellRow(cell);
|
uint8_t r = cellRow(cell);
|
||||||
int16_t cy = (int16_t)(by * 8u);
|
uint8_t col = (uint8_t)(cell - ((uint16_t)r << 5) - ((uint16_t)r << 3));
|
||||||
int16_t cx = (int16_t)((cell - ((uint16_t)by << 5) - ((uint16_t)by << 3)) * 8u);
|
if (col < gRender.dirtyColMin[r]) {
|
||||||
if (cx < *x0) {
|
gRender.dirtyColMin[r] = col;
|
||||||
*x0 = cx;
|
|
||||||
}
|
}
|
||||||
if (cy < *y0) {
|
if (col > gRender.dirtyColMax[r]) {
|
||||||
*y0 = cy;
|
gRender.dirtyColMax[r] = col;
|
||||||
}
|
|
||||||
if ((int16_t)(cx + 8) > *x1) {
|
|
||||||
*x1 = (int16_t)(cx + 8);
|
|
||||||
}
|
|
||||||
if ((int16_t)(cy + 8) > *y1) {
|
|
||||||
*y1 = (int16_t)(cy + 8);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// True when a cell about to be repainted lies under the sprite drawn at
|
||||||
|
// (px, py): its save-under backup would hold stale background, so it
|
||||||
|
// has to be undrawn before the repaint and drawn again after.
|
||||||
|
static bool dirtyUnderSprite(int16_t px, int16_t py) {
|
||||||
|
int16_t x1 = (int16_t)(px + ST_SPRITE_W);
|
||||||
|
int16_t y1 = (int16_t)(py + ST_SPRITE_PX);
|
||||||
|
uint8_t row;
|
||||||
|
uint8_t rowEnd;
|
||||||
|
uint8_t colFirst;
|
||||||
|
uint8_t colLast;
|
||||||
|
|
||||||
|
if (x1 > SURFACE_WIDTH) {
|
||||||
|
x1 = SURFACE_WIDTH;
|
||||||
|
}
|
||||||
|
if (y1 > SURFACE_HEIGHT) {
|
||||||
|
y1 = SURFACE_HEIGHT;
|
||||||
|
}
|
||||||
|
// spriteOnScreen has already rejected anything fully off the stage,
|
||||||
|
// so only the top/left overhang needs clamping to row/column 0.
|
||||||
|
row = (uint8_t)(py < 0 ? 0 : (py >> 3));
|
||||||
|
rowEnd = (uint8_t)((y1 - 1) >> 3);
|
||||||
|
colFirst = (uint8_t)(px < 0 ? 0 : (px >> 3));
|
||||||
|
colLast = (uint8_t)((x1 - 1) >> 3);
|
||||||
|
for (; row <= rowEnd; row++) {
|
||||||
|
if (gRender.dirtyColMin[row] <= colLast && gRender.dirtyColMax[row] >= colFirst) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The palette index a hardware sprite is drawn in. On the title screen
|
||||||
|
// sprites 3..7 all follow the logo's colour cycle, so they share one
|
||||||
|
// reserved slot: the cycle then recolours them with a palette write
|
||||||
|
// instead of rebuilding five cels, and -- because their drawn state
|
||||||
|
// stops changing -- without redrawing the sprites stacked above them.
|
||||||
|
static uint8_t drawColor(const StSimT *sim, uint8_t idx) {
|
||||||
|
if (gRender.logoMode && idx >= ST_LOGO_SPR_FIRST) {
|
||||||
|
return ST_PAL_LOGO_SPR;
|
||||||
|
}
|
||||||
|
return sim->frame.color[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void dropCache(void) {
|
static void dropCache(void) {
|
||||||
uint8_t k;
|
uint8_t k;
|
||||||
|
|
||||||
|
|
@ -297,32 +400,88 @@ static void dropCache(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// The mono tile for character `chr`, rebuilt when the glyph's bits no
|
// Collapse the four flip-book frame glyphs into one tile: every pixel
|
||||||
// longer match the cached shape (direct-mapped by character).
|
// is drawn in the reserved palette slot that stands for the set of
|
||||||
static const jlTileT *glyphTile(const StSimT *sim, uint8_t chr) {
|
// frames it is set in (no frame = colour 0, the background). Animating
|
||||||
// Two glyph bits -> one chunky byte of non-zero (set) / zero pixels.
|
// the logo is then a palette write per flip instead of a repaint of
|
||||||
static const uint8_t kMonoPair[4] = { 0x00u, 0x0Fu, 0xF0u, 0xFFu };
|
// every cell showing it. Returns false if the frames need more distinct
|
||||||
const uint8_t *bits = sim->charset[chr];
|
// sets than there are reserved slots, leaving the ordinary per-cell
|
||||||
uint8_t slot = (uint8_t)(chr & (ST_GLYPH_CACHE - 1u));
|
// repaint to draw the flip book.
|
||||||
jlTileT *tile = &gRender.glyphTiles[slot];
|
static bool logoBuildTile(const StSimT *sim) {
|
||||||
|
uint8_t slotOfMask[1u << ST_LOGO_FRAMES];
|
||||||
uint8_t chunky[TILE_BYTES];
|
uint8_t chunky[TILE_BYTES];
|
||||||
uint8_t row;
|
uint8_t row;
|
||||||
|
uint8_t col;
|
||||||
|
|
||||||
if (gRender.glyphUsed[slot] && memcmp(gRender.glyphBits[slot], bits, 8u) == 0) {
|
memset(slotOfMask, 0, sizeof(slotOfMask));
|
||||||
return tile;
|
gRender.logoSlotCount = 0u;
|
||||||
}
|
|
||||||
for (row = 0u; row < TILE_PIXELS_PER_SIDE; row++) {
|
for (row = 0u; row < TILE_PIXELS_PER_SIDE; row++) {
|
||||||
uint8_t b = bits[row];
|
uint8_t *out = &chunky[row * TILE_BYTES_PER_ROW];
|
||||||
uint8_t *p = &chunky[row * TILE_BYTES_PER_ROW];
|
for (col = 0u; col < TILE_PIXELS_PER_SIDE; col++) {
|
||||||
p[0] = kMonoPair[b >> 6];
|
uint8_t mask = 0u;
|
||||||
p[1] = kMonoPair[(b >> 4) & 3u];
|
uint8_t f;
|
||||||
p[2] = kMonoPair[(b >> 2) & 3u];
|
uint8_t slot;
|
||||||
p[3] = kMonoPair[b & 3u];
|
for (f = 0u; f < ST_LOGO_FRAMES; f++) {
|
||||||
|
if (((sim->charset[ST_LOGO_FRAME_FIRST + f][row] >> (7u - col)) & 1u) != 0u) {
|
||||||
|
mask |= (uint8_t)(1u << f);
|
||||||
}
|
}
|
||||||
tileFromChunky(tile, chunky);
|
}
|
||||||
memcpy(gRender.glyphBits[slot], bits, 8u);
|
if (mask != 0u && slotOfMask[mask] == 0u) {
|
||||||
gRender.glyphUsed[slot] = true;
|
if (gRender.logoSlotCount == ST_LOGO_PAL_SLOTS) {
|
||||||
return tile;
|
return false;
|
||||||
|
}
|
||||||
|
slotOfMask[mask] = kLogoPalSlot[gRender.logoSlotCount];
|
||||||
|
gRender.logoSlotMask[gRender.logoSlotCount] = mask;
|
||||||
|
gRender.logoSlotCount++;
|
||||||
|
}
|
||||||
|
slot = slotOfMask[mask];
|
||||||
|
if ((col & 1u) == 0u) {
|
||||||
|
out[col >> 1] = (uint8_t)(slot << 4);
|
||||||
|
} else {
|
||||||
|
out[col >> 1] |= slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tileFromChunky(&gRender.logoTile, chunky);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Which flip-book frame the live logo glyph holds, or ST_LOGO_FRAMES if
|
||||||
|
// it matches none of them (then the logo tile does not stand for what
|
||||||
|
// the simulation is showing and the cells must be repainted normally).
|
||||||
|
static uint8_t logoFrameIndex(const StSimT *sim) {
|
||||||
|
uint8_t f;
|
||||||
|
|
||||||
|
for (f = 0u; f < ST_LOGO_FRAMES; f++) {
|
||||||
|
if (memcmp(sim->charset[ST_LOGO_CHAR], sim->charset[ST_LOGO_FRAME_FIRST + f], 8u) == 0) {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ST_LOGO_FRAMES;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Show flip-book frame `frame` in the logo's current colour: every
|
||||||
|
// reserved slot lights up in the colour if that frame is in its set and
|
||||||
|
// goes to the background colour if it is not. The decoration sprites'
|
||||||
|
// slot follows their own colour, which the cycle sets one step behind
|
||||||
|
// the logo's (the title's sprite table overwrites it on entry).
|
||||||
|
static void logoPaletteApply(const StSimT *sim, uint8_t frame) {
|
||||||
|
uint16_t pal[SURFACE_COLORS_PER_PALETTE];
|
||||||
|
uint16_t on = kC64Palette[sim->logoColor & 15u];
|
||||||
|
uint16_t off = kC64Palette[sim->bgColor & 15u];
|
||||||
|
uint8_t k;
|
||||||
|
|
||||||
|
jlPaletteGet(gRender.stage, 0u, pal);
|
||||||
|
for (k = 0u; k < gRender.logoSlotCount; k++) {
|
||||||
|
pal[kLogoPalSlot[k]] = (((gRender.logoSlotMask[k] >> frame) & 1u) != 0u) ? on : off;
|
||||||
|
}
|
||||||
|
pal[ST_PAL_LOGO_SPR] = kC64Palette[sim->frame.color[ST_LOGO_SPR_FIRST] & 15u];
|
||||||
|
jlPaletteSet(gRender.stage, 0u, pal);
|
||||||
|
gRender.logoFrame = frame;
|
||||||
|
gRender.logoColor = sim->logoColor;
|
||||||
|
gRender.logoSprColor = sim->frame.color[ST_LOGO_SPR_FIRST];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -360,8 +519,9 @@ static void paintCells(jlSurfaceT *stage, StSimT *sim) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Paint one cell: its glyph tile coloured from colour RAM over the
|
// Paint one cell: the character's charset rows coloured from colour RAM
|
||||||
// background, and note the cell in the character's range.
|
// over the background. Also notes the cell in the character's range,
|
||||||
|
// which is where a changed glyph's cells are looked for.
|
||||||
static void pasteCell(jlSurfaceT *stage, const StSimT *sim, uint16_t cell, uint8_t bx, uint8_t by) {
|
static void pasteCell(jlSurfaceT *stage, const StSimT *sim, uint16_t cell, uint8_t bx, uint8_t by) {
|
||||||
uint8_t chr = sim->screen[cell];
|
uint8_t chr = sim->screen[cell];
|
||||||
|
|
||||||
|
|
@ -371,7 +531,14 @@ static void pasteCell(jlSurfaceT *stage, const StSimT *sim, uint16_t cell, uint8
|
||||||
if (cell > gRender.glyphLast[chr]) {
|
if (cell > gRender.glyphLast[chr]) {
|
||||||
gRender.glyphLast[chr] = cell;
|
gRender.glyphLast[chr] = cell;
|
||||||
}
|
}
|
||||||
jlTilePasteMono(stage, bx, by, glyphTile(sim, chr), sim->color[cell], sim->bgColor);
|
// On the title the logo's cells carry all four flip-book frames at
|
||||||
|
// once; the palette decides which one shows, so they are pasted as
|
||||||
|
// they are and never touched again.
|
||||||
|
if (gRender.logoTileOk && chr == ST_LOGO_CHAR) {
|
||||||
|
jlTilePaste(stage, bx, by, &gRender.logoTile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jlTilePasteGlyph(stage, bx, by, sim->charset[chr], sim->color[cell], sim->bgColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -407,18 +574,32 @@ static void tileFromChunky(jlTileT *out, const uint8_t *chunky) {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
void stRenderFrame(jlSurfaceT *stage, StSimT *sim) {
|
void stRenderFrame(jlSurfaceT *stage, StSimT *sim) {
|
||||||
int16_t dx0;
|
|
||||||
int16_t dy0;
|
|
||||||
int16_t dx1;
|
|
||||||
int16_t dy1;
|
|
||||||
uint8_t first = ST_HW_SPRITES;
|
uint8_t first = ST_HW_SPRITES;
|
||||||
uint8_t k;
|
uint8_t k;
|
||||||
|
|
||||||
// Find the first draw slot whose sprite changed, vanished, or sits
|
// Find the first draw slot whose sprite changed, vanished, or sits
|
||||||
// over cells about to be repainted; it and every later slot are
|
// over cells about to be repainted; it and every later slot are
|
||||||
// undrawn (last first) and redrawn below, the rest stay put.
|
// undrawn (last first) and redrawn below, the rest stay put.
|
||||||
|
if (gRender.logoMode) {
|
||||||
|
// The flip book only ever swaps the logo glyph for one of the
|
||||||
|
// four frames the logo tile already carries: recolour the
|
||||||
|
// reserved slots and drop the glyph's repaint on the floor.
|
||||||
|
uint8_t frame = logoFrameIndex(sim);
|
||||||
|
gRender.logoTileOk = (frame < ST_LOGO_FRAMES);
|
||||||
|
if (gRender.logoTileOk) {
|
||||||
|
// The flip book only swapped in a frame the logo tile already
|
||||||
|
// carries, so drop the repaint it asked for.
|
||||||
|
if (sim->charDirtyCount == 1u && sim->charDirtyList[0] == ST_LOGO_CHAR) {
|
||||||
|
sim->charDirtyCount = 0u;
|
||||||
|
}
|
||||||
|
if (frame != gRender.logoFrame || sim->logoColor != gRender.logoColor
|
||||||
|
|| sim->frame.color[ST_LOGO_SPR_FIRST] != gRender.logoSprColor) {
|
||||||
|
logoPaletteApply(sim, frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
collectGlyphs(sim);
|
collectGlyphs(sim);
|
||||||
dirtyRect(sim, &dx0, &dy0, &dx1, &dy1);
|
dirtyBands(sim);
|
||||||
for (k = 0u; k < ST_HW_SPRITES; k++) {
|
for (k = 0u; k < ST_HW_SPRITES; k++) {
|
||||||
uint8_t idx = (uint8_t)(ST_HW_SPRITES - 1u - k);
|
uint8_t idx = (uint8_t)(ST_HW_SPRITES - 1u - k);
|
||||||
StDrawnT *d = &gRender.slot[k];
|
StDrawnT *d = &gRender.slot[k];
|
||||||
|
|
@ -428,13 +609,11 @@ void stRenderFrame(jlSurfaceT *stage, StSimT *sim) {
|
||||||
bool want = (sim->frame.enableMask & (uint8_t)(1u << idx)) != 0u && spriteOnScreen(px, py);
|
bool want = (sim->frame.enableMask & (uint8_t)(1u << idx)) != 0u && spriteOnScreen(px, py);
|
||||||
bool same = (want == d->drawn);
|
bool same = (want == d->drawn);
|
||||||
if (same && want) {
|
if (same && want) {
|
||||||
same = (d->x == sim->frame.x[idx] && d->y == sim->frame.y[idx] && d->ptr == sim->frame.ptr[idx] && d->color == sim->frame.color[idx] && d->multi == multi);
|
same = (d->x == sim->frame.x[idx] && d->y == sim->frame.y[idx] && d->ptr == sim->frame.ptr[idx] && d->color == drawColor(sim, idx) && d->multi == multi);
|
||||||
}
|
}
|
||||||
if (same && want && dx1 > dx0) {
|
if (same && want && dirtyUnderSprite(px, py)) {
|
||||||
if (px < dx1 && (int16_t)(px + ST_SPRITE_W) > dx0 && py < dy1 && (int16_t)(py + ST_SPRITE_PX) > dy0) {
|
|
||||||
same = false;
|
same = false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!same) {
|
if (!same) {
|
||||||
first = k;
|
first = k;
|
||||||
break;
|
break;
|
||||||
|
|
@ -471,7 +650,7 @@ void stRenderFrame(jlSurfaceT *stage, StSimT *sim) {
|
||||||
d->x = sim->frame.x[idx];
|
d->x = sim->frame.x[idx];
|
||||||
d->y = sim->frame.y[idx];
|
d->y = sim->frame.y[idx];
|
||||||
d->ptr = sim->frame.ptr[idx];
|
d->ptr = sim->frame.ptr[idx];
|
||||||
d->color = sim->frame.color[idx];
|
d->color = drawColor(sim, idx);
|
||||||
d->multi = (uint8_t)((sim->frame.multiMask >> idx) & 1u);
|
d->multi = (uint8_t)((sim->frame.multiMask >> idx) & 1u);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|
@ -492,6 +671,7 @@ void stRenderInit(jlSurfaceT *stage) {
|
||||||
|
|
||||||
memset(&gRender, 0, sizeof(gRender));
|
memset(&gRender, 0, sizeof(gRender));
|
||||||
memset(gRender.glyphFirst, 0xFF, sizeof(gRender.glyphFirst));
|
memset(gRender.glyphFirst, 0xFF, sizeof(gRender.glyphFirst));
|
||||||
|
gRender.stage = stage;
|
||||||
for (k = 0u; k < ST_HW_SPRITES; k++) {
|
for (k = 0u; k < ST_HW_SPRITES; k++) {
|
||||||
gRender.backup[k].bytes = gRender.backupMem[k];
|
gRender.backup[k].bytes = gRender.backupMem[k];
|
||||||
}
|
}
|
||||||
|
|
@ -551,6 +731,24 @@ static bool loadPrecompiledCels(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// "LOADING..." centred on an otherwise black stage. The charset is the
|
||||||
|
// game's own, indexed by ASCII (the C64 font's letters sit at their
|
||||||
|
// ASCII codes), so this needs no font surface and no simulation state.
|
||||||
|
void stRenderLoading(jlSurfaceT *stage) {
|
||||||
|
static const char kLoading[] = "LOADING...";
|
||||||
|
const uint8_t *charset = stC64Charset();
|
||||||
|
uint8_t col = (uint8_t)((ST_SCREEN_COLS - (sizeof(kLoading) - 1u)) / 2u);
|
||||||
|
uint8_t k;
|
||||||
|
|
||||||
|
jlSurfaceClear(stage, 0u);
|
||||||
|
for (k = 0u; k < (uint8_t)(sizeof(kLoading) - 1u); k++) {
|
||||||
|
jlTilePasteGlyph(stage, (uint8_t)(col + k), ST_LOADING_ROW,
|
||||||
|
&charset[(uint8_t)kLoading[k] * 8u], ST_LOADING_COLOR, 0u);
|
||||||
|
}
|
||||||
|
jlStagePresent();
|
||||||
|
}
|
||||||
|
|
||||||
void stRenderPrewarm(jlSurfaceT *stage, StSimT *sim) {
|
void stRenderPrewarm(jlSurfaceT *stage, StSimT *sim) {
|
||||||
static const uint8_t kCabPtrs[] = { 0xC0, 0xC1, 0xDC, 0xDD, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8 };
|
static const uint8_t kCabPtrs[] = { 0xC0, 0xC1, 0xDC, 0xDD, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8 };
|
||||||
static const uint8_t kPassPtrs[] = { 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xD9 };
|
static const uint8_t kPassPtrs[] = { 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xD9 };
|
||||||
|
|
@ -611,11 +809,36 @@ void stRenderPrewarm(jlSurfaceT *stage, StSimT *sim) {
|
||||||
void stRenderSceneChanged(StSimT *sim) {
|
void stRenderSceneChanged(StSimT *sim) {
|
||||||
uint8_t k;
|
uint8_t k;
|
||||||
|
|
||||||
|
if (gRender.logoMode) {
|
||||||
|
// The logo's reserved slots go back to being C64 colours.
|
||||||
|
gRender.logoMode = false;
|
||||||
|
gRender.logoTileOk = false;
|
||||||
|
jlPaletteSet(gRender.stage, 0u, kC64Palette);
|
||||||
|
}
|
||||||
for (k = 0u; k < ST_HW_SPRITES; k++) {
|
for (k = 0u; k < ST_HW_SPRITES; k++) {
|
||||||
gRender.slot[k].drawn = false;
|
gRender.slot[k].drawn = false;
|
||||||
}
|
}
|
||||||
stSimDirtyAll(sim);
|
stSimDirtyAll(sim);
|
||||||
memset(sim->charDirty, 1, ST_CHARSET_CHARS);
|
sim->charDirtyAll = true;
|
||||||
|
sim->charDirtyCount = 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Turn on the title's palette-animated logo. Call it after
|
||||||
|
// stRenderSceneChanged (which turns it back off) on entry to the title
|
||||||
|
// screen; from then on a flip-book step and a colour cycle both cost
|
||||||
|
// one palette write and no cell repaint at all.
|
||||||
|
void stRenderTitleLogo(StSimT *sim) {
|
||||||
|
uint8_t frame;
|
||||||
|
|
||||||
|
if (!logoBuildTile(sim)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frame = logoFrameIndex(sim);
|
||||||
|
gRender.logoMode = true;
|
||||||
|
gRender.logoTileOk = (frame < ST_LOGO_FRAMES);
|
||||||
|
logoPaletteApply(sim, gRender.logoTileOk ? frame : 0u);
|
||||||
|
stSimDirtyAll(sim);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
63
stSim.c
|
|
@ -22,6 +22,7 @@ static const uint8_t kPumpFreq[5] = { 0x04, 0x05, 0x07, 0x0A, 0x10 };
|
||||||
static void applyVelocityX(StSimT *sim);
|
static void applyVelocityX(StSimT *sim);
|
||||||
static void applyVelocityY(StSimT *sim);
|
static void applyVelocityY(StSimT *sim);
|
||||||
static uint32_t backgroundRowMask(const StSimT *sim, int16_t px, int16_t py);
|
static uint32_t backgroundRowMask(const StSimT *sim, int16_t px, int16_t py);
|
||||||
|
static int16_t cellOfPixel(int16_t px);
|
||||||
static void bitScrollHatch(StSimT *sim);
|
static void bitScrollHatch(StSimT *sim);
|
||||||
static void collisionDispatch(StSimT *sim);
|
static void collisionDispatch(StSimT *sim);
|
||||||
static void collisionPhase1(StSimT *sim);
|
static void collisionPhase1(StSimT *sim);
|
||||||
|
|
@ -99,8 +100,8 @@ static uint32_t backgroundRowMask(const StSimT *sim, int16_t px, int16_t py) {
|
||||||
return 0u;
|
return 0u;
|
||||||
}
|
}
|
||||||
// Floor division so a negative px lands on the column to its left.
|
// Floor division so a negative px lands on the column to its left.
|
||||||
firstCol = (int16_t)((px - (px < 0 ? 7 : 0)) / 8);
|
firstCol = cellOfPixel(px);
|
||||||
shift = (uint8_t)(px - firstCol * 8);
|
shift = (uint8_t)(px - (int16_t)(firstCol << 3));
|
||||||
for (k = 0u; k < 4u; k++) {
|
for (k = 0u; k < 4u; k++) {
|
||||||
uint8_t byte = 0u;
|
uint8_t byte = 0u;
|
||||||
col = (int16_t)(firstCol + (int16_t)k);
|
col = (int16_t)(firstCol + (int16_t)k);
|
||||||
|
|
@ -113,6 +114,17 @@ static uint32_t backgroundRowMask(const StSimT *sim, int16_t px, int16_t py) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Which character column (or row) a pixel coordinate falls in: floor
|
||||||
|
// division by 8. A shift, not a divide -- the 65816 has no divider and
|
||||||
|
// the compiler's signed-division helper cost more than the collision
|
||||||
|
// test that calls it. The +256/-32 bias makes the shift exact for any
|
||||||
|
// coordinate down to -256, well past the furthest a sprite can hang off
|
||||||
|
// the left or top edge.
|
||||||
|
static int16_t cellOfPixel(int16_t px) {
|
||||||
|
return (int16_t)(((px + 256) >> 3) - 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// $63D0 -- rotate every row of the transporter hatch glyph one pixel
|
// $63D0 -- rotate every row of the transporter hatch glyph one pixel
|
||||||
// right: the animated energy field in the top-wall opening.
|
// right: the animated energy field in the top-wall opening.
|
||||||
static void bitScrollHatch(StSimT *sim) {
|
static void bitScrollHatch(StSimT *sim) {
|
||||||
|
|
@ -122,7 +134,7 @@ static void bitScrollHatch(StSimT *sim) {
|
||||||
uint8_t b = sim->charset[ST_CHAR_TRANSPORTER][row];
|
uint8_t b = sim->charset[ST_CHAR_TRANSPORTER][row];
|
||||||
sim->charset[ST_CHAR_TRANSPORTER][row] = (uint8_t)((b >> 1) | (b << 7));
|
sim->charset[ST_CHAR_TRANSPORTER][row] = (uint8_t)((b >> 1) | (b << 7));
|
||||||
}
|
}
|
||||||
sim->charDirty[ST_CHAR_TRANSPORTER] = 1u;
|
stSimMarkChar(sim, ST_CHAR_TRANSPORTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -264,6 +276,7 @@ static StTickResultE collisionPhase3(StSimT *sim) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// The C64 reads two VIC collision registers each frame: $D01F (a sprite
|
// The C64 reads two VIC collision registers each frame: $D01F (a sprite
|
||||||
// touched the background) and $D01E (two sprites touched). Only the cab
|
// touched the background) and $D01E (two sprites touched). Only the cab
|
||||||
// (sprite 0) can drive a gameplay outcome from them: the crash dispatch
|
// (sprite 0) can drive a gameplay outcome from them: the crash dispatch
|
||||||
|
|
@ -298,9 +311,9 @@ static void computeCollisions(StSimT *sim) {
|
||||||
// any non-space cell falls through to the exact test, so the result
|
// any non-space cell falls through to the exact test, so the result
|
||||||
// is unchanged (space is the only zero-pixel glyph the test can hit).
|
// is unchanged (space is the only zero-pixel glyph the test can hit).
|
||||||
{
|
{
|
||||||
int16_t firstCol = (int16_t)((cabX - (cabX < 0 ? 7 : 0)) / 8);
|
int16_t firstCol = cellOfPixel(cabX);
|
||||||
int16_t topRow = (cabY < 0) ? 0 : (int16_t)(cabY / 8);
|
int16_t topRow = (cabY < 0) ? 0 : cellOfPixel(cabY);
|
||||||
int16_t botRow = (int16_t)((cabY + (int16_t)ST_SPRITE_H - 1) / 8);
|
int16_t botRow = cellOfPixel((int16_t)(cabY + (int16_t)ST_SPRITE_H - 1));
|
||||||
bool solid = false;
|
bool solid = false;
|
||||||
int16_t rr;
|
int16_t rr;
|
||||||
int16_t cc;
|
int16_t cc;
|
||||||
|
|
@ -928,6 +941,8 @@ static void taxiSpriteCelSelect(StSimT *sim) {
|
||||||
|
|
||||||
|
|
||||||
// $4345 -- a HUD glyph's numeric value: blanks and the zero glyph are 0.
|
// $4345 -- a HUD glyph's numeric value: blanks and the zero glyph are 0.
|
||||||
|
|
||||||
|
|
||||||
static uint8_t validateDigit(uint8_t ch) {
|
static uint8_t validateDigit(uint8_t ch) {
|
||||||
uint8_t v;
|
uint8_t v;
|
||||||
|
|
||||||
|
|
@ -1073,7 +1088,8 @@ void stSimEnterLevel(StSimT *sim, const StLevelT *level) {
|
||||||
memcpy(sim->color, level->color, ST_SCREEN_CELLS);
|
memcpy(sim->color, level->color, ST_SCREEN_CELLS);
|
||||||
stSimDirtyAll(sim);
|
stSimDirtyAll(sim);
|
||||||
memcpy(sim->charset, stC64Charset(), sizeof(sim->charset));
|
memcpy(sim->charset, stC64Charset(), sizeof(sim->charset));
|
||||||
memset(sim->charDirty, 1, ST_CHARSET_CHARS);
|
sim->charDirtyAll = true;
|
||||||
|
sim->charDirtyCount = 0u;
|
||||||
// Level sprites live in the one level buffer, so the same bitmap
|
// Level sprites live in the one level buffer, so the same bitmap
|
||||||
// address now holds other pixels: rebuild every collision mask.
|
// address now holds other pixels: rebuild every collision mask.
|
||||||
memset(sim->collMaskBm, 0, sizeof(sim->collMaskBm));
|
memset(sim->collMaskBm, 0, sizeof(sim->collMaskBm));
|
||||||
|
|
@ -1147,9 +1163,14 @@ void stSimNewGame(StSimT *sim, uint8_t playerCount, bool demo) {
|
||||||
uint8_t keepWalk = sim->walkParity;
|
uint8_t keepWalk = sim->walkParity;
|
||||||
|
|
||||||
// $5EC4 clears the game state but the playback buffer, the flame
|
// $5EC4 clears the game state but the playback buffer, the flame
|
||||||
// parity, the wave index and the walk parity all survive.
|
// parity, the wave index and the walk parity all survive. So does
|
||||||
|
// the displayed picture -- screen RAM, colour RAM and the charset
|
||||||
|
// are not game state, and the attract demo's "GET READY" banner is
|
||||||
|
// drawn over the still-live title screen -- so the clear starts at
|
||||||
|
// ST_SIM_CLEAR_FROM. Every level entry rebinds the image anyway
|
||||||
|
// (stSimEnterLevel), so nothing downstream sees the difference.
|
||||||
memcpy(keepBuf, sim->demoBuf, sizeof(keepBuf));
|
memcpy(keepBuf, sim->demoBuf, sizeof(keepBuf));
|
||||||
memset(sim, 0, sizeof(*sim));
|
memset((uint8_t *)sim + ST_SIM_CLEAR_FROM, 0, sizeof(*sim) - ST_SIM_CLEAR_FROM);
|
||||||
memcpy(sim->demoBuf, keepBuf, sizeof(keepBuf));
|
memcpy(sim->demoBuf, keepBuf, sizeof(keepBuf));
|
||||||
sim->flameParity = keepParity;
|
sim->flameParity = keepParity;
|
||||||
sim->waveIdx = keepWave;
|
sim->waveIdx = keepWave;
|
||||||
|
|
@ -1173,6 +1194,30 @@ void stSimNewGame(StSimT *sim, uint8_t playerCount, bool demo) {
|
||||||
|
|
||||||
|
|
||||||
// Everything must be repainted.
|
// Everything must be repainted.
|
||||||
|
// A glyph's bits changed: the renderer has to repaint every cell showing
|
||||||
|
// that character. The list is short (one or two per frame), so a linear
|
||||||
|
// scan for duplicates is cheaper than any index; an overflow just says
|
||||||
|
// "all of them" and costs one full repaint.
|
||||||
|
void stSimMarkChar(StSimT *sim, uint8_t ch) {
|
||||||
|
uint8_t k;
|
||||||
|
|
||||||
|
if (sim->charDirtyAll) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (k = 0u; k < sim->charDirtyCount; k++) {
|
||||||
|
if (sim->charDirtyList[k] == ch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sim->charDirtyCount == ST_CHAR_DIRTY_MAX) {
|
||||||
|
sim->charDirtyAll = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sim->charDirtyList[sim->charDirtyCount] = ch;
|
||||||
|
sim->charDirtyCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void stSimDirtyAll(StSimT *sim) {
|
void stSimDirtyAll(StSimT *sim) {
|
||||||
memset(sim->cellDirty, 1, ST_SCREEN_CELLS);
|
memset(sim->cellDirty, 1, ST_SCREEN_CELLS);
|
||||||
sim->dirtyCount = 0u;
|
sim->dirtyCount = 0u;
|
||||||
|
|
|
||||||
54
stSim.h
|
|
@ -20,6 +20,7 @@
|
||||||
#define ST_SIM_H
|
#define ST_SIM_H
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
|
@ -30,7 +31,11 @@
|
||||||
#define ST_SCREEN_COLS 40u
|
#define ST_SCREEN_COLS 40u
|
||||||
#define ST_SCREEN_ROWS 25u
|
#define ST_SCREEN_ROWS 25u
|
||||||
#define ST_SCREEN_CELLS (ST_SCREEN_COLS * ST_SCREEN_ROWS)
|
#define ST_SCREEN_CELLS (ST_SCREEN_COLS * ST_SCREEN_ROWS)
|
||||||
#define ST_CELL(row, col) ((uint16_t)((row) * ST_SCREEN_COLS + (col)))
|
// row * 40 + col. Written as shifts (40 == 32 + 8) because the 65816 has
|
||||||
|
// no multiply: with a variable row the * form called the compiler's
|
||||||
|
// software multiply helper, and the collision pre-check does that a
|
||||||
|
// dozen times a tick. Constant rows still fold to a constant.
|
||||||
|
#define ST_CELL(row, col) ((uint16_t)((((uint16_t)(row)) << 5) + (((uint16_t)(row)) << 3) + (uint16_t)(col)))
|
||||||
|
|
||||||
// VIC sprite coordinate origins: the visible picture starts at
|
// VIC sprite coordinate origins: the visible picture starts at
|
||||||
// sprite-X 24 and sprite-Y 50.
|
// sprite-X 24 and sprite-Y 50.
|
||||||
|
|
@ -68,6 +73,22 @@
|
||||||
#define ST_CHAR_CAB_ICON 0xC8u
|
#define ST_CHAR_CAB_ICON 0xC8u
|
||||||
#define ST_CHAR_MENU_SENTINEL 0x7Bu // $07A6 == $7B: first fuel cell half = out of fuel
|
#define ST_CHAR_MENU_SENTINEL 0x7Bu // $07A6 == $7B: first fuel cell half = out of fuel
|
||||||
|
|
||||||
|
// Title logo ($4827): the logo is one character repeated over rows
|
||||||
|
// 1..11, and the flip-book animates it by copying one of four frame
|
||||||
|
// glyphs over it. The glyph stays the simulation's own record of which
|
||||||
|
// frame is showing; the renderer draws all four frames as one tile in
|
||||||
|
// reserved palette slots and animates them by rewriting those entries,
|
||||||
|
// so a flip and a colour cycle both cost no repaint at all.
|
||||||
|
#define ST_LOGO_CHAR 0x84u
|
||||||
|
#define ST_LOGO_FRAME_FIRST 0x85u // $85..$88 = frames 0..3
|
||||||
|
#define ST_LOGO_FRAMES 4u
|
||||||
|
// Offset stSimNewGame clears from: the block above it is the displayed
|
||||||
|
// picture and survives a new game (see StSimT).
|
||||||
|
#define ST_SIM_CLEAR_FROM offsetof(StSimT, level)
|
||||||
|
// Distinct characters whose glyph can change in one frame before the
|
||||||
|
// renderer gives up and repaints everything.
|
||||||
|
#define ST_CHAR_DIRTY_MAX 8u
|
||||||
|
|
||||||
// Level image as loaded from a .dat (STL4). Kept in the C64's units.
|
// Level image as loaded from a .dat (STL4). Kept in the C64's units.
|
||||||
#define ST_MAX_PADS 10u
|
#define ST_MAX_PADS 10u
|
||||||
#define ST_MAX_LEVEL_SPRITES 16u
|
#define ST_MAX_LEVEL_SPRITES 16u
|
||||||
|
|
@ -166,9 +187,13 @@ typedef enum {
|
||||||
} StTickResultE;
|
} StTickResultE;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
// ---- level image (mutable copies: hooks edit the pad table) ----
|
// ---- the displayed picture: screen RAM, colour RAM, the charset
|
||||||
const StLevelT *level;
|
// and the renderer's dirty bookkeeping for them. stSimNewGame
|
||||||
StPadT pads[ST_MAX_PADS]; // $7D0A working copy
|
// ($5EC4) does NOT clear this block -- the C64 resets its game
|
||||||
|
// variables with the previous picture still on screen, which is how
|
||||||
|
// the attract demo's "GET READY" banner comes up over the live
|
||||||
|
// title. Everything from `level` down IS cleared; keep new fields on
|
||||||
|
// the right side of that line ----
|
||||||
uint8_t screen[ST_SCREEN_CELLS]; // $0400 screen RAM
|
uint8_t screen[ST_SCREEN_CELLS]; // $0400 screen RAM
|
||||||
uint8_t color[ST_SCREEN_CELLS]; // $D800 colour RAM
|
uint8_t color[ST_SCREEN_CELLS]; // $D800 colour RAM
|
||||||
uint8_t cellDirty[ST_SCREEN_CELLS]; // renderer clears
|
uint8_t cellDirty[ST_SCREEN_CELLS]; // renderer clears
|
||||||
|
|
@ -176,9 +201,26 @@ typedef struct {
|
||||||
uint16_t dirtyCount;
|
uint16_t dirtyCount;
|
||||||
bool dirtyAll; // list overflowed / whole screen
|
bool dirtyAll; // list overflowed / whole screen
|
||||||
uint8_t charset[ST_CHARSET_CHARS][8]; // $2800 working copy
|
uint8_t charset[ST_CHARSET_CHARS][8]; // $2800 working copy
|
||||||
uint8_t charDirty[ST_CHARSET_CHARS]; // renderer clears
|
// Characters whose glyph bits the game has edited since the last
|
||||||
|
// frame -- the transporter hatch, the title logo flip, the laser
|
||||||
|
// beams. A short list, not a 256-entry flag array: the renderer had
|
||||||
|
// to scan all 256 every single frame to find the one or two that
|
||||||
|
// ever change. Overflow (or a scene load) sets charDirtyAll.
|
||||||
|
uint8_t charDirtyList[ST_CHAR_DIRTY_MAX];
|
||||||
|
uint8_t charDirtyCount;
|
||||||
|
bool charDirtyAll;
|
||||||
uint8_t borderColor; // $D020
|
uint8_t borderColor; // $D020
|
||||||
uint8_t bgColor; // $D021
|
uint8_t bgColor; // $D021
|
||||||
|
// The colour the logo band's cells would carry ($4861 writes it into
|
||||||
|
// 407 cells of colour RAM); part of the picture, so it survives the
|
||||||
|
// same way colour RAM does.
|
||||||
|
uint8_t logoColor;
|
||||||
|
|
||||||
|
// ---- cleared by stSimNewGame from here down ----
|
||||||
|
|
||||||
|
// ---- level image (mutable copies: hooks edit the pad table) ----
|
||||||
|
const StLevelT *level;
|
||||||
|
StPadT pads[ST_MAX_PADS]; // $7D0A working copy
|
||||||
|
|
||||||
// ---- input ----
|
// ---- input ----
|
||||||
uint8_t inputMask; // $7169 EOR $FF of $DC00: 1 = pressed
|
uint8_t inputMask; // $7169 EOR $FF of $DC00: 1 = pressed
|
||||||
|
|
@ -315,6 +357,8 @@ bool stSimAdvancePlayer(StSimT *sim);
|
||||||
void stSimArchiveHud(StSimT *sim);
|
void stSimArchiveHud(StSimT *sim);
|
||||||
// Mark every cell dirty (scene change).
|
// Mark every cell dirty (scene change).
|
||||||
void stSimDirtyAll(StSimT *sim);
|
void stSimDirtyAll(StSimT *sim);
|
||||||
|
// A glyph's bits changed: every cell showing it has to be repainted.
|
||||||
|
void stSimMarkChar(StSimT *sim, uint8_t ch);
|
||||||
// Text and HUD primitives (screen RAM writes).
|
// Text and HUD primitives (screen RAM writes).
|
||||||
void stSimDrawText(StSimT *sim, uint8_t col, uint8_t row, const uint8_t *text, uint8_t color);
|
void stSimDrawText(StSimT *sim, uint8_t col, uint8_t row, const uint8_t *text, uint8_t color);
|
||||||
void stSimPutChar(StSimT *sim, uint8_t col, uint8_t row, uint8_t ch);
|
void stSimPutChar(StSimT *sim, uint8_t col, uint8_t row, uint8_t ch);
|
||||||
|
|
|
||||||
25
stTitle.c
|
|
@ -31,7 +31,6 @@ static const int8_t kIntroCabDy[4] = { -2, 1, -1, 1 };
|
||||||
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 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 };
|
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_CAB_LIFT_ROW 0x14u
|
||||||
#define ST_TITLE_SPARKLE_TICKS 0x5Au
|
#define ST_TITLE_SPARKLE_TICKS 0x5Au
|
||||||
#define ST_TITLE_HOVER_COL 0x28u
|
#define ST_TITLE_HOVER_COL 0x28u
|
||||||
|
|
@ -65,21 +64,19 @@ static void addToSpriteX(StSimT *sim, uint8_t idx, uint8_t delta) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// $4861 -- next logo colour over rows 1..11, columns 1..37, and onto
|
// $4861 -- the next logo colour, onto sprites 3..7 and onto the logo
|
||||||
// sprites 3..7.
|
// 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) {
|
static void logoColorCycle(StSimT *sim) {
|
||||||
uint8_t color;
|
uint8_t color;
|
||||||
uint8_t row;
|
|
||||||
uint8_t col;
|
|
||||||
uint8_t k;
|
uint8_t k;
|
||||||
|
|
||||||
sim->logoColorIdx = (uint8_t)((sim->logoColorIdx + 1u) & 7u);
|
sim->logoColorIdx = (uint8_t)((sim->logoColorIdx + 1u) & 7u);
|
||||||
color = kLogoColor[sim->logoColorIdx];
|
color = kLogoColor[sim->logoColorIdx];
|
||||||
for (row = 1u; row <= 11u; row++) {
|
sim->logoColor = color;
|
||||||
for (col = 1u; col <= 37u; col++) {
|
|
||||||
stSimPutColor(sim, col, row, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (k = 3u; k < ST_HW_SPRITES; k++) {
|
for (k = 3u; k < ST_HW_SPRITES; k++) {
|
||||||
sim->spr[k].color = color;
|
sim->spr[k].color = color;
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +91,7 @@ static void logoFlip(StSimT *sim) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
memcpy(sim->charset[ST_LOGO_CHAR], sim->charset[ST_LOGO_CHAR + kLogoFlip[sim->logoCycleAux]], 8u);
|
memcpy(sim->charset[ST_LOGO_CHAR], sim->charset[ST_LOGO_CHAR + kLogoFlip[sim->logoCycleAux]], 8u);
|
||||||
sim->charDirty[ST_LOGO_CHAR] = 1u;
|
stSimMarkChar(sim, ST_LOGO_CHAR);
|
||||||
sim->logoCycleAux++;
|
sim->logoCycleAux++;
|
||||||
if (sim->logoCycleAux == 6u) {
|
if (sim->logoCycleAux == 6u) {
|
||||||
sim->logoCycleAux = 0u;
|
sim->logoCycleAux = 0u;
|
||||||
|
|
@ -174,7 +171,8 @@ void stIntroEnter(StSimT *sim, const StLevelT *level) {
|
||||||
memset(sim->color, 0, ST_SCREEN_CELLS);
|
memset(sim->color, 0, ST_SCREEN_CELLS);
|
||||||
stSimDirtyAll(sim);
|
stSimDirtyAll(sim);
|
||||||
memcpy(sim->charset, stC64Charset(), sizeof(sim->charset));
|
memcpy(sim->charset, stC64Charset(), sizeof(sim->charset));
|
||||||
memset(sim->charDirty, 1, ST_CHARSET_CHARS);
|
sim->charDirtyAll = true;
|
||||||
|
sim->charDirtyCount = 0u;
|
||||||
stSimDrawText(sim, 10u, 12u, level->name, 3u);
|
stSimDrawText(sim, 10u, 12u, level->name, 3u);
|
||||||
if (sim->demoMode != 0u && sim->postMortem == 0u) {
|
if (sim->demoMode != 0u && sim->postMortem == 0u) {
|
||||||
stSimDrawText(sim, 7u, 3u, kTextDemoExit, 5u);
|
stSimDrawText(sim, 7u, 3u, kTextDemoExit, 5u);
|
||||||
|
|
@ -240,7 +238,8 @@ void stTitleEnter(StSimT *sim, const StLevelT *title) {
|
||||||
memcpy(sim->color, title->color, ST_SCREEN_CELLS);
|
memcpy(sim->color, title->color, ST_SCREEN_CELLS);
|
||||||
stSimDirtyAll(sim);
|
stSimDirtyAll(sim);
|
||||||
memcpy(sim->charset, stC64Charset(), sizeof(sim->charset));
|
memcpy(sim->charset, stC64Charset(), sizeof(sim->charset));
|
||||||
memset(sim->charDirty, 1, ST_CHARSET_CHARS);
|
sim->charDirtyAll = true;
|
||||||
|
sim->charDirtyCount = 0u;
|
||||||
logoColorCycle(sim);
|
logoColorCycle(sim);
|
||||||
for (k = 0u; k < ST_HW_SPRITES; k++) {
|
for (k = 0u; k < ST_HW_SPRITES; k++) {
|
||||||
sim->spr[k].enable = 0u;
|
sim->spr[k].enable = 0u;
|
||||||
|
|
|
||||||