#include "gui.h" #include <SDL2/SDL.h>#include <stdio.h>#include "datatypes.h"#include "stage.h"#include "delays.h"#include "util.h" /*Copyright (c) 2013-2021 Devine Lu Linvega2018 web port by rezmason2021 C89 port by rezmason Permission to use, copy, modify, and distribute this software for anypurpose with or without fee is hereby granted, provided that the abovecopyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIESWITH REGARD TO THIS SOFTWARE.*/ #define PATH_LENGTH 64#define NODE_RENDER_PATH "graphics/node_render/%s.%i"#define CURSOR_BORDER 32 #define NUDGE_MULT 0.025 #define loadCursor(cursor, path) success &= Loader_LoadCursor(&cursor, "graphics/cursor/" path)#define loadImage(asset, path) success &= Loader_LoadImage(Loader_CreateImage(&asset, LOADTYPE_MANUAL, "graphics/interface/" path))#define lazyLoadImage(asset, path) Loader_CreateImage(&asset, LOADTYPE_LAZY, "graphics/interface/" path)#define lazierLoadImage(asset, path) Loader_CreateImage(&asset, LOADTYPE_LAZIER, "graphics/interface/" path)#define loadLogo(asset, path) Loader_CreateImage(&asset, LOADTYPE_LAZIER, "graphics/logos/" path) static const char *modifierNames[] = { "", ".filled", ".empty", ".on", ".open", ".secret", ".seal.1", ".seal.2", ".unlocked", ".seal.1", ".seal.2", ".seal.both", ".exit"}; static SDL_Cursor *cursorsByType[NumberOfCursorTypes][2];static CursorType currentCursorType = CURSORTYPE_FORWARD;static int cursorPressed = 0;static CursorType actionCursorType = CURSORTYPE_FORWARD;static int actionTriggerActive;static SDL_Color red = {0xFF, 0x0, 0x0, 0xFF};static SDL_Color cyan = {0x72, 0xDE, 0xC2, 0xFF}; static Image *iconsByType[NumberOfIconTypes];static Image *clocksByType[NumberOfClockTypes];static Image *mazeIconsByAxisAndInstruction[NumberOfMazeAxes][NumberOfMazeInstructions];static Image *progressPanesByChapter[NumberOfChapters];static Image *sealsByZone[NumberOfZones]; static Image *ententeProgressPane;static Image *menuControls;static Image *menuLogo;static Image *vignette;static Image *black; static Image *logos[5]; static int nodeImageIndex = 0;static Image *nodeImages[2];static char nodeImagePaths[2][PATH_LENGTH]; static int logoDelays[10]; static char *creditURLs[5] = { "https://xxiivv.com", "https://rezmason.net", "https://aliceffekt.bandcamp.com", "https://100r.co", "https://git.sr.ht/~rabbits/hiversaires"};static int creditIndex;static int showedURL = 0; intGUI_PreloadAssets(void){ int success = 1; /* Cursors get loaded immediately */ loadCursor(cursorsByType[CURSORTYPE_FUSE][0], "fuse.png"); loadCursor(cursorsByType[CURSORTYPE_FUSE][1], "fuse.png"); loadCursor(cursorsByType[CURSORTYPE_FORWARD][0], "pointer.up.png"); loadCursor(cursorsByType[CURSORTYPE_FORWARD][1], "pointer.up.pressed.png"); loadCursor(cursorsByType[CURSORTYPE_LEFT][0], "pointer.left.png"); loadCursor(cursorsByType[CURSORTYPE_LEFT][1], "pointer.left.pressed.png"); loadCursor(cursorsByType[CURSORTYPE_RIGHT][0], "pointer.right.png"); loadCursor(cursorsByType[CURSORTYPE_RIGHT][1], "pointer.right.pressed.png"); cursorsByType[CURSORTYPE_SYSTEM][0] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); cursorsByType[CURSORTYPE_SYSTEM][1] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); /* HUD images get loaded immediately */ loadImage(iconsByType[ICONTYPE_ALERT], "alert.svg"); loadImage(iconsByType[ICONTYPE_CLOCK_0], "clock.0.svg"); loadImage(iconsByType[ICONTYPE_CLOCK_1], "clock.1.svg"); loadImage(iconsByType[ICONTYPE_CLOCK_2], "clock.2.svg"); loadImage(iconsByType[ICONTYPE_FOOTSTEP], "footstep.svg"); loadImage(iconsByType[ICONTYPE_FUSE_0], "fuse.0.svg"); loadImage(iconsByType[ICONTYPE_FUSE_1], "fuse.1.svg"); loadImage(iconsByType[ICONTYPE_FUSE_2], "fuse.2.svg"); loadImage(iconsByType[ICONTYPE_FUSE_3], "fuse.3.svg"); loadImage(iconsByType[ICONTYPE_MUSIC_OFF], "music.off.svg"); loadImage(iconsByType[ICONTYPE_MUSIC_ON], "music.on.svg"); loadImage(iconsByType[ICONTYPE_SAVE], "save.svg"); loadImage(sealsByZone[ZONE_ANTECHANNEL], "seal.antechannel.svg"); loadImage(sealsByZone[ZONE_CAPSULE], "seal.capsule.svg"); loadImage(sealsByZone[ZONE_CIRCULAR], "seal.circular.svg"); loadImage(sealsByZone[ZONE_ENTENTE], "seal.entente.svg"); loadImage(sealsByZone[ZONE_FOREST], "seal.forest.svg"); loadImage(sealsByZone[ZONE_METAMONDST], "seal.metamondst.svg"); loadImage(sealsByZone[ZONE_NATANIEV], "seal.nataniev.svg"); loadImage(sealsByZone[ZONE_NETHER], "seal.nether.svg"); loadImage(sealsByZone[ZONE_NONE], "seal.none.svg"); loadImage(sealsByZone[ZONE_RAINRE], "seal.rainre.svg"); loadImage(sealsByZone[ZONE_STONES], "seal.stones.svg"); loadImage(sealsByZone[ZONE_STUDIO], "seal.studio.svg"); loadImage(sealsByZone[ZONE_TERMINAL], "seal.terminal.svg"); /* Clock, maze and progress images can be loaded once they're needed */ lazyLoadImage(clocksByType[CLOCKTYPE_SHADOW], "dimclock.shadow.svg"); lazyLoadImage(clocksByType[CLOCKTYPE_STATE_0], "dimclock.state0.svg"); lazyLoadImage(clocksByType[CLOCKTYPE_STATE_1], "dimclock.state1.svg"); lazyLoadImage(clocksByType[CLOCKTYPE_STATE_2], "dimclock.state2.svg"); lazyLoadImage(mazeIconsByAxisAndInstruction[MAZEAXIS_X][MAZEINSTRUCTION_LEFT], "entente.x.left.svg"); lazyLoadImage(mazeIconsByAxisAndInstruction[MAZEAXIS_X][MAZEINSTRUCTION_RIGHT], "entente.x.right.svg"); lazyLoadImage(mazeIconsByAxisAndInstruction[MAZEAXIS_X][MAZEINSTRUCTION_STRAIGHT], "entente.x.straight.svg"); lazyLoadImage(mazeIconsByAxisAndInstruction[MAZEAXIS_Y][MAZEINSTRUCTION_LEFT], "entente.y.left.svg"); lazyLoadImage(mazeIconsByAxisAndInstruction[MAZEAXIS_Y][MAZEINSTRUCTION_RIGHT], "entente.y.right.svg"); lazyLoadImage(mazeIconsByAxisAndInstruction[MAZEAXIS_Y][MAZEINSTRUCTION_STRAIGHT], "entente.y.straight.svg"); lazyLoadImage(progressPanesByChapter[CHAPTER_ACT1], "progress.act1.svg"); lazyLoadImage(progressPanesByChapter[CHAPTER_ACT2], "progress.act2.svg"); lazyLoadImage(progressPanesByChapter[CHAPTER_ACT3], "progress.act3.svg"); lazyLoadImage(progressPanesByChapter[CHAPTER_ACT4], "progress.act4.svg"); lazyLoadImage(progressPanesByChapter[CHAPTER_ACT5], "progress.act5.svg"); lazyLoadImage(ententeProgressPane, "progress.entente.svg"); /* The full-screen menus are huge and rarely needed */ lazierLoadImage(menuControls, "menu.controls.svg"); lazierLoadImage(menuLogo, "menu.logo.svg"); /* The splash screen images only show up once */ loadLogo(logos[0], "100R.svg"); loadLogo(logos[1], "xxiivv.svg"); loadLogo(logos[2], "rm.svg"); loadLogo(logos[3], "aliceffekt.svg"); loadLogo(logos[4], "hiversaires_source.svg"); /* This one's interesting: the vignette is a full-screen radial gradient that flashes almost every time the player performs an action. So it's loaded when the game initializes, but it's a very small SVG that's rendered very large. */ loadImage(vignette, "vignette.svg"); loadImage(black, "black.svg"); return success;} voidGUI_Init(void){ Stage_SetAlpha(BILLBOARD_HUD_STEP_RIGHT, 0); Stage_SetAlpha(BILLBOARD_HUD_STEP_FORWARD, 0); Stage_SetAlpha(BILLBOARD_HUD_STEP_LEFT, 0); Stage_SetAlpha(BILLBOARD_HUD_CLOCK_ALERT, 0); Stage_SetAlpha(BILLBOARD_HUD_SEAL_ALERT, 0); Stage_SetAlpha(BILLBOARD_HUD_FUSE_ALERT, 0); Stage_SetAlpha(BILLBOARD_HUD_SAVE, 0); Stage_SetAlpha(BILLBOARD_CLOCK_SHADOW, 0); Stage_SetAlpha(BILLBOARD_VIGNETTE, 0); Stage_SetAlpha(BILLBOARD_HIVERSAIRES_LOGO, 0); Stage_SetAlpha(BILLBOARD_CONTROLS, 0); Stage_SetAlpha(BILLBOARD_LOGO, 0); /* Most HUD elements occupy a Stage region for the whole game */ nodeImageIndex = 0; Stage_SetImage(BILLBOARD_NODE, nodeImages[0]); Stage_SetImage(BILLBOARD_LAST_NODE_FADE, nodeImages[1]); Stage_SetImage(BILLBOARD_HUD_STEP_RIGHT, iconsByType[ICONTYPE_FOOTSTEP]); Stage_SetImage(BILLBOARD_HUD_STEP_FORWARD, iconsByType[ICONTYPE_FOOTSTEP]); Stage_SetImage(BILLBOARD_HUD_STEP_LEFT, iconsByType[ICONTYPE_FOOTSTEP]); Stage_SetImage(BILLBOARD_HUD_CLOCK_ALERT, iconsByType[ICONTYPE_ALERT]); Stage_SetImage(BILLBOARD_HUD_SEAL_ALERT, iconsByType[ICONTYPE_ALERT]); Stage_SetImage(BILLBOARD_HUD_FUSE_ALERT, iconsByType[ICONTYPE_ALERT]); Stage_SetImage(BILLBOARD_HUD_SAVE, iconsByType[ICONTYPE_SAVE]); Stage_SetImage(BILLBOARD_CLOCK_SHADOW, clocksByType[CLOCKTYPE_SHADOW]); Stage_SetImage(BILLBOARD_VIGNETTE, vignette); Stage_SetImage(BILLBOARD_BLACK, black); Stage_SetImage(BILLBOARD_HIVERSAIRES_LOGO, menuLogo); Stage_SetImage(BILLBOARD_CONTROLS, menuControls); SDL_SetCursor(cursorsByType[currentCursorType][cursorPressed]); Loader_CreateImage(&nodeImages[0], LOADTYPE_LAZIER, nodeImagePaths[0]); Loader_CreateImage(&nodeImages[1], LOADTYPE_LAZIER, nodeImagePaths[1]);} voidGUI_Quit(void){ int i, j; for(i = 0; i < NumberOfCursorTypes; i++) { SDL_FreeCursor(cursorsByType[i][0]); SDL_FreeCursor(cursorsByType[i][1]); cursorsByType[i][0] = NULL; cursorsByType[i][1] = NULL; } for(i = 0; i < NumberOfIconTypes; i++) { Loader_DestroyImage(iconsByType[i]); iconsByType[i] = NULL; } for(i = 0; i < NumberOfClockTypes; i++) { Loader_DestroyImage(clocksByType[i]); clocksByType[i] = NULL; } for(i = 0; i < NumberOfMazeAxes; i++) { for(j = 0; j < NumberOfMazeInstructions; j++) { Loader_DestroyImage(mazeIconsByAxisAndInstruction[i][j]); mazeIconsByAxisAndInstruction[i][j] = NULL; } } for(i = 0; i < NumberOfChapters; i++) { Loader_DestroyImage(progressPanesByChapter[i]); progressPanesByChapter[i] = NULL; } for(i = 0; i < NumberOfZones; i++) { Loader_DestroyImage(sealsByZone[i]); sealsByZone[i] = NULL; } Loader_DestroyImage(ententeProgressPane); ententeProgressPane = NULL; Loader_DestroyImage(menuControls); menuControls = NULL; Loader_DestroyImage(menuLogo); menuLogo = NULL; Loader_DestroyImage(vignette); vignette = NULL; Loader_DestroyImage(black); black = NULL; for(i = 0; i < 5; i++) { Loader_DestroyImage(logos[i]); logos[i] = NULL; } Loader_DestroyImage(nodeImages[0]); Loader_DestroyImage(nodeImages[1]);} voidGUI_SetActionTrigger(int active){ actionTriggerActive = active;} voidGUI_HideCursor(void){ SDL_ShowCursor(0);} voidGUI_UpdateCursor(int mouseX, int mouseY, int pressed){ CursorType type; RegionName regionUnderMouse = GUI_GetRegionNameAt(mouseX, mouseY); if(Stage_PointNearBorder(mouseX, mouseY, CURSOR_BORDER)) { /* The system cursor is shown near the edge of the window, so that the game's cursors don't obstruct the player's view of their cursor while they resize or move the window. */ type = CURSORTYPE_SYSTEM; } else { switch(regionUnderMouse) { case TRIGGER_ACTION: type = actionCursorType; break; case TRIGGER_MOVE_FORWARD: type = CURSORTYPE_FORWARD; break; case TRIGGER_MOVE_LEFT: type = CURSORTYPE_LEFT; break; case TRIGGER_MOVE_RIGHT: type = CURSORTYPE_RIGHT; break; default: type = CURSORTYPE_FORWARD; break; } } SDL_ShowCursor(1); if(currentCursorType != type || cursorPressed != pressed) { currentCursorType = type; cursorPressed = pressed; SDL_SetCursor(cursorsByType[currentCursorType][cursorPressed]); }} voidGUI_EnableFuseCursor(int enable){ int currentCursorIsActionCursor = currentCursorType == actionCursorType; actionCursorType = enable ? CURSORTYPE_FUSE : CURSORTYPE_FORWARD; if(currentCursorIsActionCursor) { currentCursorType = actionCursorType; SDL_SetCursor(cursorsByType[currentCursorType][cursorPressed]); }} RegionNameGUI_GetRegionNameAt(int mouseX, int mouseY){ int i = actionTriggerActive ? TRIGGER_ACTION : TRIGGER_MOVE_FORWARD; for(; i <= TRIGGER_MOVE_RIGHT; i++) { if(Stage_HitTest(i, mouseX, mouseY)) { return i; } } return TRIGGER_MOVE_FORWARD;} voidGUI_Reset(void){ GUI_HideMenu(); Stage_SetAlpha(BILLBOARD_LAST_NODE_FADE, 0); Stage_SetAlpha(BILLBOARD_CLOCK, 0); Stage_SetAlpha(BILLBOARD_CLOCK_SHADOW, 0); Stage_SetAlpha(BILLBOARD_PROGRESS_PANE, 0); Stage_SetAlpha(BILLBOARD_ENTENTE_SCREEN, 0);} voidGUI_ShowClockFace(int state, int refresh){ Stage_SetImage(BILLBOARD_CLOCK, clocksByType[CLOCKTYPE_STATE_0 + state]); if(!refresh) { Stage_Fade(BILLBOARD_CLOCK_SHADOW, 0, 0.23, 1.5, 0.5); Stage_Fade(BILLBOARD_CLOCK, 0, 1, 0.5, 0.5); } else { Stage_SetAlpha(BILLBOARD_CLOCK_SHADOW, 0.23); Stage_SetAlpha(BILLBOARD_CLOCK, 1); }} voidGUI_ShowClock(int state){ Stage_SetImage(BILLBOARD_HUD_CLOCK, iconsByType[ICONTYPE_CLOCK_0 + state]); Stage_Fade(BILLBOARD_HUD_CLOCK, 1, 0, 0.5, 3);} voidGUI_ShowClockAlert(void){ Stage_Fade(BILLBOARD_HUD_CLOCK_ALERT, 1, 0, 0.5, 0.5);} voidGUI_ShowSeals(Zone zoneP, Zone zoneQ){ /* For aesthetic reasons, seals are always presented in a particular order. */ Zone zone1, zone2; if((zoneP > ZONE_NONE && zoneP < zoneQ) || zoneQ == ZONE_NONE) { zone1 = zoneP; zone2 = zoneQ; } else { zone1 = zoneQ; zone2 = zoneP; } Stage_SetImage(BILLBOARD_HUD_SEAL_1, sealsByZone[zone1]); Stage_SetImage(BILLBOARD_HUD_SEAL_2, sealsByZone[zone2]); Stage_Fade(BILLBOARD_HUD_SEAL_1, 1, 0, 0.5, 3); Stage_Fade(BILLBOARD_HUD_SEAL_2, 1, 0, 0.5, 3);} voidGUI_ShowSealAlert(void){ Stage_Fade(BILLBOARD_HUD_SEAL_ALERT, 1, 0, 0.5, 0.5);} voidGUI_ShowEnergy(int level){ Stage_SetImage(BILLBOARD_HUD_FUSE, iconsByType[ICONTYPE_FUSE_0 + level]); Stage_Fade(BILLBOARD_HUD_FUSE, 1, 0, 0.5, 3);} voidGUI_ShowEnergyAlert(void){ Stage_Fade(BILLBOARD_HUD_FUSE_ALERT, 1, 0, 1.5, 0.5);} voidGUI_ShowAudio(int enabled){ Stage_SetImage(BILLBOARD_HUD_AUDIO, iconsByType[ICONTYPE_MUSIC_OFF + enabled]); Stage_Fade(BILLBOARD_HUD_AUDIO, 1, 0, 0.5, 3);} voidGUI_HideMenu(void){ Stage_SetAlpha(BILLBOARD_BLACK, 0.); Stage_SetAlpha(BILLBOARD_HIVERSAIRES_LOGO, 0.); Stage_SetAlpha(BILLBOARD_CONTROLS, 0.); Stage_SetAlpha(BILLBOARD_LOGO, 0.);} voidGUI_ShowHomeMenu(void){ Stage_SetAlpha(BILLBOARD_HUD_SEAL_1, 0); Stage_SetAlpha(BILLBOARD_HUD_SEAL_2, 0); Stage_SetAlpha(BILLBOARD_HUD_FUSE, 0); Stage_SetAlpha(BILLBOARD_HUD_SAVE, 0); Stage_SetAlpha(BILLBOARD_NODE, 1); Stage_Fade(BILLBOARD_BLACK, 1, 0, 2, 0); Stage_Fade(BILLBOARD_HIVERSAIRES_LOGO, 1, 0, 2, 3); Stage_Fade(BILLBOARD_CONTROLS, 1, 0, 1, 8);} voidGUI_ShowMovement(int nudgeX, int nudgeY){ Stage_Nudge(nudgeX * NUDGE_MULT, nudgeY * NUDGE_MULT, 0.2); Stage_Fade(BILLBOARD_NODE, 0.5, 1, 0.2, 0); Stage_SetAlpha(BILLBOARD_LAST_NODE_FADE, 0);} voidGUI_ShowNode(char *nodeName, Orientation orientation, NodeModifier modifier, double fadeDuration, double fadeDelay){ /* The main visual information in Hiversaires comes from the images loaded here. The previous node image often cross-fades into the current one. To accomplish this effect, Hiversaires takes the old image, swaps its region with LAST_NODE_FADE, and fades out on top of the new image. The last and current images are reused in alternating roles, similar to a double buffering game. */ char path[PATH_LENGTH]; int nextIndex = !nodeImageIndex; snprintf(path, PATH_LENGTH, NODE_RENDER_PATH "%s.jpg", nodeName, orientation, modifierNames[modifier]); if(strcmp(nodeImagePaths[nodeImageIndex], path) == 0) { return; } nodeImageIndex = nextIndex; strcpy(nodeImagePaths[nextIndex], path); Stage_SwapImages(BILLBOARD_LAST_NODE_FADE, BILLBOARD_NODE); Stage_SetImage(BILLBOARD_NODE, nodeImages[nodeImageIndex]); Stage_SetAlpha(BILLBOARD_NODE, 1); Stage_Fade(BILLBOARD_LAST_NODE_FADE, 1, 0, fadeDuration, fadeDelay);} static voidsplashNext(void *data){ Image *image = data; Stage_SetImage(BILLBOARD_LOGO, image); Stage_Fade(BILLBOARD_LOGO, 1, 0, 0.125, 0.675);} static voidsplashEnd(void *data){ FuncWrapper *wrapper = data; Stage_SetAlpha(BILLBOARD_LOGO, 0);Lines 1–500 of 645. Load more
#include "gui.h" #include <SDL2/SDL.h> #include <stdio.h> #include "datatypes.h" #include "stage.h" #include "delays.h" #include "util.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. */ #define PATH_LENGTH 64 #define NODE_RENDER_PATH "graphics/node_render/%s.%i" #define CURSOR_BORDER 32 #define NUDGE_MULT 0.025 #define loadCursor(cursor, path) success &= Loader_LoadCursor(&cursor, "graphics/cursor/" path) #define loadImage(asset, path) success &= Loader_LoadImage(Loader_CreateImage(&asset, LOADTYPE_MANUAL, "graphics/interface/" path)) #define lazyLoadImage(asset, path) Loader_CreateImage(&asset, LOADTYPE_LAZY, "graphics/interface/" path) #define lazierLoadImage(asset, path) Loader_CreateImage(&asset, LOADTYPE_LAZIER, "graphics/interface/" path) #define loadLogo(asset, path) Loader_CreateImage(&asset, LOADTYPE_LAZIER, "graphics/logos/" path) static const char *modifierNames[] = { "", ".filled", ".empty", ".on", ".open", ".secret", ".seal.1", ".seal.2", ".unlocked", ".seal.1", ".seal.2", ".seal.both", ".exit"}; static SDL_Cursor *cursorsByType[NumberOfCursorTypes][2]; static CursorType currentCursorType = CURSORTYPE_FORWARD; static int cursorPressed = 0; static CursorType actionCursorType = CURSORTYPE_FORWARD; static int actionTriggerActive; static SDL_Color red = {0xFF, 0x0, 0x0, 0xFF}; static SDL_Color cyan = {0x72, 0xDE, 0xC2, 0xFF}; static Image *iconsByType[NumberOfIconTypes]; static Image *clocksByType[NumberOfClockTypes]; static Image *mazeIconsByAxisAndInstruction[NumberOfMazeAxes][NumberOfMazeInstructions]; static Image *progressPanesByChapter[NumberOfChapters]; static Image *sealsByZone[NumberOfZones]; static Image *ententeProgressPane; static Image *menuControls; static Image *menuLogo; static Image *vignette; static Image *black; static Image *logos[5]; static int nodeImageIndex = 0; static Image *nodeImages[2]; static char nodeImagePaths[2][PATH_LENGTH]; static int logoDelays[10]; static char *creditURLs[5] = { "https://xxiivv.com", "https://rezmason.net", "https://aliceffekt.bandcamp.com", "https://100r.co", "https://git.sr.ht/~rabbits/hiversaires"}; static int creditIndex; static int showedURL = 0; int GUI_PreloadAssets(void) { int success = 1; /* Cursors get loaded immediately */ loadCursor(cursorsByType[CURSORTYPE_FUSE][0], "fuse.png"); loadCursor(cursorsByType[CURSORTYPE_FUSE][1], "fuse.png"); loadCursor(cursorsByType[CURSORTYPE_FORWARD][0], "pointer.up.png"); loadCursor(cursorsByType[CURSORTYPE_FORWARD][1], "pointer.up.pressed.png"); loadCursor(cursorsByType[CURSORTYPE_LEFT][0], "pointer.left.png"); loadCursor(cursorsByType[CURSORTYPE_LEFT][1], "pointer.left.pressed.png"); loadCursor(cursorsByType[CURSORTYPE_RIGHT][0], "pointer.right.png"); loadCursor(cursorsByType[CURSORTYPE_RIGHT][1], "pointer.right.pressed.png"); cursorsByType[CURSORTYPE_SYSTEM][0] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); cursorsByType[CURSORTYPE_SYSTEM][1] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); /* HUD images get loaded immediately */ loadImage(iconsByType[ICONTYPE_ALERT], "alert.svg"); loadImage(iconsByType[ICONTYPE_CLOCK_0], "clock.0.svg"); loadImage(iconsByType[ICONTYPE_CLOCK_1], "clock.1.svg"); loadImage(iconsByType[ICONTYPE_CLOCK_2], "clock.2.svg"); loadImage(iconsByType[ICONTYPE_FOOTSTEP], "footstep.svg"); loadImage(iconsByType[ICONTYPE_FUSE_0], "fuse.0.svg"); loadImage(iconsByType[ICONTYPE_FUSE_1], "fuse.1.svg"); loadImage(iconsByType[ICONTYPE_FUSE_2], "fuse.2.svg"); loadImage(iconsByType[ICONTYPE_FUSE_3], "fuse.3.svg"); loadImage(iconsByType[ICONTYPE_MUSIC_OFF], "music.off.svg"); loadImage(iconsByType[ICONTYPE_MUSIC_ON], "music.on.svg"); loadImage(iconsByType[ICONTYPE_SAVE], "save.svg"); loadImage(sealsByZone[ZONE_ANTECHANNEL], "seal.antechannel.svg"); loadImage(sealsByZone[ZONE_CAPSULE], "seal.capsule.svg"); loadImage(sealsByZone[ZONE_CIRCULAR], "seal.circular.svg"); loadImage(sealsByZone[ZONE_ENTENTE], "seal.entente.svg"); loadImage(sealsByZone[ZONE_FOREST], "seal.forest.svg"); loadImage(sealsByZone[ZONE_METAMONDST], "seal.metamondst.svg"); loadImage(sealsByZone[ZONE_NATANIEV], "seal.nataniev.svg"); loadImage(sealsByZone[ZONE_NETHER], "seal.nether.svg"); loadImage(sealsByZone[ZONE_NONE], "seal.none.svg"); loadImage(sealsByZone[ZONE_RAINRE], "seal.rainre.svg"); loadImage(sealsByZone[ZONE_STONES], "seal.stones.svg"); loadImage(sealsByZone[ZONE_STUDIO], "seal.studio.svg"); loadImage(sealsByZone[ZONE_TERMINAL], "seal.terminal.svg"); /* Clock, maze and progress images can be loaded once they're needed */ lazyLoadImage(clocksByType[CLOCKTYPE_SHADOW], "dimclock.shadow.svg"); lazyLoadImage(clocksByType[CLOCKTYPE_STATE_0], "dimclock.state0.svg"); lazyLoadImage(clocksByType[CLOCKTYPE_STATE_1], "dimclock.state1.svg"); lazyLoadImage(clocksByType[CLOCKTYPE_STATE_2], "dimclock.state2.svg"); lazyLoadImage(mazeIconsByAxisAndInstruction[MAZEAXIS_X][MAZEINSTRUCTION_LEFT], "entente.x.left.svg"); lazyLoadImage(mazeIconsByAxisAndInstruction[MAZEAXIS_X][MAZEINSTRUCTION_RIGHT], "entente.x.right.svg"); lazyLoadImage(mazeIconsByAxisAndInstruction[MAZEAXIS_X][MAZEINSTRUCTION_STRAIGHT], "entente.x.straight.svg"); lazyLoadImage(mazeIconsByAxisAndInstruction[MAZEAXIS_Y][MAZEINSTRUCTION_LEFT], "entente.y.left.svg"); lazyLoadImage(mazeIconsByAxisAndInstruction[MAZEAXIS_Y][MAZEINSTRUCTION_RIGHT], "entente.y.right.svg"); lazyLoadImage(mazeIconsByAxisAndInstruction[MAZEAXIS_Y][MAZEINSTRUCTION_STRAIGHT], "entente.y.straight.svg"); lazyLoadImage(progressPanesByChapter[CHAPTER_ACT1], "progress.act1.svg"); lazyLoadImage(progressPanesByChapter[CHAPTER_ACT2], "progress.act2.svg"); lazyLoadImage(progressPanesByChapter[CHAPTER_ACT3], "progress.act3.svg"); lazyLoadImage(progressPanesByChapter[CHAPTER_ACT4], "progress.act4.svg"); lazyLoadImage(progressPanesByChapter[CHAPTER_ACT5], "progress.act5.svg"); lazyLoadImage(ententeProgressPane, "progress.entente.svg"); /* The full-screen menus are huge and rarely needed */ lazierLoadImage(menuControls, "menu.controls.svg"); lazierLoadImage(menuLogo, "menu.logo.svg"); /* The splash screen images only show up once */ loadLogo(logos[0], "100R.svg"); loadLogo(logos[1], "xxiivv.svg"); loadLogo(logos[2], "rm.svg"); loadLogo(logos[3], "aliceffekt.svg"); loadLogo(logos[4], "hiversaires_source.svg"); /* This one's interesting: the vignette is a full-screen radial gradient that flashes almost every time the player performs an action. So it's loaded when the game initializes, but it's a very small SVG that's rendered very large. */ loadImage(vignette, "vignette.svg"); loadImage(black, "black.svg"); return success; } void GUI_Init(void) { Stage_SetAlpha(BILLBOARD_HUD_STEP_RIGHT, 0); Stage_SetAlpha(BILLBOARD_HUD_STEP_FORWARD, 0); Stage_SetAlpha(BILLBOARD_HUD_STEP_LEFT, 0); Stage_SetAlpha(BILLBOARD_HUD_CLOCK_ALERT, 0); Stage_SetAlpha(BILLBOARD_HUD_SEAL_ALERT, 0); Stage_SetAlpha(BILLBOARD_HUD_FUSE_ALERT, 0); Stage_SetAlpha(BILLBOARD_HUD_SAVE, 0); Stage_SetAlpha(BILLBOARD_CLOCK_SHADOW, 0); Stage_SetAlpha(BILLBOARD_VIGNETTE, 0); Stage_SetAlpha(BILLBOARD_HIVERSAIRES_LOGO, 0); Stage_SetAlpha(BILLBOARD_CONTROLS, 0); Stage_SetAlpha(BILLBOARD_LOGO, 0); /* Most HUD elements occupy a Stage region for the whole game */ nodeImageIndex = 0; Stage_SetImage(BILLBOARD_NODE, nodeImages[0]); Stage_SetImage(BILLBOARD_LAST_NODE_FADE, nodeImages[1]); Stage_SetImage(BILLBOARD_HUD_STEP_RIGHT, iconsByType[ICONTYPE_FOOTSTEP]); Stage_SetImage(BILLBOARD_HUD_STEP_FORWARD, iconsByType[ICONTYPE_FOOTSTEP]); Stage_SetImage(BILLBOARD_HUD_STEP_LEFT, iconsByType[ICONTYPE_FOOTSTEP]); Stage_SetImage(BILLBOARD_HUD_CLOCK_ALERT, iconsByType[ICONTYPE_ALERT]); Stage_SetImage(BILLBOARD_HUD_SEAL_ALERT, iconsByType[ICONTYPE_ALERT]); Stage_SetImage(BILLBOARD_HUD_FUSE_ALERT, iconsByType[ICONTYPE_ALERT]); Stage_SetImage(BILLBOARD_HUD_SAVE, iconsByType[ICONTYPE_SAVE]); Stage_SetImage(BILLBOARD_CLOCK_SHADOW, clocksByType[CLOCKTYPE_SHADOW]); Stage_SetImage(BILLBOARD_VIGNETTE, vignette); Stage_SetImage(BILLBOARD_BLACK, black); Stage_SetImage(BILLBOARD_HIVERSAIRES_LOGO, menuLogo); Stage_SetImage(BILLBOARD_CONTROLS, menuControls); SDL_SetCursor(cursorsByType[currentCursorType][cursorPressed]); Loader_CreateImage(&nodeImages[0], LOADTYPE_LAZIER, nodeImagePaths[0]); Loader_CreateImage(&nodeImages[1], LOADTYPE_LAZIER, nodeImagePaths[1]); } void GUI_Quit(void) { int i, j; for(i = 0; i < NumberOfCursorTypes; i++) { SDL_FreeCursor(cursorsByType[i][0]); SDL_FreeCursor(cursorsByType[i][1]); cursorsByType[i][0] = NULL; cursorsByType[i][1] = NULL; } for(i = 0; i < NumberOfIconTypes; i++) { Loader_DestroyImage(iconsByType[i]); iconsByType[i] = NULL; } for(i = 0; i < NumberOfClockTypes; i++) { Loader_DestroyImage(clocksByType[i]); clocksByType[i] = NULL; } for(i = 0; i < NumberOfMazeAxes; i++) { for(j = 0; j < NumberOfMazeInstructions; j++) { Loader_DestroyImage(mazeIconsByAxisAndInstruction[i][j]); mazeIconsByAxisAndInstruction[i][j] = NULL; } } for(i = 0; i < NumberOfChapters; i++) { Loader_DestroyImage(progressPanesByChapter[i]); progressPanesByChapter[i] = NULL; } for(i = 0; i < NumberOfZones; i++) { Loader_DestroyImage(sealsByZone[i]); sealsByZone[i] = NULL; } Loader_DestroyImage(ententeProgressPane); ententeProgressPane = NULL; Loader_DestroyImage(menuControls); menuControls = NULL; Loader_DestroyImage(menuLogo); menuLogo = NULL; Loader_DestroyImage(vignette); vignette = NULL; Loader_DestroyImage(black); black = NULL; for(i = 0; i < 5; i++) { Loader_DestroyImage(logos[i]); logos[i] = NULL; } Loader_DestroyImage(nodeImages[0]); Loader_DestroyImage(nodeImages[1]); } void GUI_SetActionTrigger(int active) { actionTriggerActive = active; } void GUI_HideCursor(void) { SDL_ShowCursor(0); } void GUI_UpdateCursor(int mouseX, int mouseY, int pressed) { CursorType type; RegionName regionUnderMouse = GUI_GetRegionNameAt(mouseX, mouseY); if(Stage_PointNearBorder(mouseX, mouseY, CURSOR_BORDER)) { /* The system cursor is shown near the edge of the window, so that the game's cursors don't obstruct the player's view of their cursor while they resize or move the window. */ type = CURSORTYPE_SYSTEM; } else { switch(regionUnderMouse) { case TRIGGER_ACTION: type = actionCursorType; break; case TRIGGER_MOVE_FORWARD: type = CURSORTYPE_FORWARD; break; case TRIGGER_MOVE_LEFT: type = CURSORTYPE_LEFT; break; case TRIGGER_MOVE_RIGHT: type = CURSORTYPE_RIGHT; break; default: type = CURSORTYPE_FORWARD; break; } } SDL_ShowCursor(1); if(currentCursorType != type || cursorPressed != pressed) { currentCursorType = type; cursorPressed = pressed; SDL_SetCursor(cursorsByType[currentCursorType][cursorPressed]); } } void GUI_EnableFuseCursor(int enable) { int currentCursorIsActionCursor = currentCursorType == actionCursorType; actionCursorType = enable ? CURSORTYPE_FUSE : CURSORTYPE_FORWARD; if(currentCursorIsActionCursor) { currentCursorType = actionCursorType; SDL_SetCursor(cursorsByType[currentCursorType][cursorPressed]); } } RegionName GUI_GetRegionNameAt(int mouseX, int mouseY) { int i = actionTriggerActive ? TRIGGER_ACTION : TRIGGER_MOVE_FORWARD; for(; i <= TRIGGER_MOVE_RIGHT; i++) { if(Stage_HitTest(i, mouseX, mouseY)) { return i; } } return TRIGGER_MOVE_FORWARD; } void GUI_Reset(void) { GUI_HideMenu(); Stage_SetAlpha(BILLBOARD_LAST_NODE_FADE, 0); Stage_SetAlpha(BILLBOARD_CLOCK, 0); Stage_SetAlpha(BILLBOARD_CLOCK_SHADOW, 0); Stage_SetAlpha(BILLBOARD_PROGRESS_PANE, 0); Stage_SetAlpha(BILLBOARD_ENTENTE_SCREEN, 0); } void GUI_ShowClockFace(int state, int refresh) { Stage_SetImage(BILLBOARD_CLOCK, clocksByType[CLOCKTYPE_STATE_0 + state]); if(!refresh) { Stage_Fade(BILLBOARD_CLOCK_SHADOW, 0, 0.23, 1.5, 0.5); Stage_Fade(BILLBOARD_CLOCK, 0, 1, 0.5, 0.5); } else { Stage_SetAlpha(BILLBOARD_CLOCK_SHADOW, 0.23); Stage_SetAlpha(BILLBOARD_CLOCK, 1); } } void GUI_ShowClock(int state) { Stage_SetImage(BILLBOARD_HUD_CLOCK, iconsByType[ICONTYPE_CLOCK_0 + state]); Stage_Fade(BILLBOARD_HUD_CLOCK, 1, 0, 0.5, 3); } void GUI_ShowClockAlert(void) { Stage_Fade(BILLBOARD_HUD_CLOCK_ALERT, 1, 0, 0.5, 0.5); } void GUI_ShowSeals(Zone zoneP, Zone zoneQ) { /* For aesthetic reasons, seals are always presented in a particular order. */ Zone zone1, zone2; if((zoneP > ZONE_NONE && zoneP < zoneQ) || zoneQ == ZONE_NONE) { zone1 = zoneP; zone2 = zoneQ; } else { zone1 = zoneQ; zone2 = zoneP; } Stage_SetImage(BILLBOARD_HUD_SEAL_1, sealsByZone[zone1]); Stage_SetImage(BILLBOARD_HUD_SEAL_2, sealsByZone[zone2]); Stage_Fade(BILLBOARD_HUD_SEAL_1, 1, 0, 0.5, 3); Stage_Fade(BILLBOARD_HUD_SEAL_2, 1, 0, 0.5, 3); } void GUI_ShowSealAlert(void) { Stage_Fade(BILLBOARD_HUD_SEAL_ALERT, 1, 0, 0.5, 0.5); } void GUI_ShowEnergy(int level) { Stage_SetImage(BILLBOARD_HUD_FUSE, iconsByType[ICONTYPE_FUSE_0 + level]); Stage_Fade(BILLBOARD_HUD_FUSE, 1, 0, 0.5, 3); } void GUI_ShowEnergyAlert(void) { Stage_Fade(BILLBOARD_HUD_FUSE_ALERT, 1, 0, 1.5, 0.5); } void GUI_ShowAudio(int enabled) { Stage_SetImage(BILLBOARD_HUD_AUDIO, iconsByType[ICONTYPE_MUSIC_OFF + enabled]); Stage_Fade(BILLBOARD_HUD_AUDIO, 1, 0, 0.5, 3); } void GUI_HideMenu(void) { Stage_SetAlpha(BILLBOARD_BLACK, 0.); Stage_SetAlpha(BILLBOARD_HIVERSAIRES_LOGO, 0.); Stage_SetAlpha(BILLBOARD_CONTROLS, 0.); Stage_SetAlpha(BILLBOARD_LOGO, 0.); } void GUI_ShowHomeMenu(void) { Stage_SetAlpha(BILLBOARD_HUD_SEAL_1, 0); Stage_SetAlpha(BILLBOARD_HUD_SEAL_2, 0); Stage_SetAlpha(BILLBOARD_HUD_FUSE, 0); Stage_SetAlpha(BILLBOARD_HUD_SAVE, 0); Stage_SetAlpha(BILLBOARD_NODE, 1); Stage_Fade(BILLBOARD_BLACK, 1, 0, 2, 0); Stage_Fade(BILLBOARD_HIVERSAIRES_LOGO, 1, 0, 2, 3); Stage_Fade(BILLBOARD_CONTROLS, 1, 0, 1, 8); } void GUI_ShowMovement(int nudgeX, int nudgeY) { Stage_Nudge(nudgeX * NUDGE_MULT, nudgeY * NUDGE_MULT, 0.2); Stage_Fade(BILLBOARD_NODE, 0.5, 1, 0.2, 0); Stage_SetAlpha(BILLBOARD_LAST_NODE_FADE, 0); } void GUI_ShowNode(char *nodeName, Orientation orientation, NodeModifier modifier, double fadeDuration, double fadeDelay) { /* The main visual information in Hiversaires comes from the images loaded here. The previous node image often cross-fades into the current one. To accomplish this effect, Hiversaires takes the old image, swaps its region with LAST_NODE_FADE, and fades out on top of the new image. The last and current images are reused in alternating roles, similar to a double buffering game. */ char path[PATH_LENGTH]; int nextIndex = !nodeImageIndex; snprintf(path, PATH_LENGTH, NODE_RENDER_PATH "%s.jpg", nodeName, orientation, modifierNames[modifier]); if(strcmp(nodeImagePaths[nodeImageIndex], path) == 0) { return; } nodeImageIndex = nextIndex; strcpy(nodeImagePaths[nextIndex], path); Stage_SwapImages(BILLBOARD_LAST_NODE_FADE, BILLBOARD_NODE); Stage_SetImage(BILLBOARD_NODE, nodeImages[nodeImageIndex]); Stage_SetAlpha(BILLBOARD_NODE, 1); Stage_Fade(BILLBOARD_LAST_NODE_FADE, 1, 0, fadeDuration, fadeDelay); } static void splashNext(void *data) { Image *image = data; Stage_SetImage(BILLBOARD_LOGO, image); Stage_Fade(BILLBOARD_LOGO, 1, 0, 0.125, 0.675); } static void splashEnd(void *data) { FuncWrapper *wrapper = data; Stage_SetAlpha(BILLBOARD_LOGO, 0);