/* 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 "battle.h" static void logic(void); static void draw(void); static void handleKeyboard(void); static void postBattle(void); void destroyBattle(void); static void doBattle(void); static void optQuitBattle(void); static void quitBattle(void); static void drawMenu(void); static void continueGame(void); static void resume(void); static void restart(void); static void retry(void); static void start(void); static void options(void); static void returnFromOptions(void); static void checkSuspicionLevel(void); static void doTorelliFireStorm(void); static void endCampaign(void); static int show; static float ssx, ssy; void initBattle(void) { memset(&battle, 0, sizeof(Battle)); battle.bulletTail = &battle.bulletHead; battle.debrisTail = &battle.debrisHead; battle.entityTail = &battle.entityHead; battle.effectTail = &battle.effectHead; battle.objectiveTail = &battle.objectiveHead; battle.locationTail = &battle.locationHead; battle.spawnerTail = &battle.spawnerHead; app.delegate.logic = &logic; app.delegate.draw = &draw; memset(&app.keyboard, 0, sizeof(int) * MAX_KEYBOARD_KEYS); battle.hasThreats = 1; initQuadtree(&battle.quadtree); initBullets(); initEntities(); initStars(); initEffects(); initHud(); initRadar(); initMessageBox(); initDebris(); resetWaypoints(); show = SHOW_BATTLE; getWidget("ok", "startBattle")->action = start; getWidget("resume", "inBattle")->action = resume; getWidget("options", "inBattle")->action = options; getWidget("restart", "inBattle")->action = restart; getWidget("quit", "inBattle")->action = optQuitBattle; getWidget("continue", "battleWon")->action = continueGame; getWidget("retry", "battleWon")->action = retry; getWidget("retry", "battleLost")->action = retry; getWidget("quit", "battleLost")->action = quitBattle; selectWidget("ok", "startBattle"); SDL_SetWindowGrab(app.window, 1); } static void logic(void) { if (battle.status == MS_IN_PROGRESS || battle.status == MS_COMPLETE || battle.status == MS_FAILED || battle.status == MS_TIME_UP) { handleKeyboard(); if (show == SHOW_BATTLE) { if (!battle.isEpic || (battle.isEpic && !battle.playerSelect)) { doBattle(); } else if (battle.isEpic && battle.playerSelect) { doPlayerSelect(); } } app.doTrophyAlerts = (battle.status != MS_IN_PROGRESS && battle.missionFinishedTimer <= -FPS * 2); if (battle.campaignFinished) { endCampaign(); } } doWidgets(); } static void doBattle(void) { if (player->alive == ALIVE_ALIVE) { ssx = player->dx; ssy = player->dy; } else { ssx = ssy = 0; } scrollBackground(-ssx * 0.1, -ssy * 0.1); battle.planet.x -= ssx * 0.15; battle.planet.y -= ssy * 0.15; doObjectives(); doChallenges(); doHud(); doStars(ssx, ssy); doBullets(); doEntities(); doEffects(); doDebris(); doPlayer(); checkSuspicionLevel(); doTorelliFireStorm(); if (player->alive == ALIVE_ALIVE) { doSpawners(); doLocations(); doMessageBox(); if (battle.status == MS_IN_PROGRESS || battle.status == MS_COMPLETE) { doScript(); } if (battle.status == MS_IN_PROGRESS) { if (battle.stats[STAT_TIME]++ % FPS == 0) { runScriptFunction("TIME %d", battle.stats[STAT_TIME] / FPS); if (game.currentMission->challengeData.timeLimit && game.currentMission->challengeData.timeLimit - battle.stats[STAT_TIME] < 11 * FPS) { playSound(SND_TIME_WARNING); } } } } if (battle.status != MS_IN_PROGRESS) { battle.missionFinishedTimer--; if (battle.unwinnable && battle.missionFinishedTimer <= -FPS * 6) { battle.status = MS_COMPLETE; postBattle(); destroyBattle(); initGalacticMap(); } } } static void draw(void) { if (player->alive == ALIVE_ALIVE) { battle.camera.x = player->x - (app.winWidth / 2); battle.camera.y = player->y - (app.winHeight / 2); } drawBackground(battle.background); setAtlasColor(255, 255, 255, 255); blitScaled(battle.planetTexture, battle.planet.x, battle.planet.y, battle.planetWidth, battle.planetHeight, 0); if (battle.destroyTorelli) { SDL_SetRenderDrawBlendMode(app.renderer, SDL_BLENDMODE_BLEND); SDL_SetTextureAlphaMod(battle.fireStormTexture->texture, battle.torelliFireStormAlpha); blitScaled(battle.fireStormTexture, battle.planet.x, battle.planet.y, battle.planetWidth, battle.planetHeight, 0); SDL_SetRenderDrawBlendMode(app.renderer, SDL_BLENDMODE_NONE); } drawStars(); drawEntities(); drawBullets(); drawDebris(); drawEffects(); if (dev.debug) { drawLocations(); } drawHud(); if (player->alive == ALIVE_ALIVE) { drawMessageBox(); } drawMissionInfo(); switch (show) { case SHOW_MENU: drawMenu(); break; case SHOW_OPTIONS: drawOptions(); break; } } static void drawMenu(void) { SDL_Rect r; if (app.modalDialog.type == MD_NONE) { 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 = 400; 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("inBattle"); SDL_SetRenderTarget(app.renderer, app.backBuffer); } } static void handleKeyboard(void) { if (app.keyboard[SDL_SCANCODE_ESCAPE] && !app.awaitingWidgetInput && battle.status == MS_IN_PROGRESS) { switch (show) { case SHOW_BATTLE: case SHOW_OPTIONS: selectWidget("resume", "inBattle"); show = SHOW_MENU; SDL_SetWindowGrab(app.window, 0); break; case SHOW_MENU: show = SHOW_BATTLE; SDL_SetWindowGrab(app.window, 1); break; case SHOW_OBJECTIVES: show = SHOW_BATTLE; SDL_SetWindowGrab(app.window, 1); break; } clearInput(); playSound(SND_GUI_CLOSE); } if (battle.status == MS_IN_PROGRESS && app.keyboard[SDL_SCANCODE_TAB]) { battle.status = MS_PAUSED; selectWidget("ok", "startBattle"); SDL_SetWindowGrab(app.window, 0); } } static void start(void) { battle.status = MS_IN_PROGRESS; SDL_SetWindowGrab(app.window, 1); } static void resume(void) { show = SHOW_BATTLE; SDL_SetWindowGrab(app.window, 1); clearInput(); } static void continueGame(void) { postBattle(); destroyBattle(); if (!game.currentMission->challengeData.isChallenge) { initGalacticMap(); } else { initChallengeHome(); } } static void options(void) { show = SHOW_OPTIONS; initOptions(returnFromOptions); } static void returnFromOptions(void) { show = SHOW_MENU; selectWidget("resume", "inBattle"); } static void ignoreRestartQuit(void) { app.modalDialog.type = MD_NONE; } static void restart(void) { showOKCancelDialog(&retry, &ignoreRestartQuit, _("Are you sure you want to restart? You will lose your current progress.")); } static void retry(void) { app.modalDialog.type = MD_NONE; postBattle(); destroyBattle(); initBattle(); loadMission(game.currentMission->filename); } static void optQuitBattle(void) { showOKCancelDialog(&quitBattle, &ignoreRestartQuit, _("Are you sure you want to quit? You will lose your current progress.")); } static void quitBattle(void) { app.modalDialog.type = MD_NONE; postBattle(); destroyBattle(); if (!game.currentMission->challengeData.isChallenge) { initGalacticMap(); } else { initChallengeHome(); } } static void postBattle(void) { int i; for (i = 0 ; i < STAT_MAX ; i++) { if (i != STAT_TIME && i != STAT_EPIC_KILL_STREAK) { game.stats[i] += battle.stats[i]; } } game.stats[STAT_EPIC_KILL_STREAK] = MAX(game.stats[STAT_EPIC_KILL_STREAK], battle.stats[STAT_EPIC_KILL_STREAK]); updateAccuracyStats(game.stats); game.currentMission->completed = (game.currentMission->completed || battle.status == MS_COMPLETE || !battle.numObjectivesTotal); app.saveGame = 1; } static void checkSuspicionLevel(void) { if (battle.hasSuspicionLevel && battle.suspicionLevel >= MAX_SUSPICION_LEVEL) { cancelScript(); resetMessageBox(); runScriptFunction("MAX_SUSPICION_LEVEL"); battle.hasSuspicionLevel = 0; } } static void doTorelliFireStorm(void) { if (battle.destroyTorelli) { battle.torelliFireStormAlpha = MIN(battle.torelliFireStormAlpha + 0.25, 255); } } static void endCampaign(void) { awardTrophy("CAMPAIGN_COMPLETE"); postBattle(); destroyBattle(); initCredits(); } void destroyBattle(void) { Entity *ent; Bullet *b; Debris *d; Effect *e; Objective *o; Location *l; Spawner *s; while (battle.entityHead.next) { ent = battle.entityHead.next; battle.entityHead.next = ent->next; free(ent); } battle.entityTail = &battle.entityHead; while (battle.bulletHead.next) { b = battle.bulletHead.next; battle.bulletHead.next = b->next; free(b); } battle.bulletTail = &battle.bulletHead; while (battle.debrisHead.next) { d = battle.debrisHead.next; battle.debrisHead.next = d->next; free(d); } battle.debrisTail = &battle.debrisHead; while (battle.effectHead.next) { e = battle.effectHead.next; battle.effectHead.next = e->next; free(e); } battle.effectTail = &battle.effectHead; while (battle.objectiveHead.next) { o = battle.objectiveHead.next; battle.objectiveHead.next = o->next; free(o); } battle.objectiveTail = &battle.objectiveHead; while (battle.locationHead.next) { l = battle.locationHead.next; battle.locationHead.next = l->next; free(l); } battle.locationTail = &battle.locationHead; while (battle.spawnerHead.next) { s = battle.spawnerHead.next; battle.spawnerHead.next = s->next; free(s); } battle.spawnerTail = &battle.spawnerHead; cJSON_Delete(battle.missionJSON); resetHud(); resetMessageBox(); destroyEntities(); destroyScript(); destroyQuadtree(); destroyDebris(); destroyBullets(); destroyEffects(); memset(&battle, 0, sizeof(Battle)); battle.bulletTail = &battle.bulletHead; battle.debrisTail = &battle.debrisHead; battle.entityTail = &battle.entityHead; battle.effectTail = &battle.effectHead; battle.objectiveTail = &battle.objectiveHead; battle.locationTail = &battle.locationHead; battle.spawnerTail = &battle.spawnerHead; }