// Space Taxi -- level loader. // // Reads a level .dat file produced by `tools/spacetaxi/mkLevel.py` // (see assets/levels/format.md for the byte layout). // // A level file is small (~2-3 KB raw, plus a name + per-pad config), // loaded once per scene change. Read fully into RAM; tilemap + colormap // stay inside the StLevelT struct for the life of the game state. #include #include #include #include "spacetaxi.h" JOEYLIB_SEGMENT("STAXI") // STL2 adds the per-level physics templates (xAccel/yAccel/xGrav/yGrav) // and the full VIC color block ($7D00-$7D08). Drops the patience byte // from each fare entry (Space Taxi proper has no patience timeout). #define ST_LEVEL_MAGIC0 'S' #define ST_LEVEL_MAGIC1 'T' #define ST_LEVEL_MAGIC2 'L' #define ST_LEVEL_MAGIC3 '2' static bool readByte(FILE *fp, uint8_t *out) { int c = fgetc(fp); if (c == EOF) { return false; } *out = (uint8_t)c; return true; } static bool readBytes(FILE *fp, void *dst, size_t n) { return fread(dst, 1, n, fp) == n; } bool stLevelLoad(StLevelT *out, const char *path) { FILE *fp; uint8_t hdr[4]; uint8_t nameLen; uint8_t i; size_t cells; memset(out, 0, sizeof(*out)); fp = fopen(path, "rb"); if (fp == NULL) { return false; } if (!readBytes(fp, hdr, 4) || hdr[0] != ST_LEVEL_MAGIC0 || hdr[1] != ST_LEVEL_MAGIC1 || hdr[2] != ST_LEVEL_MAGIC2 || hdr[3] != ST_LEVEL_MAGIC3) { fclose(fp); return false; } if (!readByte(fp, &nameLen) || nameLen >= sizeof(out->name)) { fclose(fp); return false; } if (!readBytes(fp, out->name, nameLen)) { fclose(fp); return false; } out->name[nameLen] = '\0'; if (!readByte(fp, &out->tileBankId) || !readByte(fp, &out->musicId) || !readByte(fp, &out->bgColor) || !readByte(fp, &out->borderColor) || !readByte(fp, &out->taxiSpawnTileX) || !readByte(fp, &out->taxiSpawnTileY)) { fclose(fp); return false; } { uint8_t xGravByte; uint8_t yGravByte; if (!readByte(fp, &out->xAccel) || !readByte(fp, &out->yAccel) || !readByte(fp, &xGravByte) || !readByte(fp, &yGravByte)) { fclose(fp); return false; } out->xGrav = (int8_t)xGravByte; out->yGrav = (int8_t)yGravByte; } if (!readByte(fp, &out->bgColor1) || !readByte(fp, &out->bgColor2) || !readByte(fp, &out->bgColor3) || !readByte(fp, &out->spriteMc0) || !readByte(fp, &out->spriteMc1) || !readByte(fp, &out->sprite0Color) || !readByte(fp, &out->sprite1Color)) { fclose(fp); return false; } if (!readByte(fp, &out->padCount) || out->padCount > ST_MAX_PADS) { fclose(fp); return false; } for (i = 0u; i < out->padCount; i++) { if (!readByte(fp, &out->pads[i].letter) || !readByte(fp, &out->pads[i].tileX) || !readByte(fp, &out->pads[i].tileY) || !readByte(fp, &out->pads[i].tileW)) { fclose(fp); return false; } } if (!readByte(fp, &out->fareCount) || out->fareCount > ST_MAX_FARES) { fclose(fp); return false; } for (i = 0u; i < out->fareCount; i++) { if (!readByte(fp, &out->fares[i].spawnPad) || !readByte(fp, &out->fares[i].destPad)) { fclose(fp); return false; } } cells = (size_t)ST_TILEMAP_W * (size_t)ST_PLAYFIELD_ROWS; if (!readBytes(fp, out->tilemap, cells) || !readBytes(fp, out->colormap, cells)) { fclose(fp); return false; } fclose(fp); return true; }