#include "terminals.h" #include #include "util.h" #include "datatypes.h" #include "mazes.h" #include "parse.h" #include "audio.h" #include "gui.h" /* Copyright (c) 2013-2021 Devine Lu Linvega 2018 web port by rezmason 2021 C89 port by rezmason Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE. */ static Terminal (*terminals)[] = NULL; static int numTerminals; static int _isOpen = 0; static void setupAudio(TerminalData *data, int *isOpen, GameState *state) { NodeModifier modifier = state->audio ? MODIFIER_ON : MODIFIER_NONE; double fadeDelay = state->audio ? 0.1 : 0; (void)data; (void)isOpen; GUI_ShowNode(state->nodeName, state->orientation, modifier, 0.3, fadeDelay); GUI_ShowAudio(state->audio); } static int changeAudio(TerminalData *data, int *isOpen, GameState *state) { (void)isOpen; state->audio = !state->audio; Audio_SetActive(state->audio); setupAudio(data, isOpen, state); return 1; } static void setupProgress(TerminalData *data, int *isOpen, GameState *state) { (void)data; (void)isOpen; GUI_ShowProgressScreen(state->chapter); } static void updateClock(GameState *state, int refresh) { GUI_ShowClockFace(state->clock, refresh); GUI_ShowClock(state->clock); } static void setupClock(TerminalData *data, int *isOpen, GameState *state) { (void)data; (void)isOpen; updateClock(state, 0); Audio_PlayEffect(EFFECT_ACTION_ENERGY_INIT); } static int changeClock(TerminalData *data, int *isOpen, GameState *state) { (void)data; (void)isOpen; state->clock = (state->clock + 1) % 3; updateClock(state, 1); Audio_PlayEffect(EFFECT_ACTION_ENERGY_ACTIVE); return 1; } static void updateSeal(TerminalData *data, GameState *state) { int i, numSeals = 0, hasSeal = 0; double fadeDuration = 0.2, fadeDelay = 0.2; NodeModifier modifier = MODIFIER_NONE; GUI_ShowSeals(state->seals[0], state->seals[1]); /* Look to see how many seals the player has, and if they have this one, what index it's at */ for(i = 0; i < 2; i++) { if(state->seals[i] == data->seal) { hasSeal = 1; } if(state->seals[i] != ZONE_NONE) { numSeals++; } } if(hasSeal) { modifier = numSeals == 2 ? MODIFIER_SEAL_2 : MODIFIER_SEAL_1; fadeDuration = 0.1; fadeDelay = 0.1; } GUI_ShowNode(state->nodeName, state->orientation, modifier, fadeDuration, fadeDelay); } static void setupSeal(TerminalData *data, int *isOpen, GameState *state) { (void)isOpen; updateSeal(data, state); Audio_PlayEffect(EFFECT_ACTION_SEAL_INIT); } static int changeSeal(TerminalData *data, int *isOpen, GameState *state) { int i, filledIndex = -1, emptyIndex = -1; (void)isOpen; /* Look to see what, if any, empty seal slot the player has, and if they have this seal, what index it's at */ for(i = 0; i < 2; i++) { if(emptyIndex == -1 && state->seals[i] == ZONE_NONE) { emptyIndex = i; } if(filledIndex == -1 && state->seals[i] == data->seal) { filledIndex = i; break; } } if(filledIndex != -1) { Audio_PlayEffect(EFFECT_ACTION_SEAL_INACTIVE); state->seals[filledIndex] = ZONE_NONE; } else if(emptyIndex != -1) { Audio_PlayEffect(EFFECT_ACTION_SEAL_ACTIVE); state->seals[emptyIndex] = data->seal; } else { Audio_PlayEffect(EFFECT_ACTION_ENERGY_STACK); GUI_ShowSealAlert(); } updateSeal(data, state); return 1; } static void updateEnergy(TerminalData *data, int *isOpen, GameState *state) { NodeModifier modifier = MODIFIER_NONE; int holdsFuse = 0; GUI_ShowEnergy(state->energy); if(*isOpen) { /* Find out whether this terminal is filled, aka whether this terminal is listed in the state's list of terminal locations */ int i, filled = 0; for(i = 0; i < 3; i++) { if(strcmp(state->fuseLocationNames[i], data->energyTerminalData.fuseName) == 0) { filled = 1; break; } } modifier = filled ? MODIFIER_ENERGY_FILLED : MODIFIER_ENERGY_EMPTY; holdsFuse = !filled && state->energy > 0; } GUI_ShowNode(state->nodeName, state->orientation, modifier, 0.1, 0); GUI_EnableFuseCursor(holdsFuse); } static void setupEnergy(TerminalData *data, int *isOpen, GameState *state) { *isOpen = 0; updateEnergy(data, isOpen, state); Audio_PlayEffect(EFFECT_ACTION_ENERGY_INIT); } static int changeEnergy(TerminalData *data, int *isOpen, GameState *state) { int success = 1; if(!*isOpen) { *isOpen = 1; } else { int i, filledIndex = -1, emptyIndex = -1; /* Look to see what, if any, empty fuse location slot the player has, and if they have this fuse, what index it's at */ for(i = 0; i < 3; i++) { if(filledIndex == -1 && strcmp(state->fuseLocationNames[i], data->energyTerminalData.fuseName) == 0) { filledIndex = i; } else if(emptyIndex == -1 && strcmp(state->fuseLocationNames[i], "") == 0) { emptyIndex = i; } } if(filledIndex != -1) { strcpy(state->fuseLocationNames[filledIndex], ""); state->energy++; } else if(state->energy > 0 && emptyIndex != -1) { strcpy(state->fuseLocationNames[emptyIndex], data->energyTerminalData.fuseName); state->energy--; } else { GUI_ShowEnergy(state->energy); GUI_ShowEnergyAlert(); success = 0; } } if(success) { updateEnergy(data, isOpen, state); } return success; } static void resetEnergy(TerminalData *data, GameState *state) { int i; if(!data->energyTerminalData.isFilledAtStart) { return; } /* Fill the first empty fuse location slot in the state with the name of this terminal */ for(i = 0; i < 3; i++) { if(strcmp(state->fuseLocationNames[i], "") == 0) { strcpy(state->fuseLocationNames[i], data->energyTerminalData.fuseName); break; } } } static void findStudioSeals(TerminalData *data, GameState *state, int *hasSeal1, int *hasSeal2) { /* Look to see whether the player state lists both seals */ int i; for(i = 0; i < 2; i++) { if(state->seals[i] == data->sealLock[0]) { *hasSeal1 = 1; } if(state->seals[i] == data->sealLock[1]) { *hasSeal2 = 1; } } } static void updateStudio(TerminalData *data, GameState *state) { NodeModifier modifier = MODIFIER_NONE; if(state->studio) { modifier = MODIFIER_STUDIO_UNLOCKED; } else { int hasSeal1 = 0, hasSeal2 = 0; findStudioSeals(data, state, &hasSeal1, &hasSeal2); if(hasSeal1 && hasSeal2) { modifier = MODIFIER_STUDIO_SEAL_BOTH; } else if(hasSeal1) { modifier = MODIFIER_STUDIO_SEAL_1; } else if(hasSeal2) { modifier = MODIFIER_STUDIO_SEAL_2; } } GUI_ShowNode(state->nodeName, state->orientation, modifier, 0.1, 0.1); } static void setupStudio(TerminalData *data, int *isOpen, GameState *state) { (void)isOpen; updateStudio(data, state); Audio_PlayEffect(EFFECT_ACTION_DOOR_INIT); } static int changeStudio(TerminalData *data, int *isOpen, GameState *state) { int hasSeal1 = 0, hasSeal2 = 0; (void)isOpen; if(state->studio) { return 0; } findStudioSeals(data, state, &hasSeal1, &hasSeal2); if(hasSeal1 && hasSeal2) { state->studio = 1; updateStudio(data, state); Audio_PlayEffect(EFFECT_ACTION_ENERGY_ACTIVE); return 1; } return 0; } static void setupEntente(TerminalData *data, int *isOpen, GameState *state) { (void)isOpen; GUI_ShowMazeInstruction(data->axis, Mazes_GetInstruction(data->axis, state)); } static void setupEntenteProgress(TerminalData *data, int *isOpen, GameState *state) { (void)data; (void)isOpen; (void)state; GUI_ShowEntenteScreen(); } static int changeEndgame(TerminalData *data, int *isOpen, GameState *state) { (void)data; (void)isOpen; state->completed = 1; return 1; } static void setupSecret(TerminalData *data, int *isOpen, GameState *state) { (void)data; (void)isOpen; if(state->secret) { GUI_ShowNode(state->nodeName, state->orientation, MODIFIER_ON, 0.3, 0.1); } else { GUI_ShowNode(state->nodeName, state->orientation, MODIFIER_NONE, 0, 0); } } static int changeSecret(TerminalData *data, int *isOpen, GameState *state) { state->secret = !state->secret; setupSecret(data, isOpen, state); return 1; } static void nothing(TerminalData *data, int *isOpen, GameState *state) { (void)data; (void)isOpen; (void)state; } static int changeNothing(TerminalData *data, int *isOpen, GameState *state) { (void)data; (void)isOpen; (void)state; return 1; } static void resetNothing(TerminalData *data, GameState *state) { (void)data; (void)state; } static void parseTerminal(Terminal *terminal, char *line, int lineNumber) { int missing = 0; char name[40] = "", type[30] = ""; char params[200]; int numFound; strcpy(name, ""); strcpy(type, ""); strcpy(params, ""); /* example: type("name", params); */ numFound = sscanf(line, "%[^(](\"%[^\"]\", %[^)]);", type, name, params); if(numFound < 3) { printf("Terminal data error: only %d terms matched on line %d: %s\n", numFound, lineNumber, line); sprintf(name, "BROKEN_%d", lineNumber); } strcpy(terminal->name, name); terminal->setup = nothing; terminal->change = changeNothing; terminal->reset = resetNothing; if(strcmp(type, "audioTerminal") == 0) { terminal->setup = setupAudio; terminal->change = changeAudio; } else if(strcmp(type, "progressTerminal") == 0) { terminal->setup = setupProgress; } else if(strcmp(type, "clockTerminal") == 0) { terminal->setup = setupClock; terminal->change = changeClock; } else if(strcmp(type, "sealTerminal") == 0) { /* example: ANTECHANNEL */ char sealName[20]; strcpy(sealName, ""); missing = 1 - sscanf(params, "%[A-Z]", sealName); parseZone(&terminal->data.seal, sealName); terminal->setup = setupSeal; terminal->change = changeSeal; } else if(strcmp(type, "energyTerminal") == 0) { /* example: 1 */ missing = 1 - sscanf(params, "%d", &terminal->data.energyTerminalData.isFilledAtStart); strcpy(terminal->data.energyTerminalData.fuseName, name); terminal->setup = setupEnergy; terminal->change = changeEnergy; terminal->reset = resetEnergy; } else if(strcmp(type, "studioTerminal") == 0) { /* example: {FOREST, RAINRE} */ char sealNames[2][20]; strcpy(sealNames[0], "NONE"); strcpy(sealNames[1], "NONE"); missing = 2 - sscanf(params, "{%[^,], %[^}]", sealNames[0], sealNames[1]); parseZone(&terminal->data.sealLock[0], sealNames[0]); parseZone(&terminal->data.sealLock[1], sealNames[1]); terminal->setup = setupStudio; terminal->change = changeStudio; } else if(strcmp(type, "ententeTerminal") == 0) { /* example: X */ char axisName[5]; strcpy(axisName, ""); missing = 1 - sscanf(params, "%s", axisName); parseMazeAxis(&terminal->data.axis, axisName); terminal->setup = setupEntente; } else if(strcmp(type, "ententeProgressTerminal") == 0) { terminal->setup = setupEntenteProgress; } else if(strcmp(type, "secretTerminal") == 0) { terminal->setup = setupSecret; terminal->change = changeSecret; } else if(strcmp(type, "endgame") == 0) { terminal->change = changeEndgame; } else { printf("Terminal data error: unknown terminal type on line %d: %s\n", lineNumber, type); } if(missing > 0) { printf("Terminal data error: missing %d params on line %d: %s\n", missing, lineNumber, line); } } int Terminals_Init(char *data) { const char *lineDelim = "\n"; char *lineSave = NULL; int i = 0, lineNumber = 1; char *line; if(terminals != NULL) { printf("Terminal data error: the terminals were already initialized.\n"); return 0; } numTerminals = getNumLines(data); terminals = calloc(numTerminals, sizeof(Terminal)); line = getNextToken(data, lineDelim, &lineSave); while(line != NULL) { parseTerminal(&(*terminals)[i], line, lineNumber); line = getNextToken(NULL, lineDelim, &lineSave); lineNumber++; i++; } return 1; } void Terminals_Quit() { free(terminals); terminals = NULL; } void Terminals_Reset(GameState *state) { int i; for(i = 0; i < numTerminals; i++) { Terminal *terminal = &(*terminals)[i]; terminal->reset(&terminal->data, state); } } Terminal * Terminals_Get(char *name) { int i; for(i = 0; i < numTerminals; i++) { Terminal *terminal = &(*terminals)[i]; if(strcmp(terminal->name, name) == 0) { return terminal; } } return NULL; } void Terminals_Setup(TerminalSubject *subject, GameState *state) { Terminal *terminal = subject->terminal; _isOpen = 0; terminal->setup(&terminal->data, &_isOpen, state); GUI_FlashVignette(); } int Terminals_Action(TerminalSubject *subject, GameState *state) { Terminal *terminal = subject->terminal; int changed = terminal->change(&terminal->data, &_isOpen, state); if(!changed) { GUI_FlashVignette(); } return changed; }