578 lines
19 KiB
C
578 lines
19 KiB
C
// save.game and restore.game.
|
|
//
|
|
// A save holds the interpreter state that is not rebuilt from the game
|
|
// files: variables, flags, strings, the inventory, the screen objects,
|
|
// the screen layout and the logics in memory. The picture is not saved;
|
|
// as in Sierra's interpreter, the commands that built the current room's
|
|
// picture (draw.pic, overlay.pic, add.to.pic, and the loads) are kept in
|
|
// a script, which is saved and played back on restore. new.room empties
|
|
// the script; f7 stops commands being added.
|
|
//
|
|
// The files are JoeyLib save files (SAVES/), AGISAVE1..AGISAVE9, in
|
|
// JoeyAGI's own format: the state as the machine holds it, so a save made
|
|
// on one kind of machine is for that kind only.
|
|
//
|
|
// set.simple names the saves itself, as Sierra's interpreter does: save
|
|
// goes without asking to the slot with that description, else to the
|
|
// first empty one (with every slot taken by others, a pointer picks one to
|
|
// overwrite), and restore lists the saves by description for a pointer.
|
|
// The pointer starts on the newest save (each is numbered one above the
|
|
// highest on the disk) and wraps at either end of the list.
|
|
|
|
#include "agi.h"
|
|
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "joey/core.h"
|
|
#include "joey/draw.h"
|
|
#include "joey/file.h"
|
|
|
|
|
|
#define SAVE_MAGIC_0 'J'
|
|
#define SAVE_MAGIC_1 'A'
|
|
#define SAVE_MAGIC_2 'G'
|
|
#define SAVE_VERSION 2u
|
|
#define SAVE_SLOTS 9u
|
|
#define SAVE_DESC_MAX 30u
|
|
#define SAVE_NAME_MAX 12u
|
|
// The pointer lists: their longest header, the box's width (as Sierra's,
|
|
// whatever the text), and the pointer drawn over the first column of an
|
|
// entry (" - name").
|
|
#define POINTER_HEADER_LINES 4u
|
|
#define POINTER_BOX_COLS 34
|
|
#define POINTER_ON ">"
|
|
#define POINTER_OFF " "
|
|
// The slot window is as wide as the screen allows with its frame.
|
|
#define PICK_WIDTH (AGI_TEXT_COLS - 4u)
|
|
|
|
|
|
// One saved object: AgiObjectT without its pointers and blit state.
|
|
typedef struct {
|
|
uint8_t viewId;
|
|
uint8_t loop;
|
|
uint8_t cel;
|
|
uint8_t priority;
|
|
int16_t x;
|
|
int16_t y;
|
|
uint16_t flags;
|
|
uint8_t direction;
|
|
uint8_t stepSize;
|
|
uint8_t stepTime;
|
|
uint8_t stepCount;
|
|
uint8_t cycleTime;
|
|
uint8_t cycleCount;
|
|
uint8_t cycleMode;
|
|
uint8_t motion;
|
|
uint8_t parm[AGI_OBJ_PARMS];
|
|
} SavedObjectT;
|
|
|
|
typedef struct {
|
|
char magic[3];
|
|
uint8_t version;
|
|
uint32_t sequence;
|
|
char description[SAVE_DESC_MAX + 1u];
|
|
uint8_t vars[AGI_NUM_VARS];
|
|
uint8_t flags[AGI_NUM_FLAGS];
|
|
char strings[AGI_MAX_STRINGS][AGI_STRING_LEN + 1u];
|
|
uint8_t itemRoom[AGI_MAX_ITEMS];
|
|
AgiKeyMapT keyMaps[AGI_MAX_KEYMAPS];
|
|
uint8_t keyMapCount;
|
|
SavedObjectT objects[AGI_MAX_OBJECTS];
|
|
bool logicLoaded[AGI_MAX_RESOURCES];
|
|
uint16_t logicScanStart[AGI_MAX_RESOURCES];
|
|
bool menuEnabled[AGI_MAX_MENU_ITEMS];
|
|
uint8_t horizon;
|
|
bool blockActive;
|
|
uint8_t blockX1;
|
|
uint8_t blockY1;
|
|
uint8_t blockX2;
|
|
uint8_t blockY2;
|
|
bool playerControl;
|
|
uint8_t picRow;
|
|
uint8_t inputRow;
|
|
uint8_t statusRow;
|
|
bool statusOn;
|
|
bool inputOn;
|
|
uint8_t textFg;
|
|
uint8_t textBg;
|
|
char cursorChar;
|
|
bool holdKey;
|
|
bool simpleSaveOn;
|
|
char simpleSaveDesc[AGI_STRING_LEN + 1u];
|
|
bool priorityTableSet;
|
|
uint8_t priorityTable[AGI_HEIGHT];
|
|
uint8_t scriptCount;
|
|
AgiScriptEntryT script[AGI_SCRIPT_MAX];
|
|
} SaveFileT;
|
|
|
|
|
|
// ----- Prototypes -----
|
|
|
|
static bool fileBuffer(void);
|
|
static bool isSimpleDesc(const char *desc);
|
|
static uint8_t pickSlot(const char *title, bool mustExist);
|
|
static uint8_t pointerPick(const char *header, bool offerNamed);
|
|
static uint32_t newestSequence(void);
|
|
static bool readDescription(uint8_t slot, char *out, uint32_t *sequence);
|
|
static void simpleDesc(char *out);
|
|
static uint8_t simpleSlot(void);
|
|
static void slotName(uint8_t slot, char *out);
|
|
|
|
|
|
// ----- Strings -----
|
|
|
|
// set.simple's dialogs, worded as Sierra's.
|
|
static const char kSimpleRestore[] = "Use the arrow keys to move\n the pointer to your name.\nThen press ENTER\n";
|
|
static const char kSimpleFull[] = " Sorry, this disk is full.\nPosition pointer and press ENTER\n to overwrite a saved game\nor press ESC to cancel.\n";
|
|
|
|
|
|
// ----- Module state -----
|
|
|
|
static SaveFileT *gFile;
|
|
|
|
|
|
// ----- Internal helpers (alphabetical) -----
|
|
|
|
// The save file's image, allocated on first use (it is too big for the
|
|
// IIgs's bank 0).
|
|
static bool fileBuffer(void) {
|
|
if (gFile == NULL) {
|
|
gFile = (SaveFileT *)jlAlloc(sizeof(SaveFileT));
|
|
}
|
|
return gFile != NULL;
|
|
}
|
|
|
|
|
|
// A save description that is set.simple's.
|
|
static bool isSimpleDesc(const char *desc) {
|
|
return strncmp(desc, gAgi->simpleSaveDesc, SAVE_DESC_MAX) == 0;
|
|
}
|
|
|
|
|
|
static uint8_t pickSlot(const char *title, bool mustExist) {
|
|
char text[(SAVE_SLOTS + 3u) * (AGI_TEXT_COLS + 1u)];
|
|
char desc[SAVE_DESC_MAX + 1u];
|
|
uint8_t slot;
|
|
uint16_t key;
|
|
size_t n;
|
|
|
|
// A window listing the slots; a digit picks one.
|
|
n = (size_t)snprintf(text, sizeof(text), "%s\n\n", title);
|
|
for (slot = 1u; slot <= SAVE_SLOTS && n < sizeof(text); slot++) {
|
|
if (!readDescription(slot, desc, NULL)) {
|
|
strcpy(desc, "-");
|
|
}
|
|
n += (size_t)snprintf(text + n, sizeof(text) - n, "%u. %s\n", (unsigned)slot, desc);
|
|
}
|
|
if (n < sizeof(text)) {
|
|
(void)snprintf(text + n, sizeof(text) - n, "\nPress 1-9, or ESC to cancel.");
|
|
}
|
|
textPrint(text, -1, -1, PICK_WIDTH);
|
|
for (;;) {
|
|
key = inputWaitKey();
|
|
if (key == AGI_KEY_ESCAPE) {
|
|
slot = 0u;
|
|
break;
|
|
}
|
|
if (key >= (uint16_t)'1' && key <= (uint16_t)('0' + SAVE_SLOTS)) {
|
|
slot = (uint8_t)(key - '0');
|
|
if (!mustExist || readDescription(slot, desc, NULL)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
textCloseWindow();
|
|
return slot;
|
|
}
|
|
|
|
|
|
// The highest sequence number of the saves on the disk.
|
|
static uint32_t newestSequence(void) {
|
|
char desc[SAVE_DESC_MAX + 1u];
|
|
uint32_t newest;
|
|
uint32_t sequence;
|
|
uint8_t slot;
|
|
|
|
newest = 0u;
|
|
for (slot = 1u; slot <= SAVE_SLOTS; slot++) {
|
|
if (readDescription(slot, desc, &sequence) && sequence > newest) {
|
|
newest = sequence;
|
|
}
|
|
}
|
|
return newest;
|
|
}
|
|
|
|
|
|
// The saves listed under a header, one chosen with a pointer (set.simple's
|
|
// dialogs), starting on the newest. With offerNamed, set.simple's description is listed too when
|
|
// no save has it, as slot 0: Sierra's Mother Goose ships a save by that
|
|
// name ("Start New Character") that starts the game afresh, which
|
|
// JoeyAGI, keeping its own save files, does by restoring nothing. The
|
|
// slot, or 0 for that entry or ESC.
|
|
static uint8_t pointerPick(const char *header, bool offerNamed) {
|
|
char text[(SAVE_SLOTS + POINTER_HEADER_LINES + 3u) * (AGI_TEXT_COLS + 1u)];
|
|
char desc[SAVE_DESC_MAX + 1u];
|
|
uint8_t slots[SAVE_SLOTS + 1u];
|
|
uint8_t count;
|
|
uint8_t current;
|
|
uint8_t first;
|
|
uint8_t slot;
|
|
uint16_t key;
|
|
uint32_t sequence;
|
|
uint32_t newest;
|
|
size_t n;
|
|
bool named;
|
|
const char *c;
|
|
|
|
// The entries start a line below the header's last.
|
|
first = 1u;
|
|
for (c = header; *c != '\0'; c++) {
|
|
if (*c == '\n') {
|
|
first++;
|
|
}
|
|
}
|
|
n = (size_t)snprintf(text, sizeof(text), "%s", header);
|
|
count = 0u;
|
|
current = 0u;
|
|
newest = 0u;
|
|
named = false;
|
|
for (slot = 1u; slot <= SAVE_SLOTS; slot++) {
|
|
if (readDescription(slot, desc, &sequence)) {
|
|
if (sequence > newest) {
|
|
newest = sequence;
|
|
current = count;
|
|
}
|
|
n += (size_t)snprintf(text + n, sizeof(text) - n, "\n - %s", desc);
|
|
slots[count++] = slot;
|
|
named = named || isSimpleDesc(desc);
|
|
}
|
|
}
|
|
if (offerNamed && !named) {
|
|
simpleDesc(desc);
|
|
n += (size_t)snprintf(text + n, sizeof(text) - n, "\n - %s", desc);
|
|
slots[count++] = 0u;
|
|
}
|
|
// A blank last line as wide as the box sets its size.
|
|
(void)snprintf(text + n, sizeof(text) - n, "\n%*s", POINTER_BOX_COLS, "");
|
|
if (count == 0u) {
|
|
return 0u;
|
|
}
|
|
textPrint(text, -1, -1, PICK_WIDTH);
|
|
for (;;) {
|
|
textWindowPut((uint8_t)(first + current), 0u, POINTER_ON);
|
|
key = inputWaitKey();
|
|
textWindowPut((uint8_t)(first + current), 0u, POINTER_OFF);
|
|
if (key == AGI_KEY_ESCAPE) {
|
|
slot = 0u;
|
|
break;
|
|
}
|
|
if (key == AGI_KEY_ENTER) {
|
|
slot = slots[current];
|
|
break;
|
|
}
|
|
if (key == AGI_KEY_UP) {
|
|
current = (uint8_t)((current + count - 1u) % count);
|
|
} else if (key == AGI_KEY_DOWN) {
|
|
current = (uint8_t)((current + 1u) % count);
|
|
}
|
|
}
|
|
textCloseWindow();
|
|
return slot;
|
|
}
|
|
|
|
|
|
// A slot's description and sequence number (unless NULL), read from the
|
|
// start of its file; false for no save of this version.
|
|
static bool readDescription(uint8_t slot, char *out, uint32_t *sequence) {
|
|
char name[SAVE_NAME_MAX];
|
|
FILE *fp;
|
|
char head[offsetof(SaveFileT, description) + SAVE_DESC_MAX + 1u];
|
|
|
|
slotName(slot, name);
|
|
fp = jlSaveOpen(name, "rb");
|
|
if (fp == NULL) {
|
|
return false;
|
|
}
|
|
if (fread(head, 1, sizeof(head), fp) != sizeof(head) || head[0] != SAVE_MAGIC_0 || head[1] != SAVE_MAGIC_1 || head[2] != SAVE_MAGIC_2 || (uint8_t)head[offsetof(SaveFileT, version)] != SAVE_VERSION) {
|
|
fclose(fp);
|
|
return false;
|
|
}
|
|
fclose(fp);
|
|
memcpy(out, &head[offsetof(SaveFileT, description)], SAVE_DESC_MAX + 1u);
|
|
out[SAVE_DESC_MAX] = '\0';
|
|
if (sequence != NULL) {
|
|
memcpy(sequence, &head[offsetof(SaveFileT, sequence)], sizeof(*sequence));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// set.simple's description, cut to a save's.
|
|
static void simpleDesc(char *out) {
|
|
const char *end;
|
|
|
|
end = (const char *)memchr(gAgi->simpleSaveDesc, '\0', SAVE_DESC_MAX);
|
|
memset(out, 0, SAVE_DESC_MAX + 1u);
|
|
memcpy(out, gAgi->simpleSaveDesc, end != NULL ? (size_t)(end - gAgi->simpleSaveDesc) : SAVE_DESC_MAX);
|
|
}
|
|
|
|
|
|
// set.simple's save slot: the one saved under its description, else the
|
|
// first empty one; 0 when every slot holds another.
|
|
static uint8_t simpleSlot(void) {
|
|
char desc[SAVE_DESC_MAX + 1u];
|
|
uint8_t slot;
|
|
uint8_t fresh;
|
|
|
|
fresh = 0u;
|
|
for (slot = 1u; slot <= SAVE_SLOTS; slot++) {
|
|
if (readDescription(slot, desc, NULL)) {
|
|
if (isSimpleDesc(desc)) {
|
|
return slot;
|
|
}
|
|
} else if (fresh == 0u) {
|
|
fresh = slot;
|
|
}
|
|
}
|
|
return fresh;
|
|
}
|
|
|
|
|
|
static void slotName(uint8_t slot, char *out) {
|
|
(void)snprintf(out, SAVE_NAME_MAX, "AGISAVE%u", (unsigned)slot);
|
|
}
|
|
|
|
|
|
// ----- Public API (alphabetical) -----
|
|
|
|
bool restoreGame(void) {
|
|
char name[SAVE_NAME_MAX];
|
|
uint8_t slot;
|
|
uint8_t i;
|
|
uint16_t r;
|
|
|
|
if (gAgi->simpleSaveOn) {
|
|
slot = pointerPick(kSimpleRestore, true);
|
|
} else {
|
|
slot = pickSlot("Restore which game?", true);
|
|
}
|
|
if (slot == 0u) {
|
|
return false;
|
|
}
|
|
if (!fileBuffer()) {
|
|
return false;
|
|
}
|
|
slotName(slot, name);
|
|
if (jlSaveRead(name, gFile, sizeof(SaveFileT)) != sizeof(SaveFileT) || gFile->version != SAVE_VERSION) {
|
|
textMessageBox("Error in restoring game.");
|
|
return false;
|
|
}
|
|
soundStop();
|
|
gfxEraseAll();
|
|
agiResetState();
|
|
memcpy(gAgi->vars, gFile->vars, sizeof(gAgi->vars));
|
|
memcpy(gAgi->flags, gFile->flags, sizeof(gAgi->flags));
|
|
memcpy(gAgi->strings, gFile->strings, sizeof(gAgi->strings));
|
|
memcpy(gAgi->itemRoom, gFile->itemRoom, sizeof(gAgi->itemRoom));
|
|
memcpy(gAgi->keyMaps, gFile->keyMaps, sizeof(gAgi->keyMaps));
|
|
gAgi->keyMapCount = gFile->keyMapCount;
|
|
for (r = 0u; r < AGI_MAX_RESOURCES; r++) {
|
|
gAgi->logicLoaded[r] = gFile->logicLoaded[r];
|
|
gAgi->logicScanStart[r] = gFile->logicScanStart[r];
|
|
}
|
|
for (i = 0u; i < gAgi->menuItemCount && i < AGI_MAX_MENU_ITEMS; i++) {
|
|
gAgi->menuItems[i].enabled = gFile->menuEnabled[i];
|
|
}
|
|
gAgi->horizon = gFile->horizon;
|
|
gAgi->blockActive = gFile->blockActive;
|
|
gAgi->blockX1 = gFile->blockX1;
|
|
gAgi->blockY1 = gFile->blockY1;
|
|
gAgi->blockX2 = gFile->blockX2;
|
|
gAgi->blockY2 = gFile->blockY2;
|
|
gAgi->playerControl = gFile->playerControl;
|
|
gAgi->picRow = gFile->picRow;
|
|
gAgi->inputRow = gFile->inputRow;
|
|
gAgi->statusRow = gFile->statusRow;
|
|
gAgi->statusOn = gFile->statusOn;
|
|
gAgi->inputOn = gFile->inputOn;
|
|
gAgi->textFg = gFile->textFg;
|
|
gAgi->textBg = gFile->textBg;
|
|
gAgi->cursorChar = gFile->cursorChar;
|
|
gAgi->holdKey = gFile->holdKey;
|
|
gAgi->simpleSaveOn = gFile->simpleSaveOn;
|
|
memcpy(gAgi->simpleSaveDesc, gFile->simpleSaveDesc, sizeof(gAgi->simpleSaveDesc));
|
|
gAgi->priorityTableSet = gFile->priorityTableSet;
|
|
memcpy(gAgi->priorityTable, gFile->priorityTable, sizeof(gAgi->priorityTable));
|
|
// Rebuild the picture with no objects drawn, then the objects.
|
|
gAgi->scriptCount = gFile->scriptCount;
|
|
memcpy(gAgi->script, gFile->script, sizeof(gAgi->script));
|
|
for (i = 0u; i < gAgi->scriptCount; i++) {
|
|
vmReplay(&gAgi->script[i]);
|
|
}
|
|
for (i = 0u; i < AGI_MAX_OBJECTS; i++) {
|
|
const SavedObjectT *s = &gFile->objects[i];
|
|
AgiObjectT *o = &gAgi->objects[i];
|
|
|
|
memset(o, 0, sizeof(*o));
|
|
if ((s->flags & OBJ_ANIMATED) != 0u) {
|
|
(void)objSetView(i, s->viewId);
|
|
}
|
|
o->loop = s->loop;
|
|
o->cel = s->cel;
|
|
objSetLoop(i, s->loop);
|
|
objSetCel(i, s->cel);
|
|
o->priority = s->priority;
|
|
o->x = s->x;
|
|
o->y = s->y;
|
|
o->prevX = s->x;
|
|
o->prevY = s->y;
|
|
o->flags = s->flags;
|
|
o->direction = s->direction;
|
|
o->stepSize = s->stepSize;
|
|
o->stepTime = s->stepTime;
|
|
o->stepCount = s->stepCount;
|
|
o->cycleTime = s->cycleTime;
|
|
o->cycleCount = s->cycleCount;
|
|
o->cycleMode = s->cycleMode;
|
|
o->motion = s->motion;
|
|
memcpy(o->parm, s->parm, sizeof(o->parm));
|
|
}
|
|
gfxDrawStatic();
|
|
jlSurfaceClear(jlStageGet(), AGI_COLOR_BLACK);
|
|
gfxShowPic();
|
|
textDrawStatus();
|
|
textDrawInputLine();
|
|
gAgi->flags[FLAG_RESTORED] = 1u;
|
|
gAgi->restored = true;
|
|
return true;
|
|
}
|
|
|
|
|
|
void saveGame(void) {
|
|
char name[SAVE_NAME_MAX];
|
|
char desc[SAVE_DESC_MAX + 1u];
|
|
uint8_t slot;
|
|
uint8_t i;
|
|
uint16_t r;
|
|
|
|
if (gAgi->simpleSaveOn) {
|
|
slot = simpleSlot();
|
|
if (slot == 0u) {
|
|
slot = pointerPick(kSimpleFull, false);
|
|
}
|
|
if (slot == 0u) {
|
|
return;
|
|
}
|
|
simpleDesc(desc);
|
|
} else {
|
|
slot = pickSlot("Save in which slot?", false);
|
|
if (slot == 0u) {
|
|
return;
|
|
}
|
|
desc[0] = '\0';
|
|
if (!inputGetString(desc, SAVE_DESC_MAX, AGI_NO_POSITION, AGI_NO_POSITION, "Description: ")) {
|
|
return;
|
|
}
|
|
}
|
|
if (!fileBuffer()) {
|
|
return;
|
|
}
|
|
memset(gFile, 0, sizeof(SaveFileT));
|
|
gFile->magic[0] = SAVE_MAGIC_0;
|
|
gFile->magic[1] = SAVE_MAGIC_1;
|
|
gFile->magic[2] = SAVE_MAGIC_2;
|
|
gFile->version = SAVE_VERSION;
|
|
gFile->sequence = newestSequence() + 1u;
|
|
memcpy(gFile->description, desc, sizeof(desc));
|
|
memcpy(gFile->vars, gAgi->vars, sizeof(gFile->vars));
|
|
memcpy(gFile->flags, gAgi->flags, sizeof(gFile->flags));
|
|
memcpy(gFile->strings, gAgi->strings, sizeof(gFile->strings));
|
|
memcpy(gFile->itemRoom, gAgi->itemRoom, sizeof(gFile->itemRoom));
|
|
memcpy(gFile->keyMaps, gAgi->keyMaps, sizeof(gFile->keyMaps));
|
|
gFile->keyMapCount = gAgi->keyMapCount;
|
|
for (r = 0u; r < AGI_MAX_RESOURCES; r++) {
|
|
gFile->logicLoaded[r] = gAgi->logicLoaded[r];
|
|
gFile->logicScanStart[r] = gAgi->logicScanStart[r];
|
|
}
|
|
for (i = 0u; i < gAgi->menuItemCount && i < AGI_MAX_MENU_ITEMS; i++) {
|
|
gFile->menuEnabled[i] = gAgi->menuItems[i].enabled;
|
|
}
|
|
gFile->horizon = gAgi->horizon;
|
|
gFile->blockActive = gAgi->blockActive;
|
|
gFile->blockX1 = gAgi->blockX1;
|
|
gFile->blockY1 = gAgi->blockY1;
|
|
gFile->blockX2 = gAgi->blockX2;
|
|
gFile->blockY2 = gAgi->blockY2;
|
|
gFile->playerControl = gAgi->playerControl;
|
|
gFile->picRow = gAgi->picRow;
|
|
gFile->inputRow = gAgi->inputRow;
|
|
gFile->statusRow = gAgi->statusRow;
|
|
gFile->statusOn = gAgi->statusOn;
|
|
gFile->inputOn = gAgi->inputOn;
|
|
gFile->textFg = gAgi->textFg;
|
|
gFile->textBg = gAgi->textBg;
|
|
gFile->cursorChar = gAgi->cursorChar;
|
|
gFile->holdKey = gAgi->holdKey;
|
|
gFile->simpleSaveOn = gAgi->simpleSaveOn;
|
|
memcpy(gFile->simpleSaveDesc, gAgi->simpleSaveDesc, sizeof(gFile->simpleSaveDesc));
|
|
gFile->priorityTableSet = gAgi->priorityTableSet;
|
|
memcpy(gFile->priorityTable, gAgi->priorityTable, sizeof(gFile->priorityTable));
|
|
for (i = 0u; i < AGI_MAX_OBJECTS; i++) {
|
|
const AgiObjectT *o = &gAgi->objects[i];
|
|
SavedObjectT *s = &gFile->objects[i];
|
|
|
|
s->viewId = o->viewId;
|
|
s->loop = o->loop;
|
|
s->cel = o->cel;
|
|
s->priority = o->priority;
|
|
s->x = o->x;
|
|
s->y = o->y;
|
|
s->flags = o->flags;
|
|
s->direction = o->direction;
|
|
s->stepSize = o->stepSize;
|
|
s->stepTime = o->stepTime;
|
|
s->stepCount = o->stepCount;
|
|
s->cycleTime = o->cycleTime;
|
|
s->cycleCount = o->cycleCount;
|
|
s->cycleMode = o->cycleMode;
|
|
s->motion = o->motion;
|
|
memcpy(s->parm, o->parm, sizeof(s->parm));
|
|
}
|
|
gFile->scriptCount = gAgi->scriptCount;
|
|
memcpy(gFile->script, gAgi->script, sizeof(gFile->script));
|
|
slotName(slot, name);
|
|
if (!jlSaveWrite(name, gFile, sizeof(SaveFileT))) {
|
|
textMessageBox("The game could not be saved.");
|
|
}
|
|
}
|
|
|
|
|
|
// Remember a picture-building command for restore (unless f7 blocks it).
|
|
void saveScriptAdd(uint8_t op, uint8_t arg) {
|
|
uint8_t args[AGI_SCRIPT_ARGS];
|
|
|
|
memset(args, 0, sizeof(args));
|
|
args[0] = arg;
|
|
saveScriptAddArgs(op, args);
|
|
}
|
|
|
|
|
|
void saveScriptAddArgs(uint8_t op, const uint8_t *args) {
|
|
AgiScriptEntryT *e;
|
|
|
|
if (gAgi->flags[FLAG_NO_SCRIPT] != 0u || gAgi->scriptCount >= AGI_SCRIPT_MAX) {
|
|
return;
|
|
}
|
|
e = &gAgi->script[gAgi->scriptCount];
|
|
e->op = op;
|
|
memcpy(e->args, args, AGI_SCRIPT_ARGS);
|
|
gAgi->scriptCount++;
|
|
}
|
|
|
|
|
|
// new.room: the new room's picture starts from nothing.
|
|
void saveScriptClear(void) {
|
|
gAgi->scriptCount = 0u;
|
|
}
|