/* Copyright (C) 2015-2019 Parallel Realities This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "galacticMap.h" static void logic(void); static void draw(void); static void handleKeyboard(void); static void handleMouse(void); static void scrollGalaxy(void); static void drawStarSystemDetail(void); static void selectStarSystem(void); static void drawGalaxy(void); static void centerOnSelectedStarSystem(void); static void doStarSystems(void); void destroyGalacticMap(void); static void drawPulses(void); static void drawInfoBars(void); static void doPulses(void); static void addPulses(void); static void drawMenu(void); static void resume(void); static void stats(void); static void trophies(void); static void options(void); static void ok(void); static void quit(void); static void startMission(void); static void returnFromOptions(void); static void doStarSystemView(void); static void updatePandoranAdvance(void); static void fallenOK(void); static void updateCampaignProgress(void); static void campaignCompleteOK(void); static Mission *nextAvailableMission(StarSystem *starSystem); static void fighterDatabase(void); static StarSystem *selectedStarSystem; static SDL_Texture *background; static AtlasImage *starSystemTexture; static AtlasImage *arrowTexture; static SDL_Point camera; static Pulse pulseHead = {0}; static Pulse *pulseTail; static int pulseTimer; static float ssx, ssy; static float arrowPulse; static int show; static int scrollingMap; static int campaignComplete = 0; static PointF cameraMin, cameraMax; static Widget *startMissionButton; static Mission *hoverMission; static char *MISSIONS_TEXT; static char *PILOT_TEXT; static char *CRAFT_TEXT; static char *SQUADRON_TEXT; static char *COMPLETED_TEXT; static char *EPIC_TEXT; static char *OPTIONAL_TEXT; void initGalacticMap(void) { show = SHOW_GALAXY; startSectionTransition(); stopMusic(); app.delegate.logic = &logic; app.delegate.draw = &draw; memset(&app.keyboard, 0, sizeof(int) * MAX_KEYBOARD_KEYS); MISSIONS_TEXT = _("Missions: %d / %d"); PILOT_TEXT = _("Pilot: %s"); CRAFT_TEXT = _("Craft: %s"); SQUADRON_TEXT = _("Squadron: %s"); COMPLETED_TEXT = _("This mission has been completed."); EPIC_TEXT = _("Note: this is an epic mission."); OPTIONAL_TEXT = _("Note: this is an optional mission."); background = getTexture("gfx/backgrounds/background02.jpg"); starSystemTexture = getAtlasImage("gfx/galaxy/starSystem.png"); arrowTexture = getAtlasImage("gfx/galaxy/arrow.png"); selectedStarSystem = getStarSystem(game.selectedStarSystem); centerOnSelectedStarSystem(); updateAllMissions(); updatePandoranAdvance(); awardCampaignTrophies(); awardStatsTrophies(); updateCampaignProgress(); hoverMission = NULL; app.saveGame = 1; pulseTimer = 0; arrowPulse = 0; selectedStarSystem = NULL; /* clear the pulses */ destroyGalacticMap(); getWidget("close", "starSystem")->action = resume; startMissionButton = getWidget("startMission", "starSystem"); startMissionButton->action = startMission; getWidget("resume", "galacticMap")->action = resume; getWidget("stats", "galacticMap")->action = stats; getWidget("trophies", "galacticMap")->action = trophies; getWidget("fighterDB", "galacticMap")->action = fighterDatabase; getWidget("options", "galacticMap")->action = options; getWidget("quit", "galacticMap")->action = quit; getWidget("ok", "stats")->action = ok; getWidget("ok", "trophies")->action = ok; getWidget("ok", "fighterDB")->action = ok; getWidget("ok", "fallen")->action = fallenOK; autoSizeWidgetButtons("galacticMap", 1); endSectionTransition(); SDL_SetWindowGrab(app.window, 0); playMusic("music/main/Pressure.ogg", 1); } static void updateCampaignProgress(void) { StarSystem *starSystem; if (!campaignComplete && game.completedMissions == game.totalMissions) { for (starSystem = game.starSystemHead.next ; starSystem != NULL ; starSystem = starSystem->next) { if (starSystem->missionHead.next && starSystem->missionHead.next->available) { starSystem->activeMission = starSystem->missionHead.next; } } campaignComplete = 1; showOKDialog(&campaignCompleteOK, _("Congratulations, you have completed the campaign! You may now replay past missions, for fun, to boost stats, or to earn missing trophies!")); } } static void updatePandoranAdvance(void) { StarSystem *starSystem, *fallenStarSystem; fallenStarSystem = NULL; for (starSystem = game.starSystemHead.next ; starSystem != NULL ; starSystem = starSystem->next) { if (starSystem->side != SIDE_PANDORAN && starSystem->fallsToPandorans && starSystem->completedMissions == starSystem->totalMissions && starSystem->totalMissions > 0) { starSystem->side = SIDE_PANDORAN; fallenStarSystem = starSystem; } } if (fallenStarSystem) { showOKDialog(&fallenOK, _("%s has fallen to the Pandorans"), fallenStarSystem->name); } } static void logic(void) { handleKeyboard(); handleMouse(); switch (show) { case SHOW_GALAXY: doStarSystems(); scrollGalaxy(); scrollBackground(-ssx, -ssy); doStars(ssx, ssy); break; case SHOW_STAR_SYSTEM: doStarSystemView(); break; } doPulses(); if (pulseTimer % FPS == 0) { addPulses(); } pulseTimer++; pulseTimer %= (FPS * 60); arrowPulse += 0.1; doWidgets(); if (show == SHOW_FIGHTER_DB) { doFighterDatabase(); } app.doTrophyAlerts = 1; } static void doStarSystems(void) { StarSystem *starSystem; int cx, cy; if (!scrollingMap) { cx = app.mouse.x - 32; cy = app.mouse.y - 32; cameraMin.x = cameraMin.y = 99999; cameraMax.x = cameraMax.y = -99999; selectedStarSystem = NULL; for (starSystem = game.starSystemHead.next ; starSystem != NULL ; starSystem = starSystem->next) { cameraMin.x = MIN(cameraMin.x, starSystem->x); cameraMin.y = MIN(cameraMin.y, starSystem->y); cameraMax.x = MAX(cameraMax.x, starSystem->x); cameraMax.y = MAX(cameraMax.y, starSystem->y); if (starSystem->availableMissions > 0 && collision(cx, cy, 64, 64, starSystem->x - camera.x, starSystem->y - camera.y, 4, 4)) { if (selectedStarSystem != starSystem) { selectedStarSystem = starSystem; if (app.mouse.button[SDL_BUTTON_LEFT]) { selectStarSystem(); app.mouse.button[SDL_BUTTON_LEFT] = 0; } } } } cameraMin.x -= app.winWidth / 2; cameraMin.y -= app.winHeight / 2; cameraMax.x -= app.winWidth / 2; cameraMax.y -= app.winHeight / 2; } } static void scrollGalaxy(void) { int lastX, lastY; lastX = camera.x; lastY = camera.y; ssx = ssy = 0; if (scrollingMap) { camera.x -= app.mouse.dx * 1.5; camera.y -= app.mouse.dy * 1.5; ssx = -app.mouse.dx; ssx /= 3; ssy = -app.mouse.dy; ssy /= 3; camera.x = MAX(cameraMin.x, MIN(camera.x, cameraMax.x)); camera.y = MAX(cameraMin.y, MIN(camera.y, cameraMax.y)); if (lastX == camera.x) { ssx = 0; } if (lastY == camera.y) { ssy = 0; } } } static void doStarSystemView(void) { Mission *mission; for (mission = selectedStarSystem->missionHead.next ; mission != NULL ; mission = mission->next) { if (mission->available && collision(app.uiMouse.x - app.mouse.w / 2, app.uiMouse.y - app.mouse.h / 2, app.mouse.w, app.mouse.h, mission->rect.x, mission->rect.y, mission->rect.w, mission->rect.h)) { hoverMission = mission; if (app.mouse.button[SDL_BUTTON_LEFT]) { if (game.currentMission != mission) { playSound(SND_GUI_CLICK); } game.currentMission = mission; return; } } } /* allow closing by pressing the right mouse button */ if (app.mouse.button[SDL_BUTTON_RIGHT]) { show = SHOW_GALAXY; playSound(SND_GUI_CLOSE); } } static void addPulses(void) { Pulse *pulse; StarSystem *starSystem; for (starSystem = game.starSystemHead.next ; starSystem != NULL ; starSystem = starSystem->next) { if (starSystem->completedMissions < starSystem->availableMissions || (campaignComplete && starSystem->activeMission)) { pulse = malloc(sizeof(Pulse)); memset(pulse, 0, sizeof(Pulse)); pulse->x = starSystem->x; pulse->y = starSystem->y; pulse->life = 255; switch (starSystem->type) { case SS_NORMAL: if (!starSystem->activeMission->isOptional) { pulse->r = 255; } else { pulse->r = 128; pulse->g = 128; pulse->b = 255; } break; case SS_SOL: pulse->g = 255; break; case SS_PANDORAN: pulse->r = 128; pulse->g = 128; pulse->b = 255; break; } pulseTail->next = pulse; pulseTail = pulse; } } } static void doPulses(void) { Pulse *pulse, *prev; prev = &pulseHead; for (pulse = pulseHead.next ; pulse != NULL ; pulse = pulse->next) { pulse->size += 0.5; pulse->life--; if (pulse->life <= 0) { if (pulse == pulseTail) { pulseTail = prev; } prev->next = pulse->next; free(pulse); pulse = prev; } prev = pulse; } } static void draw(void) { drawBackground(background); drawStars(); drawGalaxy(); drawPulses(); drawInfoBars(); switch (show) { case SHOW_STAR_SYSTEM: drawStarSystemDetail(); break; case SHOW_MENU: drawMenu(); break; case SHOW_STATS: drawStats(); break; case SHOW_TROPHIES: drawTrophies(); break; case SHOW_OPTIONS: drawOptions(); break; case SHOW_FIGHTER_DB: drawFighterDatabase(); break; } } static void drawPulses(void) { Pulse *pulse; for (pulse = pulseHead.next ; pulse != NULL ; pulse = pulse->next) { drawCircle(pulse->x - camera.x, pulse->y - camera.y, pulse->size, pulse->r, pulse->g, pulse->b, pulse->life); } } static void centerOnSelectedStarSystem(void) { camera.x = selectedStarSystem->x; camera.x -= app.winWidth / 2; camera.y = selectedStarSystem->y; camera.y -= app.winHeight / 2; } static void drawGalaxy(void) { SDL_Rect r; StarSystem *starSystem; SDL_Color color; float ax, ay, aa; for (starSystem = game.starSystemHead.next ; starSystem != NULL ; starSystem = starSystem->next) { r.x = starSystem->x - camera.x; r.y = starSystem->y - camera.y; blit(starSystemTexture, r.x, r.y, 1); switch (starSystem->side) { case SIDE_CSN: color = colors.cyan; break; case SIDE_UNF: color = colors.white; break; case SIDE_PANDORAN: color = colors.red; break; } drawText(r.x, r.y + 12, 14, TA_CENTER, color, starSystem->name); if (starSystem->completedMissions < starSystem->availableMissions) { ax = r.x; ay = r.y; aa = -1; ax = MAX(MIN(app.winWidth - 64, ax), 64); ay = MAX(MIN(app.winHeight - 64, ay), 64); if (r.x < 0) { ax = 64 + (sin(arrowPulse) * 10); aa = 270; } else if (r.x > app.winWidth) { ax = app.winWidth - 64 + (sin(arrowPulse) * 10); aa = 90; } else if (r.y < 0) { ay = 64 + (sin(arrowPulse) * 10); aa = 0; } else if (r.y > app.winHeight) { ay = app.winHeight - 64 + (sin(arrowPulse) * 10); aa = 180; } if (aa != -1) { switch (starSystem->type) { case SS_NORMAL: setAtlasColor(255, 0, 0, 255); break; case SS_SOL: setAtlasColor(0, 255, 0, 255); break; case SS_PANDORAN: setAtlasColor(64, 128, 255, 255); break; } blitRotated(arrowTexture, ax, ay, aa); setAtlasColor(255, 255, 255, 255); } } } } static void drawInfoBars(void) { SDL_Rect r; if (show != SHOW_STAR_SYSTEM && selectedStarSystem != NULL) { r.x = 0; r.y = app.winHeight - 35; r.w = app.winWidth; r.h = 35; SDL_SetRenderDrawBlendMode(app.renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(app.renderer, 0, 0, 0, 200); SDL_RenderFillRect(app.renderer, &r); SDL_SetRenderDrawBlendMode(app.renderer, SDL_BLENDMODE_NONE); drawText(app.winWidth / 2, app.winHeight - 30, 18, TA_CENTER, colors.white, selectedStarSystem->description); } r.x = 0; r.y = 0; r.w = app.winWidth; r.h = 35; SDL_SetRenderDrawBlendMode(app.renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(app.renderer, 0, 0, 0, 200); SDL_RenderFillRect(app.renderer, &r); SDL_SetRenderDrawBlendMode(app.renderer, SDL_BLENDMODE_NONE); drawText((app.winWidth / 2), 5, 18, TA_CENTER, colors.white, MISSIONS_TEXT, game.completedMissions, game.availableMissions); } static void selectStarSystem(void) { if (selectedStarSystem->availableMissions > 0) { show = SHOW_STAR_SYSTEM; STRNCPY(game.selectedStarSystem, selectedStarSystem->name, MAX_NAME_LENGTH); game.currentMission = nextAvailableMission(selectedStarSystem); playSound(SND_GUI_SELECT); } } static Mission *nextAvailableMission(StarSystem *starSystem) { Mission *m; for (m = starSystem->missionHead.next ; m != NULL ; m = m->next) { if (m->available && !m->completed) { return m; } } return starSystem->missionHead.next; } static void drawStarSystemDetail(void) { int y; Mission *mission; SDL_Rect r; SDL_SetRenderTarget(app.renderer, app.uiBuffer); r.w = 900; r.h = 600; r.x = (UI_WIDTH / 2) - (r.w / 2); r.y = (UI_HEIGHT / 2) - (r.h / 2); SDL_SetRenderDrawBlendMode(app.renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(app.renderer, 0, 0, 0, 225); SDL_RenderFillRect(app.renderer, &r); SDL_SetRenderDrawColor(app.renderer, 255, 255, 255, 200); SDL_RenderDrawRect(app.renderer, &r); SDL_SetRenderDrawBlendMode(app.renderer, SDL_BLENDMODE_NONE); y = 70; drawText(UI_WIDTH / 2, y, 28, TA_CENTER, colors.cyan, selectedStarSystem->name); SDL_RenderDrawLine(app.renderer, r.x, 120, r.x + r.w - 1, 120); SDL_RenderDrawLine(app.renderer, 515, 120, 515, 659); y += 80; for (mission = selectedStarSystem->missionHead.next ; mission != NULL ; mission = mission->next) { mission->rect.x = 200; mission->rect.y = y - 2; mission->rect.w = 300; mission->rect.h = 38; if (mission == hoverMission) { SDL_SetRenderDrawColor(app.renderer, 16, 32, 64, 255); SDL_RenderFillRect(app.renderer, &mission->rect); SDL_SetRenderDrawColor(app.renderer, 32, 64, 128, 255); SDL_RenderDrawRect(app.renderer, &mission->rect); } if (mission == game.currentMission) { SDL_SetRenderDrawColor(app.renderer, 32, 64, 128, 255); SDL_RenderFillRect(app.renderer, &mission->rect); SDL_SetRenderDrawColor(app.renderer, 64, 96, 196, 255); SDL_RenderDrawRect(app.renderer, &mission->rect); } if (mission->available) { drawText(210, y, 22, TA_LEFT, mission->completed ? colors.lightGrey : colors.yellow, mission->name); y += 42; } } if (game.currentMission->available) { drawText(525, 135, 18, TA_LEFT, colors.lightGrey, PILOT_TEXT, game.currentMission->pilot); drawText(525, 160, 18, TA_LEFT, colors.lightGrey, CRAFT_TEXT, game.currentMission->craft); drawText(525, 185, 18, TA_LEFT, colors.lightGrey, SQUADRON_TEXT, game.currentMission->squadron); app.textWidth = 525; drawText(525, 230, 22, TA_LEFT, colors.white, game.currentMission->description); app.textWidth = 0; } if (game.currentMission->completed) { drawText(525, UI_HEIGHT - 95, 18, TA_LEFT, colors.green, COMPLETED_TEXT); } else if (game.currentMission->epic) { drawText(525, UI_HEIGHT - 95, 18, TA_LEFT, colors.yellow, EPIC_TEXT); } else if (game.currentMission->isOptional) { drawText(525, UI_HEIGHT - 95, 18, TA_LEFT, colors.cyan, OPTIONAL_TEXT); } startMissionButton->enabled = (!game.currentMission->completed || selectedStarSystem->type == SS_SOL || campaignComplete); drawWidgets("starSystem"); SDL_SetRenderTarget(app.renderer, app.backBuffer); } static void fallenOK(void) { show = SHOW_GALAXY; app.modalDialog.type = MD_NONE; } static void campaignCompleteOK(void) { show = SHOW_GALAXY; app.modalDialog.type = MD_NONE; campaignComplete = 2; } static void handleKeyboard(void) { if (app.keyboard[SDL_SCANCODE_ESCAPE] && !app.awaitingWidgetInput) { switch (show) { case SHOW_GALAXY: selectWidget("resume", "galacticMap"); show = SHOW_MENU; break; case SHOW_STAR_SYSTEM: show = SHOW_GALAXY; break; case SHOW_MENU: show = SHOW_GALAXY; break; case SHOW_OPTIONS: case SHOW_STATS: case SHOW_TROPHIES: case SHOW_FIGHTER_DB: show = SHOW_MENU; selectWidget("resume", "galacticMap"); break; } playSound(SND_GUI_CLOSE); clearInput(); } } static void handleMouse(void) { if (app.mouse.button[SDL_BUTTON_LEFT]) { if (app.mouse.dx != 0 || app.mouse.dy != 0) { scrollingMap = 1; } } else { scrollingMap = 0; } setMouseCursor(app.mouse.button[SDL_BUTTON_LEFT] && show == SHOW_GALAXY); } static void startMission(void) { initBattle(); loadMission(game.currentMission->filename); } static void drawMenu(void) { SDL_Rect r; SDL_SetRenderDrawBlendMode(app.renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(app.renderer, 0, 0, 0, 128); SDL_RenderFillRect(app.renderer, NULL); SDL_SetRenderDrawBlendMode(app.renderer, SDL_BLENDMODE_NONE); SDL_SetRenderTarget(app.renderer, app.uiBuffer); r.w = 400; r.h = 500; r.x = (UI_WIDTH / 2) - r.w / 2; r.y = (UI_HEIGHT / 2) - r.h / 2; SDL_SetRenderDrawColor(app.renderer, 0, 0, 0, 255); SDL_RenderFillRect(app.renderer, &r); SDL_SetRenderDrawColor(app.renderer, 200, 200, 200, 255); SDL_RenderDrawRect(app.renderer, &r); drawWidgets("galacticMap"); SDL_SetRenderTarget(app.renderer, app.backBuffer); } static void resume(void) { show = SHOW_GALAXY; } static void options(void) { show = SHOW_OPTIONS; initOptions(returnFromOptions); } static void fighterDatabase(void) { show = SHOW_FIGHTER_DB; initFighterDatabaseDisplay(); } static void stats(void) { selectWidget("ok", "stats"); show = SHOW_STATS; initStatsDisplay(); } static void trophies(void) { selectWidget("ok", "trophies"); show = SHOW_TROPHIES; initTrophiesDisplay(); } static void ok(void) { selectWidget("resume", "galacticMap"); show = SHOW_MENU; } static void returnFromOptions(void) { show = SHOW_MENU; selectWidget("resume", "galacticMap"); } static void quit(void) { initTitle(); } void destroyGalacticMap(void) { Pulse *pulse; while (pulseHead.next) { pulse = pulseHead.next; pulseHead.next = pulse->next; free(pulse); } pulseTail = &pulseHead; }