#include "terminals.h"
#include <stdlib.h>
#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
changeEndgameCredit(TerminalData *data, int *isOpen, GameState *state)
{
(void)data;
(void)isOpen;
state->completed = 1;
GUI_ShowCreditsMenu(state->energy > 0);
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, "endgameCredit") == 0) {
terminal->change = changeEndgameCredit;
} 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;
}