1 /*
2 * Copyright 2010-2014 OpenXcom Developers.
3 *
4 * This file is part of OpenXcom.
5 *
6 * OpenXcom is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * OpenXcom is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with OpenXcom. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #define _USE_MATH_DEFINES
20 #include "GeoscapeState.h"
21 #include <cmath>
22 #include <sstream>
23 #include <iomanip>
24 #include <ctime>
25 #include <algorithm>
26 #include <functional>
27 #include <assert.h>
28 #include "../Engine/RNG.h"
29 #include "../Engine/Game.h"
30 #include "../Engine/Action.h"
31 #include "../Resource/ResourcePack.h"
32 #include "../Engine/Language.h"
33 #include "../Engine/Palette.h"
34 #include "../Engine/Screen.h"
35 #include "../Engine/Surface.h"
36 #include "../Engine/Options.h"
37 #include "Globe.h"
38 #include "../Interface/Text.h"
39 #include "../Interface/TextButton.h"
40 #include "../Interface/Cursor.h"
41 #include "../Interface/FpsCounter.h"
42 #include "../Engine/Timer.h"
43 #include "../Savegame/GameTime.h"
44 #include "../Engine/Music.h"
45 #include "../Savegame/SavedGame.h"
46 #include "../Ruleset/Ruleset.h"
47 #include "../Savegame/Base.h"
48 #include "../Savegame/BaseFacility.h"
49 #include "../Ruleset/RuleBaseFacility.h"
50 #include "../Savegame/Craft.h"
51 #include "../Ruleset/RuleCraft.h"
52 #include "../Savegame/Ufo.h"
53 #include "../Ruleset/RuleUfo.h"
54 #include "../Savegame/Waypoint.h"
55 #include "../Savegame/Transfer.h"
56 #include "../Savegame/Soldier.h"
57 #include "../Savegame/SoldierDeath.h"
58 #include "../Menu/PauseState.h"
59 #include "InterceptState.h"
60 #include "../Basescape/BasescapeState.h"
61 #include "../Basescape/SellState.h"
62 #include "../Menu/ErrorMessageState.h"
63 #include "GraphsState.h"
64 #include "FundingState.h"
65 #include "MonthlyReportState.h"
66 #include "ProductionCompleteState.h"
67 #include "UfoDetectedState.h"
68 #include "GeoscapeCraftState.h"
69 #include "DogfightState.h"
70 #include "UfoLostState.h"
71 #include "CraftPatrolState.h"
72 #include "LowFuelState.h"
73 #include "MultipleTargetsState.h"
74 #include "ConfirmLandingState.h"
75 #include "ItemsArrivingState.h"
76 #include "CraftErrorState.h"
77 #include "../Ufopaedia/Ufopaedia.h"
78 #include "../Savegame/ResearchProject.h"
79 #include "ResearchCompleteState.h"
80 #include "../Ruleset/RuleResearch.h"
81 #include "ResearchRequiredState.h"
82 #include "NewPossibleResearchState.h"
83 #include "NewPossibleManufactureState.h"
84 #include "../Savegame/Production.h"
85 #include "../Ruleset/RuleManufacture.h"
86 #include "../Savegame/ItemContainer.h"
87 #include "../Savegame/TerrorSite.h"
88 #include "../Savegame/AlienBase.h"
89 #include "../Ruleset/RuleRegion.h"
90 #include "../Ruleset/City.h"
91 #include "AlienTerrorState.h"
92 #include "AlienBaseState.h"
93 #include "../Savegame/Region.h"
94 #include "../Savegame/Country.h"
95 #include "../Ruleset/RuleCountry.h"
96 #include "../Ruleset/RuleAlienMission.h"
97 #include "../Savegame/AlienStrategy.h"
98 #include "../Savegame/AlienMission.h"
99 #include "../Savegame/SavedBattleGame.h"
100 #include "../Battlescape/BattlescapeGenerator.h"
101 #include "../Battlescape/BriefingState.h"
102 #include "../Ruleset/UfoTrajectory.h"
103 #include "../Ruleset/Armor.h"
104 #include "BaseDefenseState.h"
105 #include "BaseDestroyedState.h"
106 #include "DefeatState.h"
107 #include "../Menu/LoadGameState.h"
108 #include "../Menu/SaveGameState.h"
109 #include "../Menu/ListSaveState.h"
110 #include "../Ruleset/AlienRace.h"
111
112 namespace OpenXcom
113 {
114
115 /**
116 * Initializes all the elements in the Geoscape screen.
117 * @param game Pointer to the core game.
118 */
GeoscapeState(Game * game)119 GeoscapeState::GeoscapeState(Game *game) : State(game), _pause(false), _zoomInEffectDone(false), _zoomOutEffectDone(false), _popups(), _dogfights(), _dogfightsToBeStarted(), _minimizedDogfights(0)
120 {
121 int screenWidth = Options::baseXGeoscape;
122 int screenHeight = Options::baseYGeoscape;
123
124 // Create objects
125 Surface *hd = _game->getResourcePack()->getSurface("ALTGEOBORD.SCR");
126 _bg = new Surface(hd->getWidth(), hd->getHeight(), 0, 0);
127 _sideLine = new Surface(64, screenHeight, screenWidth - 64, 0);
128 _sidebar = new Surface(64, 200, screenWidth - 64, screenHeight / 2 - 100);
129
130 _globe = new Globe(_game, (screenWidth-64)/2, screenHeight/2, screenWidth-64, screenHeight, 0, 0);
131 _bg->setX((_globe->getWidth() - _bg->getWidth()) / 2);
132 _bg->setY((_globe->getHeight() - _bg->getHeight()) / 2);
133
134 _btnIntercept = new TextButton(63, 11, screenWidth-63, screenHeight/2-100);
135 _btnBases = new TextButton(63, 11, screenWidth-63, screenHeight/2-88);
136 _btnGraphs = new TextButton(63, 11, screenWidth-63, screenHeight/2-76);
137 _btnUfopaedia = new TextButton(63, 11, screenWidth-63, screenHeight/2-64);
138 _btnOptions = new TextButton(63, 11, screenWidth-63, screenHeight/2-52);
139 _btnFunding = new TextButton(63, 11, screenWidth-63, screenHeight/2-40);
140
141 _btn5Secs = new TextButton(31, 13, screenWidth-63, screenHeight/2+12);
142 _btn1Min = new TextButton(31, 13, screenWidth-31, screenHeight/2+12);
143 _btn5Mins = new TextButton(31, 13, screenWidth-63, screenHeight/2+26);
144 _btn30Mins = new TextButton(31, 13, screenWidth-31, screenHeight/2+26);
145 _btn1Hour = new TextButton(31, 13, screenWidth-63, screenHeight/2+40);
146 _btn1Day = new TextButton(31, 13, screenWidth-31, screenHeight/2+40);
147
148 _btnRotateLeft = new InteractiveSurface(12, 10, screenWidth-61, screenHeight/2+76);
149 _btnRotateRight = new InteractiveSurface(12, 10, screenWidth-37, screenHeight/2+76);
150 _btnRotateUp = new InteractiveSurface(13, 12, screenWidth-49, screenHeight/2+62);
151 _btnRotateDown = new InteractiveSurface(13, 12, screenWidth-49, screenHeight/2+87);
152 _btnZoomIn = new InteractiveSurface(23, 23, screenWidth-25, screenHeight/2+56);
153 _btnZoomOut = new InteractiveSurface(13, 17, screenWidth-20, screenHeight/2+82);
154
155 int height = (screenHeight - Screen::ORIGINAL_HEIGHT) / 2 + 10;
156 _sideTop = new TextButton(63, height, screenWidth-63, _sidebar->getY() - height - 1);
157 _sideBottom = new TextButton(63, height, screenWidth-63, _sidebar->getY() + _sidebar->getHeight() + 1);
158
159 _txtHour = new Text(20, 16, screenWidth-61, screenHeight/2-26);
160 _txtHourSep = new Text(4, 16, screenWidth-41, screenHeight/2-26);
161 _txtMin = new Text(20, 16, screenWidth-37, screenHeight/2-26);
162 _txtMinSep = new Text(4, 16, screenWidth-17, screenHeight/2-26);
163 _txtSec = new Text(11, 8, screenWidth-13, screenHeight/2-20);
164 _txtWeekday = new Text(59, 8, screenWidth-61, screenHeight/2-13);
165 _txtDay = new Text(29, 8, screenWidth-61, screenHeight/2-6);
166 _txtMonth = new Text(29, 8, screenWidth-32, screenHeight/2-6);
167 _txtYear = new Text(59, 8, screenWidth-61, screenHeight/2+1);
168 _txtFunds = new Text(59, 8, screenWidth-61, screenHeight/2-27);
169
170 _timeSpeed = _btn5Secs;
171 _gameTimer = new Timer(Options::geoClockSpeed);
172
173 _zoomInEffectTimer = new Timer(Options::dogfightSpeed+10);
174 _zoomOutEffectTimer = new Timer(Options::dogfightSpeed+10);
175 _dogfightStartTimer = new Timer(Options::dogfightSpeed+10);
176
177 _txtDebug = new Text(200, 18, 0, 0);
178
179 // Set palette
180 setPalette("PAL_GEOSCAPE");
181
182 // Fix system colors
183 _game->getCursor()->setColor(Palette::blockOffset(15)+12);
184 _game->getFpsCounter()->setColor(Palette::blockOffset(15)+12);
185
186 add(_bg);
187 add(_sideLine);
188 add(_sidebar);
189 add(_globe);
190
191 add(_btnIntercept);
192 add(_btnBases);
193 add(_btnGraphs);
194 add(_btnUfopaedia);
195 add(_btnOptions);
196 add(_btnFunding);
197
198 add(_btn5Secs);
199 add(_btn1Min);
200 add(_btn5Mins);
201 add(_btn30Mins);
202 add(_btn1Hour);
203 add(_btn1Day);
204
205 add(_btnRotateLeft);
206 add(_btnRotateRight);
207 add(_btnRotateUp);
208 add(_btnRotateDown);
209 add(_btnZoomIn);
210 add(_btnZoomOut);
211
212 add(_sideTop);
213 add(_sideBottom);
214
215 add(_txtFunds);
216 add(_txtHour);
217 add(_txtHourSep);
218 add(_txtMin);
219 add(_txtMinSep);
220 add(_txtSec);
221 add(_txtWeekday);
222 add(_txtDay);
223 add(_txtMonth);
224 add(_txtYear);
225
226 add(_txtDebug);
227
228 // Set up objects
229 Surface *geobord = _game->getResourcePack()->getSurface("GEOBORD.SCR");
230 geobord->setX(_sidebar->getX() - geobord->getWidth() + _sidebar->getWidth());
231 geobord->setY(_sidebar->getY());
232 _sidebar->copy(geobord);
233 _game->getResourcePack()->getSurface("ALTGEOBORD.SCR")->blit(_bg);
234
235 _sideLine->drawRect(0, 0, _sideLine->getWidth(), _sideLine->getHeight(), 15);
236
237 _btnIntercept->initText(_game->getResourcePack()->getFont("FONT_GEO_BIG"), _game->getResourcePack()->getFont("FONT_GEO_SMALL"), _game->getLanguage());
238 _btnIntercept->setColor(Palette::blockOffset(15)+6);
239 _btnIntercept->setTextColor(Palette::blockOffset(15)+5);
240 _btnIntercept->setText(tr("STR_INTERCEPT"));
241 _btnIntercept->onMouseClick((ActionHandler)&GeoscapeState::btnInterceptClick);
242 _btnIntercept->onKeyboardPress((ActionHandler)&GeoscapeState::btnInterceptClick, Options::keyGeoIntercept);
243
244 _btnBases->initText(_game->getResourcePack()->getFont("FONT_GEO_BIG"), _game->getResourcePack()->getFont("FONT_GEO_SMALL"), _game->getLanguage());
245 _btnBases->setColor(Palette::blockOffset(15)+6);
246 _btnBases->setTextColor(Palette::blockOffset(15)+5);
247 _btnBases->setText(tr("STR_BASES"));
248 _btnBases->onMouseClick((ActionHandler)&GeoscapeState::btnBasesClick);
249 _btnBases->onKeyboardPress((ActionHandler)&GeoscapeState::btnBasesClick, Options::keyGeoBases);
250
251 _btnGraphs->initText(_game->getResourcePack()->getFont("FONT_GEO_BIG"), _game->getResourcePack()->getFont("FONT_GEO_SMALL"), _game->getLanguage());
252 _btnGraphs->setColor(Palette::blockOffset(15)+6);
253 _btnGraphs->setTextColor(Palette::blockOffset(15)+5);
254 _btnGraphs->setText(tr("STR_GRAPHS"));
255 _btnGraphs->onMouseClick((ActionHandler)&GeoscapeState::btnGraphsClick);
256 _btnGraphs->onKeyboardPress((ActionHandler)&GeoscapeState::btnGraphsClick, Options::keyGeoGraphs);
257
258 _btnUfopaedia->initText(_game->getResourcePack()->getFont("FONT_GEO_BIG"), _game->getResourcePack()->getFont("FONT_GEO_SMALL"), _game->getLanguage());
259 _btnUfopaedia->setColor(Palette::blockOffset(15)+6);
260 _btnUfopaedia->setTextColor(Palette::blockOffset(15)+5);
261 _btnUfopaedia->setText(tr("STR_UFOPAEDIA_UC"));
262 _btnUfopaedia->onMouseClick((ActionHandler)&GeoscapeState::btnUfopaediaClick);
263 _btnUfopaedia->onKeyboardPress((ActionHandler)&GeoscapeState::btnUfopaediaClick, Options::keyGeoUfopedia);
264
265 _btnOptions->initText(_game->getResourcePack()->getFont("FONT_GEO_BIG"), _game->getResourcePack()->getFont("FONT_GEO_SMALL"), _game->getLanguage());
266 _btnOptions->setColor(Palette::blockOffset(15)+6);
267 _btnOptions->setTextColor(Palette::blockOffset(15)+5);
268 _btnOptions->setText(tr("STR_OPTIONS_UC"));
269 _btnOptions->onMouseClick((ActionHandler)&GeoscapeState::btnOptionsClick);
270 _btnOptions->onKeyboardPress((ActionHandler)&GeoscapeState::btnOptionsClick, Options::keyGeoOptions);
271
272 _btnFunding->initText(_game->getResourcePack()->getFont("FONT_GEO_BIG"), _game->getResourcePack()->getFont("FONT_GEO_SMALL"), _game->getLanguage());
273 _btnFunding->setColor(Palette::blockOffset(15)+6);
274 _btnFunding->setTextColor(Palette::blockOffset(15)+5);
275 _btnFunding->setText(tr("STR_FUNDING_UC"));
276 _btnFunding->onMouseClick((ActionHandler)&GeoscapeState::btnFundingClick);
277 _btnFunding->onKeyboardPress((ActionHandler)&GeoscapeState::btnFundingClick, Options::keyGeoFunding);
278
279 _btn5Secs->initText(_game->getResourcePack()->getFont("FONT_GEO_BIG"), _game->getResourcePack()->getFont("FONT_GEO_SMALL"), _game->getLanguage());
280 _btn5Secs->setBig();
281 _btn5Secs->setColor(Palette::blockOffset(15)+6);
282 _btn5Secs->setTextColor(Palette::blockOffset(15)+5);
283 _btn5Secs->setText(tr("STR_5_SECONDS"));
284 _btn5Secs->setGroup(&_timeSpeed);
285 _btn5Secs->onKeyboardPress((ActionHandler)&GeoscapeState::btnTimerClick, Options::keyGeoSpeed1);
286
287 _btn1Min->initText(_game->getResourcePack()->getFont("FONT_GEO_BIG"), _game->getResourcePack()->getFont("FONT_GEO_SMALL"), _game->getLanguage());
288 _btn1Min->setBig();
289 _btn1Min->setColor(Palette::blockOffset(15)+6);
290 _btn1Min->setTextColor(Palette::blockOffset(15)+5);
291 _btn1Min->setText(tr("STR_1_MINUTE"));
292 _btn1Min->setGroup(&_timeSpeed);
293 _btn1Min->onKeyboardPress((ActionHandler)&GeoscapeState::btnTimerClick, Options::keyGeoSpeed2);
294
295 _btn5Mins->initText(_game->getResourcePack()->getFont("FONT_GEO_BIG"), _game->getResourcePack()->getFont("FONT_GEO_SMALL"), _game->getLanguage());
296 _btn5Mins->setBig();
297 _btn5Mins->setColor(Palette::blockOffset(15)+6);
298 _btn5Mins->setTextColor(Palette::blockOffset(15)+5);
299 _btn5Mins->setText(tr("STR_5_MINUTES"));
300 _btn5Mins->setGroup(&_timeSpeed);
301 _btn5Mins->onKeyboardPress((ActionHandler)&GeoscapeState::btnTimerClick, Options::keyGeoSpeed3);
302
303 _btn30Mins->initText(_game->getResourcePack()->getFont("FONT_GEO_BIG"), _game->getResourcePack()->getFont("FONT_GEO_SMALL"), _game->getLanguage());
304 _btn30Mins->setBig();
305 _btn30Mins->setColor(Palette::blockOffset(15)+6);
306 _btn30Mins->setTextColor(Palette::blockOffset(15)+5);
307 _btn30Mins->setText(tr("STR_30_MINUTES"));
308 _btn30Mins->setGroup(&_timeSpeed);
309 _btn30Mins->onKeyboardPress((ActionHandler)&GeoscapeState::btnTimerClick, Options::keyGeoSpeed4);
310
311 _btn1Hour->initText(_game->getResourcePack()->getFont("FONT_GEO_BIG"), _game->getResourcePack()->getFont("FONT_GEO_SMALL"), _game->getLanguage());
312 _btn1Hour->setBig();
313 _btn1Hour->setColor(Palette::blockOffset(15)+6);
314 _btn1Hour->setTextColor(Palette::blockOffset(15)+5);
315 _btn1Hour->setText(tr("STR_1_HOUR"));
316 _btn1Hour->setGroup(&_timeSpeed);
317 _btn1Hour->onKeyboardPress((ActionHandler)&GeoscapeState::btnTimerClick, Options::keyGeoSpeed5);
318
319 _btn1Day->initText(_game->getResourcePack()->getFont("FONT_GEO_BIG"), _game->getResourcePack()->getFont("FONT_GEO_SMALL"), _game->getLanguage());
320 _btn1Day->setBig();
321 _btn1Day->setColor(Palette::blockOffset(15)+6);
322 _btn1Day->setTextColor(Palette::blockOffset(15)+5);
323 _btn1Day->setText(tr("STR_1_DAY"));
324 _btn1Day->setGroup(&_timeSpeed);
325 _btn1Day->onKeyboardPress((ActionHandler)&GeoscapeState::btnTimerClick, Options::keyGeoSpeed6);
326
327 _btnRotateLeft->onMousePress((ActionHandler)&GeoscapeState::btnRotateLeftPress);
328 _btnRotateLeft->onMouseRelease((ActionHandler)&GeoscapeState::btnRotateLeftRelease);
329 _btnRotateLeft->onKeyboardPress((ActionHandler)&GeoscapeState::btnRotateLeftPress, Options::keyGeoLeft);
330 _btnRotateLeft->onKeyboardRelease((ActionHandler)&GeoscapeState::btnRotateLeftRelease, Options::keyGeoLeft);
331
332 _btnRotateRight->onMousePress((ActionHandler)&GeoscapeState::btnRotateRightPress);
333 _btnRotateRight->onMouseRelease((ActionHandler)&GeoscapeState::btnRotateRightRelease);
334 _btnRotateRight->onKeyboardPress((ActionHandler)&GeoscapeState::btnRotateRightPress, Options::keyGeoRight);
335 _btnRotateRight->onKeyboardRelease((ActionHandler)&GeoscapeState::btnRotateRightRelease, Options::keyGeoRight);
336
337 _btnRotateUp->onMousePress((ActionHandler)&GeoscapeState::btnRotateUpPress);
338 _btnRotateUp->onMouseRelease((ActionHandler)&GeoscapeState::btnRotateUpRelease);
339 _btnRotateUp->onKeyboardPress((ActionHandler)&GeoscapeState::btnRotateUpPress, Options::keyGeoUp);
340 _btnRotateUp->onKeyboardRelease((ActionHandler)&GeoscapeState::btnRotateUpRelease, Options::keyGeoUp);
341
342 _btnRotateDown->onMousePress((ActionHandler)&GeoscapeState::btnRotateDownPress);
343 _btnRotateDown->onMouseRelease((ActionHandler)&GeoscapeState::btnRotateDownRelease);
344 _btnRotateDown->onKeyboardPress((ActionHandler)&GeoscapeState::btnRotateDownPress, Options::keyGeoDown);
345 _btnRotateDown->onKeyboardRelease((ActionHandler)&GeoscapeState::btnRotateDownRelease, Options::keyGeoDown);
346
347 _btnZoomIn->onMouseClick((ActionHandler)&GeoscapeState::btnZoomInLeftClick, SDL_BUTTON_LEFT);
348 _btnZoomIn->onMouseClick((ActionHandler)&GeoscapeState::btnZoomInRightClick, SDL_BUTTON_RIGHT);
349 _btnZoomIn->onKeyboardPress((ActionHandler)&GeoscapeState::btnZoomInLeftClick, Options::keyGeoZoomIn);
350
351 _btnZoomOut->onMouseClick((ActionHandler)&GeoscapeState::btnZoomOutLeftClick, SDL_BUTTON_LEFT);
352 _btnZoomOut->onMouseClick((ActionHandler)&GeoscapeState::btnZoomOutRightClick, SDL_BUTTON_RIGHT);
353 _btnZoomOut->onKeyboardPress((ActionHandler)&GeoscapeState::btnZoomOutLeftClick, Options::keyGeoZoomOut);
354
355 _sideTop->setColor(Palette::blockOffset(15)+6);
356 _sideBottom->setColor(Palette::blockOffset(15)+6);
357
358 _txtFunds->setColor(Palette::blockOffset(15)+4);
359 _txtFunds->setAlign(ALIGN_CENTER);
360 _txtFunds->setVisible(Options::showFundsOnGeoscape);
361
362 _txtHour->setBig();
363 _txtHour->setColor(Palette::blockOffset(15)+4);
364 _txtHour->setAlign(ALIGN_RIGHT);
365
366 _txtHourSep->setBig();
367 _txtHourSep->setColor(Palette::blockOffset(15)+4);
368 _txtHourSep->setText(L":");
369
370 _txtMin->setBig();
371 _txtMin->setColor(Palette::blockOffset(15)+4);
372
373 _txtMinSep->setBig();
374 _txtMinSep->setColor(Palette::blockOffset(15)+4);
375 _txtMinSep->setText(L":");
376
377 _txtSec->setColor(Palette::blockOffset(15)+4);
378
379 _txtWeekday->setColor(Palette::blockOffset(15)+4);
380 _txtWeekday->setAlign(ALIGN_CENTER);
381
382 _txtDay->setColor(Palette::blockOffset(15)+4);
383 _txtDay->setAlign(ALIGN_CENTER);
384
385 _txtMonth->setColor(Palette::blockOffset(15)+4);
386 _txtMonth->setAlign(ALIGN_CENTER);
387
388 _txtYear->setColor(Palette::blockOffset(15)+4);
389 _txtYear->setAlign(ALIGN_CENTER);
390
391 _txtDebug->setColor(Palette::blockOffset(15)+4);
392
393 if (Options::showFundsOnGeoscape)
394 {
395 _txtHour->setY(_txtHour->getY()+6);
396 _txtHour->setSmall();
397 _txtHourSep->setY(_txtHourSep->getY()+6);
398 _txtHourSep->setSmall();
399 _txtMin->setY(_txtMin->getY()+6);
400 _txtMin->setSmall();
401 _txtMinSep->setX(_txtMinSep->getX()-10);
402 _txtMinSep->setY(_txtMinSep->getY()+6);
403 _txtMinSep->setSmall();
404 _txtSec->setX(_txtSec->getX()-10);
405 }
406
407 _gameTimer->onTimer((StateHandler)&GeoscapeState::timeAdvance);
408 _gameTimer->start();
409
410 _zoomInEffectTimer->onTimer((StateHandler)&GeoscapeState::zoomInEffect);
411 _zoomOutEffectTimer->onTimer((StateHandler)&GeoscapeState::zoomOutEffect);
412 _dogfightStartTimer->onTimer((StateHandler)&GeoscapeState::startDogfight);
413
414 timeDisplay();
415 }
416
417 /**
418 * Deletes timers.
419 */
~GeoscapeState()420 GeoscapeState::~GeoscapeState()
421 {
422 delete _gameTimer;
423 delete _zoomInEffectTimer;
424 delete _zoomOutEffectTimer;
425 delete _dogfightStartTimer;
426
427 std::list<DogfightState*>::iterator it = _dogfights.begin();
428 for(; it != _dogfights.end();)
429 {
430 delete *it;
431 it = _dogfights.erase(it);
432 }
433 for(it = _dogfightsToBeStarted.begin(); it != _dogfightsToBeStarted.end();)
434 {
435 delete *it;
436 it = _dogfightsToBeStarted.erase(it);
437 }
438 }
439
440 /**
441 * Handle blitting of Geoscape and Dogfights.
442 */
blit()443 void GeoscapeState::blit()
444 {
445 State::blit();
446 for(std::list<DogfightState*>::iterator it = _dogfights.begin(); it != _dogfights.end(); ++it)
447 {
448 (*it)->blit();
449 }
450 }
451 /**
452 * Handle key shortcuts.
453 * @param action Pointer to an action.
454 */
handle(Action * action)455 void GeoscapeState::handle(Action *action)
456 {
457 if (_dogfights.size() == _minimizedDogfights)
458 {
459 State::handle(action);
460 }
461
462 if (action->getDetails()->type == SDL_KEYDOWN)
463 {
464 // "ctrl-d" - enable debug mode
465 if (Options::debug && action->getDetails()->key.keysym.sym == SDLK_d && (SDL_GetModState() & KMOD_CTRL) != 0)
466 {
467 _game->getSavedGame()->setDebugMode();
468 if (_game->getSavedGame()->getDebugMode())
469 {
470 _txtDebug->setText(L"DEBUG MODE");
471 }
472 else
473 {
474 _txtDebug->setText(L"");
475 }
476 }
477 // quick save and quick load
478 else if (!_game->getSavedGame()->isIronman())
479 {
480 if (action->getDetails()->key.keysym.sym == Options::keyQuickSave)
481 {
482 popup(new SaveGameState(_game, OPT_GEOSCAPE, SAVE_QUICK));
483 }
484 else if (action->getDetails()->key.keysym.sym == Options::keyQuickLoad)
485 {
486 popup(new LoadGameState(_game, OPT_GEOSCAPE, SAVE_QUICK));
487 }
488 }
489 }
490 if (!_dogfights.empty())
491 {
492 for(std::list<DogfightState*>::iterator it = _dogfights.begin(); it != _dogfights.end(); ++it)
493 {
494 (*it)->handle(action);
495 }
496 _minimizedDogfights = minimizedDogfightsCount();
497 }
498 }
499
500 /**
501 * Updates the timer display and resets the palette
502 * since it's bound to change on other screens.
503 */
init()504 void GeoscapeState::init()
505 {
506 State::init();
507 timeDisplay();
508
509 _globe->onMouseClick((ActionHandler)&GeoscapeState::globeClick);
510 _globe->onMouseOver(0);
511 _globe->rotateStop();
512 _globe->setFocus(true);
513 _globe->draw();
514
515 // Pop up save screen if it's a new ironman game
516 if (_game->getSavedGame()->isIronman() && _game->getSavedGame()->getName().empty())
517 {
518 popup(new ListSaveState(_game, OPT_GEOSCAPE));
519 }
520
521 // Set music if it's not already playing
522 if (_dogfights.empty() && !_dogfightStartTimer->isRunning())
523 {
524 if (_game->getSavedGame()->getMonthsPassed() == -1)
525 {
526 _game->getResourcePack()->playMusic("GMGEO1");
527 }
528 else
529 {
530 _game->getResourcePack()->playMusic("GMGEO", true);
531 }
532 }
533 _globe->unsetNewBaseHover();
534 }
535
536 /**
537 * Runs the game timer and handles popups.
538 */
think()539 void GeoscapeState::think()
540 {
541 State::think();
542
543 _zoomInEffectTimer->think(this, 0);
544 _zoomOutEffectTimer->think(this, 0);
545 _dogfightStartTimer->think(this, 0);
546
547 if (_game->getSavedGame()->getMonthsPassed() == -1)
548 {
549 _game->getSavedGame()->addMonth();
550 determineAlienMissions(true);
551 setupTerrorMission();
552 _game->getSavedGame()->setFunds(_game->getSavedGame()->getFunds() - (_game->getSavedGame()->getBaseMaintenance() - _game->getSavedGame()->getBases()->front()->getPersonnelMaintenance()));
553 }
554 if (_popups.empty() && _dogfights.empty() && (!_zoomInEffectTimer->isRunning() || _zoomInEffectDone) && (!_zoomOutEffectTimer->isRunning() || _zoomOutEffectDone))
555 {
556 // Handle timers
557 _gameTimer->think(this, 0);
558 }
559 else
560 {
561 if (!_dogfights.empty() || _minimizedDogfights != 0)
562 {
563 handleDogfights();
564 }
565 if (!_popups.empty())
566 {
567 // Handle popups
568 _globe->rotateStop();
569 _game->pushState(_popups.front());
570 _popups.erase(_popups.begin());
571 }
572 }
573 }
574
575 /**
576 * Updates the Geoscape clock with the latest
577 * game time and date in human-readable format. (+Funds)
578 */
timeDisplay()579 void GeoscapeState::timeDisplay()
580 {
581 if (Options::showFundsOnGeoscape)
582 {
583 _txtFunds->setText(Text::formatFunding(_game->getSavedGame()->getFunds()));
584 }
585
586 std::wostringstream ss;
587 ss << std::setfill(L'0') << std::setw(2) << _game->getSavedGame()->getTime()->getSecond();
588 _txtSec->setText(ss.str());
589
590 std::wostringstream ss2;
591 ss2 << std::setfill(L'0') << std::setw(2) << _game->getSavedGame()->getTime()->getMinute();
592 _txtMin->setText(ss2.str());
593
594 std::wostringstream ss3;
595 ss3 << _game->getSavedGame()->getTime()->getHour();
596 _txtHour->setText(ss3.str());
597
598 std::wostringstream ss4;
599 ss4 << _game->getSavedGame()->getTime()->getDayString(_game->getLanguage());
600 _txtDay->setText(ss4.str());
601
602 _txtWeekday->setText(tr(_game->getSavedGame()->getTime()->getWeekdayString()));
603
604 _txtMonth->setText(tr(_game->getSavedGame()->getTime()->getMonthString()));
605
606 std::wostringstream ss5;
607 ss5 << _game->getSavedGame()->getTime()->getYear();
608 _txtYear->setText(ss5.str());
609 }
610
611 /**
612 * Advances the game timer according to
613 * the timer speed set, and calls the respective
614 * triggers. The timer always advances in "5 secs"
615 * cycles, regardless of the speed, otherwise it might
616 * skip important steps. Instead, it just keeps advancing
617 * the timer until the next speed step (eg. the next day
618 * on 1 Day speed) or until an event occurs, since updating
619 * the screen on each step would become cumbersomely slow.
620 */
timeAdvance()621 void GeoscapeState::timeAdvance()
622 {
623 int timeSpan = 0;
624 if (_timeSpeed == _btn5Secs)
625 {
626 timeSpan = 1;
627 }
628 else if (_timeSpeed == _btn1Min)
629 {
630 timeSpan = 12;
631 }
632 else if (_timeSpeed == _btn5Mins)
633 {
634 timeSpan = 12 * 5;
635 }
636 else if (_timeSpeed == _btn30Mins)
637 {
638 timeSpan = 12 * 5 * 6;
639 }
640 else if (_timeSpeed == _btn1Hour)
641 {
642 timeSpan = 12 * 5 * 6 * 2;
643 }
644 else if (_timeSpeed == _btn1Day)
645 {
646 timeSpan = 12 * 5 * 6 * 2 * 24;
647 }
648
649 for (int i = 0; i < timeSpan && !_pause; ++i)
650 {
651 TimeTrigger trigger;
652 trigger = _game->getSavedGame()->getTime()->advance();
653 switch (trigger)
654 {
655 case TIME_1MONTH:
656 time1Month();
657 case TIME_1DAY:
658 time1Day();
659 case TIME_1HOUR:
660 time1Hour();
661 case TIME_30MIN:
662 time30Minutes();
663 case TIME_10MIN:
664 time10Minutes();
665 case TIME_5SEC:
666 time5Seconds();
667 }
668 }
669
670 _pause = !_dogfightsToBeStarted.empty();
671
672 timeDisplay();
673 _globe->draw();
674 }
675
676 /**
677 * Takes care of any game logic that has to
678 * run every game second, like craft movement.
679 */
time5Seconds()680 void GeoscapeState::time5Seconds()
681 {
682 // Game over if there are no more bases.
683 if (_game->getSavedGame()->getBases()->empty())
684 {
685 popup(new DefeatState(_game));
686 return;
687 }
688
689 // Handle UFO logic
690 for (std::vector<Ufo*>::iterator i = _game->getSavedGame()->getUfos()->begin(); i != _game->getSavedGame()->getUfos()->end(); ++i)
691 {
692 switch ((*i)->getStatus())
693 {
694 case Ufo::FLYING:
695 if (!_zoomInEffectTimer->isRunning() && !_zoomOutEffectTimer->isRunning())
696 {
697 (*i)->think();
698 if ((*i)->reachedDestination())
699 {
700 size_t terrorSiteCount = _game->getSavedGame()->getTerrorSites()->size();
701 AlienMission *mission = (*i)->getMission();
702 bool detected = (*i)->getDetected();
703 mission->ufoReachedWaypoint(**i, *_game, *_globe);
704 if (detected != (*i)->getDetected() && !(*i)->getFollowers()->empty())
705 {
706 if (!((*i)->getTrajectory().getID() == "__RETALIATION_ASSAULT_RUN" && (*i)->getStatus() == Ufo::LANDED))
707 popup(new UfoLostState(_game, (*i)->getName(_game->getLanguage())));
708 }
709 if (terrorSiteCount < _game->getSavedGame()->getTerrorSites()->size())
710 {
711 TerrorSite *ts = _game->getSavedGame()->getTerrorSites()->back();
712 const City *city = _game->getRuleset()->locateCity(ts->getLongitude(), ts->getLatitude());
713 assert(city);
714 popup(new AlienTerrorState(_game, ts, city->getName(), this));
715 }
716 // If UFO was destroyed, don't spawn missions
717 if ((*i)->getStatus() == Ufo::DESTROYED)
718 return;
719 if (Base *base = dynamic_cast<Base*>((*i)->getDestination()))
720 {
721 mission->setWaveCountdown(30 * (RNG::generate(0, 48) + 400));
722 (*i)->setDestination(0);
723 base->setupDefenses();
724 timerReset();
725 if (!base->getDefenses()->empty())
726 {
727 popup(new BaseDefenseState(_game, base, *i, this));
728 }
729 else
730 {
731 handleBaseDefense(base, *i);
732 return;
733 }
734 }
735 }
736 }
737 break;
738 case Ufo::LANDED:
739 (*i)->think();
740 if ((*i)->getSecondsRemaining() == 0)
741 {
742 AlienMission *mission = (*i)->getMission();
743 bool detected = (*i)->getDetected();
744 mission->ufoLifting(**i, *_game, *_globe);
745 if (detected != (*i)->getDetected() && !(*i)->getFollowers()->empty())
746 {
747 popup(new UfoLostState(_game, (*i)->getName(_game->getLanguage())));
748 }
749 }
750 break;
751 case Ufo::CRASHED:
752 (*i)->think();
753 if ((*i)->getSecondsRemaining() == 0)
754 {
755 (*i)->setDetected(false);
756 (*i)->setStatus(Ufo::DESTROYED);
757 }
758 break;
759 case Ufo::DESTROYED:
760 // Nothing to do
761 break;
762 }
763 }
764
765 // Handle craft logic
766 for (std::vector<Base*>::iterator i = _game->getSavedGame()->getBases()->begin(); i != _game->getSavedGame()->getBases()->end(); ++i)
767 {
768 for (std::vector<Craft*>::iterator j = (*i)->getCrafts()->begin(); j != (*i)->getCrafts()->end();)
769 {
770 if ((*j)->isDestroyed())
771 {
772 for(std::vector<Country*>::iterator country = _game->getSavedGame()->getCountries()->begin(); country != _game->getSavedGame()->getCountries()->end(); ++country)
773 {
774 if ((*country)->getRules()->insideCountry((*j)->getLongitude(), (*j)->getLatitude()))
775 {
776 (*country)->addActivityXcom(-(*j)->getRules()->getScore());
777 break;
778 }
779 }
780 for(std::vector<Region*>::iterator region = _game->getSavedGame()->getRegions()->begin(); region != _game->getSavedGame()->getRegions()->end(); ++region)
781 {
782 if ((*region)->getRules()->insideRegion((*j)->getLongitude(), (*j)->getLatitude()))
783 {
784 (*region)->addActivityXcom(-(*j)->getRules()->getScore());
785 break;
786 }
787 }
788 // if a transport craft has been shot down, kill all the soldiers on board.
789 if ((*j)->getRules()->getSoldiers() > 0)
790 {
791 for (std::vector<Soldier*>::iterator k = (*i)->getSoldiers()->begin(); k != (*i)->getSoldiers()->end();)
792 {
793 if ((*k)->getCraft() == (*j))
794 {
795 SoldierDeath *death = new SoldierDeath();
796 death->setTime(*_game->getSavedGame()->getTime());
797 (*k)->die(death);
798 _game->getSavedGame()->getDeadSoldiers()->push_back((*k));
799 k = (*i)->getSoldiers()->erase(k);
800 }
801 else
802 {
803 ++k;
804 }
805 }
806 }
807 delete *j;
808 j = (*i)->getCrafts()->erase(j);
809 continue;
810 }
811 if ((*j)->getDestination() != 0)
812 {
813 Ufo* u = dynamic_cast<Ufo*>((*j)->getDestination());
814 if (u != 0 && !u->getDetected())
815 {
816 if (u->getTrajectory().getID() == "__RETALIATION_ASSAULT_RUN" && (u->getStatus() == Ufo::LANDED || u->getStatus() == Ufo::DESTROYED))
817 {
818 (*j)->returnToBase();
819 }
820 else
821 {
822 (*j)->setDestination(0);
823 Waypoint *w = new Waypoint();
824 w->setLongitude(u->getLongitude());
825 w->setLatitude(u->getLatitude());
826 w->setId(u->getId());
827 popup(new GeoscapeCraftState(_game, (*j), _globe, w));
828 }
829 }
830 if (u != 0 && u->getStatus() == Ufo::DESTROYED)
831 {
832 (*j)->returnToBase();
833 }
834 }
835 if (!_zoomInEffectTimer->isRunning() && !_zoomOutEffectTimer->isRunning())
836 {
837 (*j)->think();
838 }
839 if ((*j)->reachedDestination())
840 {
841 Ufo* u = dynamic_cast<Ufo*>((*j)->getDestination());
842 Waypoint *w = dynamic_cast<Waypoint*>((*j)->getDestination());
843 TerrorSite* t = dynamic_cast<TerrorSite*>((*j)->getDestination());
844 AlienBase* b = dynamic_cast<AlienBase*>((*j)->getDestination());
845 if (u != 0)
846 {
847 switch (u->getStatus())
848 {
849 case Ufo::FLYING:
850 // Not more than 4 interceptions at a time.
851 if (_dogfights.size() + _dogfightsToBeStarted.size() >= 4)
852 {
853 ++j;
854 continue;
855 }
856 if (!(*j)->isInDogfight() && !(*j)->getDistance(u))
857 {
858 _dogfightsToBeStarted.push_back(new DogfightState(_game, _globe, (*j), u));
859
860 if (!_dogfightStartTimer->isRunning())
861 {
862 _pause = true;
863 timerReset();
864 _globe->center((*j)->getLongitude(), (*j)->getLatitude());
865 startDogfight();
866 _dogfightStartTimer->start();
867 }
868 _game->getResourcePack()->playMusic("GMINTER");
869 }
870 break;
871 case Ufo::LANDED:
872 case Ufo::CRASHED:
873 case Ufo::DESTROYED: // Just before expiration
874 if ((*j)->getNumSoldiers() > 0 || (*j)->getNumVehicles() > 0)
875 {
876 if (!(*j)->isInDogfight())
877 {
878 // look up polygons texture
879 int texture, shade;
880 _globe->getPolygonTextureAndShade(u->getLongitude(), u->getLatitude(), &texture, &shade);
881 timerReset();
882 popup(new ConfirmLandingState(_game, *j, texture, shade));
883 }
884 }
885 else if (u->getStatus() != Ufo::LANDED)
886 {
887 (*j)->returnToBase();
888 }
889 break;
890 }
891 }
892 else if (w != 0)
893 {
894 popup(new CraftPatrolState(_game, (*j), _globe));
895 (*j)->setDestination(0);
896 }
897 else if (t != 0)
898 {
899 if ((*j)->getNumSoldiers() > 0)
900 {
901 // look up polygons texture
902 int texture, shade;
903 _globe->getPolygonTextureAndShade(t->getLongitude(), t->getLatitude(), &texture, &shade);
904 timerReset();
905 popup(new ConfirmLandingState(_game, *j, texture, shade));
906 }
907 else
908 {
909 (*j)->returnToBase();
910 }
911 }
912 else if (b != 0)
913 {
914 if (b->isDiscovered())
915 {
916 if ((*j)->getNumSoldiers() > 0)
917 {
918 int texture, shade;
919 _globe->getPolygonTextureAndShade(b->getLongitude(), b->getLatitude(), &texture, &shade);
920 timerReset();
921 popup(new ConfirmLandingState(_game, *j, texture, shade));
922 }
923 else
924 {
925 (*j)->returnToBase();
926 }
927 }
928 }
929 }
930 ++j;
931 }
932 }
933
934 // Clean up dead UFOs and end dogfights which were minimized.
935 for (std::vector<Ufo*>::iterator i = _game->getSavedGame()->getUfos()->begin(); i != _game->getSavedGame()->getUfos()->end();)
936 {
937 if ((*i)->getStatus() == Ufo::DESTROYED)
938 {
939 if (!(*i)->getFollowers()->empty())
940 {
941 // Remove all dogfights with this UFO.
942 for(std::list<DogfightState*>::iterator d = _dogfights.begin(); d != _dogfights.end();)
943 {
944 if ((*d)->getUfo() == (*i))
945 {
946 delete *d;
947 d = _dogfights.erase(d);
948 }
949 else
950 {
951 ++d;
952 }
953 }
954 }
955 delete *i;
956 i = _game->getSavedGame()->getUfos()->erase(i);
957 }
958 else
959 {
960 ++i;
961 }
962 }
963
964 // Clean up unused waypoints
965 for (std::vector<Waypoint*>::iterator i = _game->getSavedGame()->getWaypoints()->begin(); i != _game->getSavedGame()->getWaypoints()->end();)
966 {
967 if ((*i)->getFollowers()->empty())
968 {
969 delete *i;
970 i = _game->getSavedGame()->getWaypoints()->erase(i);
971 }
972 else
973 {
974 ++i;
975 }
976 }
977 }
978
979 /**
980 * Functor that attempt to detect an XCOM base.
981 */
982 class DetectXCOMBase: public std::unary_function<Ufo *, bool>
983 {
984 public:
985 /// Create a detector for the given base.
DetectXCOMBase(const Base & base,int difficulty)986 DetectXCOMBase(const Base &base, int difficulty) : _base(base), _difficulty(difficulty) { /* Empty by design. */ }
987 /// Attempt detection
988 bool operator()(const Ufo *ufo) const;
989 private:
990 const Base &_base; //!< The target base.
991 const int _difficulty;
992 };
993
994 /**
995 * Only UFOs within detection range of the base have a chance to detect it.
996 * @param ufo Pointer to the UFO attempting detection.
997 * @return If the base is detected by @a ufo.
998 */
operator ()(const Ufo * ufo) const999 bool DetectXCOMBase::operator()(const Ufo *ufo) const
1000 {
1001 if ((ufo->getMissionType() != "STR_ALIEN_RETALIATION" && !Options::aggressiveRetaliation) || // only UFOs on retaliation missions actively scan for bases
1002 ufo->getTrajectory().getID() == "__RETALIATION_ASSAULT_RUN" || // UFOs attacking a base don't detect!
1003 ufo->isCrashed() || // Crashed UFOs don't detect!
1004 _base.getDistance(ufo) >= 80 * (1 / 60.0) * (M_PI / 180.0)) // UFOs have a detection range of 80 XCOM units.
1005 {
1006 return false;
1007 }
1008 return RNG::percent(_base.getDetectionChance(_difficulty));
1009 }
1010
1011 /**
1012 * Functor that marks an XCOM base for retaliation.
1013 * This is required because of the iterator type.
1014 */
1015 struct SetRetaliationTarget: public std::unary_function<std::map<const Region *, Base *>::value_type, void>
1016 {
1017 /// Mark as a valid retaliation target.
operator ()OpenXcom::SetRetaliationTarget1018 void operator()(const argument_type &iter) const { iter.second->setRetaliationTarget(true); }
1019 };
1020
1021 /**
1022 * Takes care of any game logic that has to
1023 * run every game ten minutes, like fuel consumption.
1024 */
time10Minutes()1025 void GeoscapeState::time10Minutes()
1026 {
1027 for (std::vector<Base*>::iterator i = _game->getSavedGame()->getBases()->begin(); i != _game->getSavedGame()->getBases()->end(); ++i)
1028 {
1029 // Fuel consumption for XCOM craft.
1030 for (std::vector<Craft*>::iterator j = (*i)->getCrafts()->begin(); j != (*i)->getCrafts()->end(); ++j)
1031 {
1032 if ((*j)->getStatus() == "STR_OUT")
1033 {
1034 (*j)->consumeFuel();
1035 if (!(*j)->getLowFuel() && (*j)->getFuel() <= (*j)->getFuelLimit())
1036 {
1037 (*j)->setLowFuel(true);
1038 (*j)->returnToBase();
1039 popup(new LowFuelState(_game, (*j), this));
1040 }
1041
1042 if ((*j)->getDestination() == 0)
1043 {
1044 for(std::vector<AlienBase*>::iterator b = _game->getSavedGame()->getAlienBases()->begin(); b != _game->getSavedGame()->getAlienBases()->end(); b++)
1045 {
1046 double range = (1696 * (1 / 60.0) * (M_PI / 180));
1047 if ((*j)->getDistance(*b) <= range)
1048 {
1049 // TODO: move the detection range to the ruleset, or use the pre-defined one (which is 600, but detection range should be 500).
1050 if (RNG::percent(50-((*j)->getDistance(*b) / range) * 50) && !(*b)->isDiscovered())
1051 {
1052 (*b)->setDiscovered(true);
1053 }
1054 }
1055 }
1056 }
1057 }
1058 }
1059 }
1060 int diff = (int)(_game->getSavedGame()->getDifficulty());
1061 if (Options::aggressiveRetaliation)
1062 {
1063 // Detect as many bases as possible.
1064 for (std::vector<Base*>::iterator iBase = _game->getSavedGame()->getBases()->begin(); iBase != _game->getSavedGame()->getBases()->end(); ++iBase)
1065 {
1066 // Find a UFO that detected this base, if any.
1067 std::vector<Ufo*>::const_iterator uu = std::find_if (_game->getSavedGame()->getUfos()->begin(), _game->getSavedGame()->getUfos()->end(), DetectXCOMBase(**iBase, diff));
1068 if (uu != _game->getSavedGame()->getUfos()->end())
1069 {
1070 // Base found
1071 (*iBase)->setRetaliationTarget(true);
1072 }
1073 }
1074 }
1075 else
1076 {
1077 // Only remember last base in each region.
1078 std::map<const Region *, Base *> discovered;
1079 for (std::vector<Base*>::iterator iBase = _game->getSavedGame()->getBases()->begin(); iBase != _game->getSavedGame()->getBases()->end(); ++iBase)
1080 {
1081 // Find a UFO that detected this base, if any.
1082 std::vector<Ufo*>::const_iterator uu = std::find_if (_game->getSavedGame()->getUfos()->begin(), _game->getSavedGame()->getUfos()->end(), DetectXCOMBase(**iBase, diff));
1083 if (uu != _game->getSavedGame()->getUfos()->end())
1084 {
1085 discovered[_game->getSavedGame()->locateRegion(**iBase)] = *iBase;
1086 }
1087 }
1088 // Now mark the bases as discovered.
1089 std::for_each(discovered.begin(), discovered.end(), SetRetaliationTarget());
1090 }
1091 }
1092
1093 /** @brief Call AlienMission::think() with proper parameters.
1094 * This function object calls AlienMission::think() with the proper parameters.
1095 */
1096 class callThink: public std::unary_function<AlienMission*, void>
1097 {
1098 public:
1099 /// Store the parameters.
1100 /**
1101 * @param game The game engine.
1102 * @param globe The globe object.
1103 */
callThink(Game & game,const Globe & globe)1104 callThink(Game &game, const Globe &globe) : _game(game), _globe(globe) { /* Empty by design. */ }
1105 /// Call AlienMission::think() with stored parameters.
operator ()(AlienMission * am) const1106 void operator()(AlienMission *am) const { am->think(_game, _globe); }
1107 private:
1108 Game &_game;
1109 const Globe &_globe;
1110 };
1111
1112 /** @brief Process a TerrorSite.
1113 * This function object will count down towards expiring a TerrorSite, and handle expired TerrorSites.
1114 * @param ts Pointer to terror site.
1115 * @return Has terror site expired?
1116 */
processTerrorSite(TerrorSite * ts) const1117 bool GeoscapeState::processTerrorSite(TerrorSite *ts) const
1118 {
1119 if (ts->getSecondsRemaining() >= 30 * 60)
1120 {
1121 ts->setSecondsRemaining(ts->getSecondsRemaining() - 30 * 60);
1122 return false;
1123 }
1124 if (!ts->getFollowers()->empty()) // CHEEKY EXPLOIT
1125 {
1126 return false;
1127 }
1128 // Score and delete it.
1129 Region *region = _game->getSavedGame()->locateRegion(*ts);
1130 if (region)
1131 {
1132 region->addActivityAlien(_game->getRuleset()->getAlienMission("STR_ALIEN_TERROR")->getPoints() * 100);
1133 //kids, tell your folks... don't ignore terror sites.
1134 }
1135 for (std::vector<Country*>::iterator k = _game->getSavedGame()->getCountries()->begin(); k != _game->getSavedGame()->getCountries()->end(); ++k)
1136 {
1137 if ((*k)->getRules()->insideCountry(ts->getLongitude(), ts->getLatitude()))
1138 {
1139 (*k)->addActivityAlien(_game->getRuleset()->getAlienMission("STR_ALIEN_TERROR")->getPoints() * 100);
1140 break;
1141 }
1142 }
1143 delete ts;
1144 return true;
1145 }
1146
1147 /** @brief Advance time for crashed UFOs.
1148 * This function object will decrease the expiration timer for crashed UFOs.
1149 */
1150 struct expireCrashedUfo: public std::unary_function<Ufo*, void>
1151 {
1152 /// Decrease UFO expiration timer.
operator ()OpenXcom::expireCrashedUfo1153 void operator()(Ufo *ufo) const
1154 {
1155 if (ufo->getStatus() == Ufo::CRASHED)
1156 {
1157 if (ufo->getSecondsRemaining() >= 30 * 60)
1158 {
1159 ufo->setSecondsRemaining(ufo->getSecondsRemaining() - 30 * 60);
1160 return;
1161 }
1162 // Marked expired UFOs for removal.
1163 ufo->setStatus(Ufo::DESTROYED);
1164 }
1165 }
1166 };
1167
1168 /**
1169 * Takes care of any game logic that has to
1170 * run every game half hour, like UFO detection.
1171 */
time30Minutes()1172 void GeoscapeState::time30Minutes()
1173 {
1174 // Decrease mission countdowns
1175 std::for_each(_game->getSavedGame()->getAlienMissions().begin(),
1176 _game->getSavedGame()->getAlienMissions().end(),
1177 callThink(*_game, *_globe));
1178 // Remove finished missions
1179 for (std::vector<AlienMission*>::iterator am = _game->getSavedGame()->getAlienMissions().begin();
1180 am != _game->getSavedGame()->getAlienMissions().end();)
1181 {
1182 if ((*am)->isOver())
1183 {
1184 delete *am;
1185 am = _game->getSavedGame()->getAlienMissions().erase(am);
1186 }
1187 else
1188 {
1189 ++am;
1190 }
1191 }
1192
1193 // Handle crashed UFOs expiration
1194 std::for_each(_game->getSavedGame()->getUfos()->begin(),
1195 _game->getSavedGame()->getUfos()->end(),
1196 expireCrashedUfo());
1197
1198
1199 // Handle craft maintenance and alien base detection
1200 for (std::vector<Base*>::iterator i = _game->getSavedGame()->getBases()->begin(); i != _game->getSavedGame()->getBases()->end(); ++i)
1201 {
1202 for (std::vector<Craft*>::iterator j = (*i)->getCrafts()->begin(); j != (*i)->getCrafts()->end(); ++j)
1203 {
1204 if ((*j)->getStatus() == "STR_REFUELLING")
1205 {
1206 std::string item = (*j)->getRules()->getRefuelItem();
1207 if (item == "")
1208 {
1209 (*j)->refuel();
1210 }
1211 else
1212 {
1213 if ((*i)->getItems()->getItem(item) > 0)
1214 {
1215 (*i)->getItems()->removeItem(item);
1216 (*j)->refuel();
1217 (*j)->setLowFuel(false);
1218 }
1219 else if (!(*j)->getLowFuel())
1220 {
1221 std::wstring msg = tr("STR_NOT_ENOUGH_ITEM_TO_REFUEL_CRAFT_AT_BASE")
1222 .arg(tr(item))
1223 .arg((*j)->getName(_game->getLanguage()))
1224 .arg((*i)->getName());
1225 popup(new CraftErrorState(_game, this, msg));
1226 if ((*j)->getFuel() > 0)
1227 (*j)->setStatus("STR_READY");
1228 else
1229 (*j)->setLowFuel(true);
1230 }
1231 }
1232 }
1233 }
1234 }
1235
1236 // Handle UFO detection and give aliens points
1237 for (std::vector<Ufo*>::iterator u = _game->getSavedGame()->getUfos()->begin(); u != _game->getSavedGame()->getUfos()->end(); ++u)
1238 {
1239 int points = 0;
1240 switch ((*u)->getStatus())
1241 {
1242 case Ufo::LANDED:
1243 points++;
1244 case Ufo::FLYING:
1245 points++;
1246 // Get area
1247 for (std::vector<Region*>::iterator k = _game->getSavedGame()->getRegions()->begin(); k != _game->getSavedGame()->getRegions()->end(); ++k)
1248 {
1249 if ((*k)->getRules()->insideRegion((*u)->getLongitude(), (*u)->getLatitude()))
1250 {
1251 //one point per UFO in-flight per half hour
1252 (*k)->addActivityAlien(points);
1253 break;
1254 }
1255 }
1256 // Get country
1257 for (std::vector<Country*>::iterator k = _game->getSavedGame()->getCountries()->begin(); k != _game->getSavedGame()->getCountries()->end(); ++k)
1258 {
1259 if ((*k)->getRules()->insideCountry((*u)->getLongitude(), (*u)->getLatitude()))
1260 {
1261 //one point per UFO in-flight per half hour
1262 (*k)->addActivityAlien(points);
1263 break;
1264 }
1265 }
1266 if (!(*u)->getDetected())
1267 {
1268 bool detected = false, hyperdetected = false;
1269 for (std::vector<Base*>::iterator b = _game->getSavedGame()->getBases()->begin(); !hyperdetected && b != _game->getSavedGame()->getBases()->end(); ++b)
1270 {
1271 switch ((*b)->detect(*u))
1272 {
1273 case 2: // hyper-wave decoder
1274 (*u)->setHyperDetected(true);
1275 hyperdetected = true;
1276 case 1: // conventional radar
1277 detected = true;
1278 }
1279 for (std::vector<Craft*>::iterator c = (*b)->getCrafts()->begin(); !detected && c != (*b)->getCrafts()->end(); ++c)
1280 {
1281 if ((*c)->getStatus() == "STR_OUT" && (*c)->detect(*u))
1282 {
1283 detected = true;
1284 break;
1285 }
1286 }
1287 }
1288 if (detected)
1289 {
1290 (*u)->setDetected(true);
1291 popup(new UfoDetectedState(_game, (*u), this, true, (*u)->getHyperDetected()));
1292 }
1293 }
1294 else
1295 {
1296 bool detected = false, hyperdetected = false;
1297 for (std::vector<Base*>::iterator b = _game->getSavedGame()->getBases()->begin(); !hyperdetected && b != _game->getSavedGame()->getBases()->end(); ++b)
1298 {
1299 switch ((*b)->insideRadarRange(*u))
1300 {
1301 case 2: // hyper-wave decoder
1302 detected = true;
1303 hyperdetected = true;
1304 (*u)->setHyperDetected(true);
1305 break;
1306 case 1: // conventional radar
1307 detected = true;
1308 hyperdetected = (*u)->getHyperDetected();
1309 }
1310 for (std::vector<Craft*>::iterator c = (*b)->getCrafts()->begin(); !detected && c != (*b)->getCrafts()->end(); ++c)
1311 {
1312 if ((*c)->getStatus() == "STR_OUT" && (*c)->detect(*u))
1313 {
1314 detected = true;
1315 hyperdetected = (*u)->getHyperDetected();
1316 break;
1317 }
1318 }
1319 }
1320 if (!detected)
1321 {
1322 (*u)->setDetected(false);
1323 (*u)->setHyperDetected(false);
1324 if (!(*u)->getFollowers()->empty())
1325 {
1326 popup(new UfoLostState(_game, (*u)->getName(_game->getLanguage())));
1327 }
1328 }
1329 }
1330 break;
1331 case Ufo::CRASHED:
1332 case Ufo::DESTROYED:
1333 break;
1334 }
1335 }
1336
1337 // Processes TerrorSites
1338 for (std::vector<TerrorSite*>::iterator ts = _game->getSavedGame()->getTerrorSites()->begin();
1339 ts != _game->getSavedGame()->getTerrorSites()->end();)
1340 {
1341 if (processTerrorSite(*ts))
1342 {
1343 ts = _game->getSavedGame()->getTerrorSites()->erase(ts);
1344 }
1345 else
1346 {
1347 ++ts;
1348 }
1349 }
1350 }
1351
1352 /**
1353 * Takes care of any game logic that has to
1354 * run every game hour, like transfers.
1355 */
time1Hour()1356 void GeoscapeState::time1Hour()
1357 {
1358 // Handle craft maintenance
1359 for (std::vector<Base*>::iterator i = _game->getSavedGame()->getBases()->begin(); i != _game->getSavedGame()->getBases()->end(); ++i)
1360 {
1361 for (std::vector<Craft*>::iterator j = (*i)->getCrafts()->begin(); j != (*i)->getCrafts()->end(); ++j)
1362 {
1363 if ((*j)->getStatus() == "STR_REPAIRS")
1364 {
1365 (*j)->repair();
1366 }
1367 else if ((*j)->getStatus() == "STR_REARMING")
1368 {
1369 std::string s = (*j)->rearm(_game->getRuleset());
1370 if (s != "")
1371 {
1372 std::wstring msg = tr("STR_NOT_ENOUGH_ITEM_TO_REARM_CRAFT_AT_BASE")
1373 .arg(tr(s))
1374 .arg((*j)->getName(_game->getLanguage()))
1375 .arg((*i)->getName());
1376 popup(new CraftErrorState(_game, this, msg));
1377 }
1378 }
1379 }
1380 }
1381
1382 // Handle transfers
1383 bool window = false;
1384 for (std::vector<Base*>::iterator i = _game->getSavedGame()->getBases()->begin(); i != _game->getSavedGame()->getBases()->end(); ++i)
1385 {
1386 for (std::vector<Transfer*>::iterator j = (*i)->getTransfers()->begin(); j != (*i)->getTransfers()->end(); ++j)
1387 {
1388 (*j)->advance(*i);
1389 if (!window && (*j)->getHours() == 0)
1390 {
1391 window = true;
1392 }
1393 }
1394 }
1395 if (window)
1396 {
1397 popup(new ItemsArrivingState(_game, this));
1398 }
1399 // Handle Production
1400 for (std::vector<Base*>::iterator i = _game->getSavedGame()->getBases()->begin(); i != _game->getSavedGame()->getBases()->end(); ++i)
1401 {
1402 std::map<Production*, productionProgress_e> toRemove;
1403 for (std::vector<Production*>::const_iterator j = (*i)->getProductions().begin(); j != (*i)->getProductions().end(); ++j)
1404 {
1405 toRemove[(*j)] = (*j)->step((*i), _game->getSavedGame(), _game->getRuleset());
1406 }
1407 for (std::map<Production*, productionProgress_e>::iterator j = toRemove.begin(); j != toRemove.end(); ++j)
1408 {
1409 if (j->second > PROGRESS_NOT_COMPLETE)
1410 {
1411 (*i)->removeProduction (j->first);
1412 popup(new ProductionCompleteState(_game, (*i), tr(j->first->getRules()->getName()), this, j->second));
1413 }
1414 }
1415
1416 if (Options::storageLimitsEnforced && (*i)->storesOverfull())
1417 {
1418 popup(new ErrorMessageState(_game, tr("STR_STORAGE_EXCEEDED").arg((*i)->getName()).c_str(), _palette, Palette::blockOffset(15) + 1, "BACK13.SCR", 6));
1419 popup(new SellState(_game, (*i)));
1420 }
1421 }
1422 }
1423
1424 /**
1425 * This class will attempt to generate a supply mission for a base.
1426 * Each alien base has a 6/101 chance to generate a supply mission.
1427 */
1428 class GenerateSupplyMission: public std::unary_function<const AlienBase *, void>
1429 {
1430 public:
1431 /// Store rules and game data references for later use.
GenerateSupplyMission(const Ruleset & ruleset,SavedGame & save)1432 GenerateSupplyMission(const Ruleset &ruleset, SavedGame &save) : _ruleset(ruleset), _save(save) { /* Empty by design */ }
1433 /// Check and spawn mission.
1434 void operator()(const AlienBase *base) const;
1435 private:
1436 const Ruleset &_ruleset;
1437 SavedGame &_save;
1438 };
1439
1440 /**
1441 * Check and create supply mission for the given base.
1442 * There is a 6/101 chance of the mission spawning.
1443 * @param base A pointer to the alien base.
1444 */
operator ()(const AlienBase * base) const1445 void GenerateSupplyMission::operator()(const AlienBase *base) const
1446 {
1447 if (RNG::percent(6))
1448 {
1449 //Spawn supply mission for this base.
1450 const RuleAlienMission &rule = *_ruleset.getAlienMission("STR_ALIEN_SUPPLY");
1451 AlienMission *mission = new AlienMission(rule);
1452 mission->setRegion(_save.locateRegion(*base)->getRules()->getType(), _ruleset);
1453 mission->setId(_save.getId("ALIEN_MISSIONS"));
1454 mission->setRace(base->getAlienRace());
1455 mission->setAlienBase(base);
1456 mission->start();
1457 _save.getAlienMissions().push_back(mission);
1458 }
1459 }
1460
1461 /**
1462 * Takes care of any game logic that has to
1463 * run every game day, like constructions.
1464 */
time1Day()1465 void GeoscapeState::time1Day()
1466 {
1467 for (std::vector<Base*>::iterator i = _game->getSavedGame()->getBases()->begin(); i != _game->getSavedGame()->getBases()->end(); ++i)
1468 {
1469 // Handle facility construction
1470 for (std::vector<BaseFacility*>::iterator j = (*i)->getFacilities()->begin(); j != (*i)->getFacilities()->end(); ++j)
1471 {
1472 if ((*j)->getBuildTime() > 0)
1473 {
1474 (*j)->build();
1475 if ((*j)->getBuildTime() == 0)
1476 {
1477 popup(new ProductionCompleteState(_game, (*i), tr((*j)->getRules()->getType()), this, PROGRESS_CONSTRUCTION));
1478 }
1479 }
1480 }
1481 // Handle science project
1482 std::vector<ResearchProject*> finished;
1483 for(std::vector<ResearchProject*>::const_iterator iter = (*i)->getResearch().begin (); iter != (*i)->getResearch().end (); ++iter)
1484 {
1485 if ((*iter)->step())
1486 {
1487 finished.push_back(*iter);
1488 }
1489 }
1490 for(std::vector<ResearchProject*>::const_iterator iter = finished.begin (); iter != finished.end (); ++iter)
1491 {
1492 (*i)->removeResearch(*iter);
1493 RuleResearch * bonus = 0;
1494 const RuleResearch * research = (*iter)->getRules ();
1495 // If "researched" the live alien, his body sent to the stores.
1496 if (Options::spendResearchedItems && research->needItem() && _game->getRuleset()->getUnit(research->getName()))
1497 {
1498 (*i)->getItems()->addItem(
1499 _game->getRuleset()->getArmor(
1500 _game->getRuleset()->getUnit(
1501 research->getName()
1502 )->getArmor()
1503 )->getCorpseGeoscape()
1504 ); // ;)
1505 }
1506 if ((*iter)->getRules()->getGetOneFree().size() != 0)
1507 {
1508 std::vector<std::string> possibilities;
1509 for(std::vector<std::string>::const_iterator f = (*iter)->getRules()->getGetOneFree().begin(); f != (*iter)->getRules()->getGetOneFree().end(); ++f)
1510 {
1511 bool newFound = true;
1512 for(std::vector<const RuleResearch*>::const_iterator discovered = _game->getSavedGame()->getDiscoveredResearch().begin(); discovered != _game->getSavedGame()->getDiscoveredResearch().end(); ++discovered)
1513 {
1514 if (*f == (*discovered)->getName())
1515 {
1516 newFound = false;
1517 }
1518 }
1519 if (newFound)
1520 {
1521 possibilities.push_back(*f);
1522 }
1523 }
1524 if (possibilities.size() != 0)
1525 {
1526 size_t pick = RNG::generate(0, possibilities.size()-1);
1527 std::string sel = possibilities.at(pick);
1528 bonus = _game->getRuleset()->getResearch(sel);
1529 _game->getSavedGame()->addFinishedResearch(bonus, _game->getRuleset ());
1530 if (bonus->getLookup() != "")
1531 {
1532 _game->getSavedGame()->addFinishedResearch(_game->getRuleset()->getResearch(bonus->getLookup()), _game->getRuleset ());
1533 }
1534 }
1535 }
1536 const RuleResearch * newResearch = research;
1537 std::string name = research->getLookup() == "" ? research->getName() : research->getLookup();
1538 if (_game->getSavedGame()->isResearched(name))
1539 {
1540 newResearch = 0;
1541 }
1542 _game->getSavedGame()->addFinishedResearch(research, _game->getRuleset ());
1543 if (research->getLookup() != "")
1544 {
1545 _game->getSavedGame()->addFinishedResearch(_game->getRuleset()->getResearch(research->getLookup()), _game->getRuleset ());
1546 }
1547 popup(new ResearchCompleteState(_game, newResearch, bonus));
1548 std::vector<RuleResearch *> newPossibleResearch;
1549 _game->getSavedGame()->getDependableResearch (newPossibleResearch, (*iter)->getRules(), _game->getRuleset(), *i);
1550 std::vector<RuleManufacture *> newPossibleManufacture;
1551 _game->getSavedGame()->getDependableManufacture (newPossibleManufacture, (*iter)->getRules(), _game->getRuleset(), *i);
1552 timerReset();
1553 // check for possible researching weapon before clip
1554 if (newResearch)
1555 {
1556 RuleItem *item = _game->getRuleset()->getItem(newResearch->getName());
1557 if (item && item->getBattleType() == BT_FIREARM && !item->getCompatibleAmmo()->empty())
1558 {
1559 RuleManufacture *man = _game->getRuleset()->getManufacture(item->getType());
1560 if (man && !man->getRequirements().empty())
1561 {
1562 const std::vector<std::string> &req = man->getRequirements();
1563 RuleItem *ammo = _game->getRuleset()->getItem(item->getCompatibleAmmo()->front());
1564 if (ammo && std::find(req.begin(), req.end(), ammo->getType()) != req.end() && !_game->getSavedGame()->isResearched(req))
1565 {
1566 popup(new ResearchRequiredState(_game, item));
1567 }
1568 }
1569 }
1570 }
1571
1572 popup(new NewPossibleResearchState(_game, *i, newPossibleResearch));
1573 if (!newPossibleManufacture.empty())
1574 {
1575 popup(new NewPossibleManufactureState(_game, *i, newPossibleManufacture));
1576 }
1577 // now iterate through all the bases and remove this project from their labs
1578 for (std::vector<Base*>::iterator j = _game->getSavedGame()->getBases()->begin(); j != _game->getSavedGame()->getBases()->end(); ++j)
1579 {
1580 for (std::vector<ResearchProject*>::const_iterator iter2 = (*j)->getResearch().begin(); iter2 != (*j)->getResearch().end(); ++iter2)
1581 {
1582 if ((*iter)->getRules()->getName() == (*iter2)->getRules()->getName() &&
1583 _game->getRuleset()->getUnit((*iter2)->getRules()->getName()) == 0)
1584 {
1585 (*j)->removeResearch(*iter2);
1586 break;
1587 }
1588 }
1589 }
1590 delete(*iter);
1591 }
1592 // Handle soldier wounds
1593 for (std::vector<Soldier*>::iterator j = (*i)->getSoldiers()->begin(); j != (*i)->getSoldiers()->end(); ++j)
1594 {
1595 if ((*j)->getWoundRecovery() > 0)
1596 {
1597 (*j)->heal();
1598 }
1599 }
1600 // Handle psionic training
1601 if ((*i)->getAvailablePsiLabs() > 0 && Options::anytimePsiTraining)
1602 {
1603 for (std::vector<Soldier*>::const_iterator s = (*i)->getSoldiers()->begin(); s != (*i)->getSoldiers()->end(); ++s)
1604 {
1605 (*s)->trainPsi1Day();
1606 (*s)->calcStatString(_game->getRuleset()->getStatStrings(), (Options::psiStrengthEval && _game->getSavedGame()->isResearched(_game->getRuleset()->getPsiRequirements())));
1607 }
1608 }
1609 }
1610 // handle regional and country points for alien bases
1611 for(std::vector<AlienBase*>::const_iterator b = _game->getSavedGame()->getAlienBases()->begin(); b != _game->getSavedGame()->getAlienBases()->end(); ++b)
1612 {
1613 for (std::vector<Region*>::iterator k = _game->getSavedGame()->getRegions()->begin(); k != _game->getSavedGame()->getRegions()->end(); ++k)
1614 {
1615 if ((*k)->getRules()->insideRegion((*b)->getLongitude(), (*b)->getLatitude()))
1616 {
1617 (*k)->addActivityAlien(_game->getRuleset()->getAlienMission("STR_ALIEN_BASE")->getPoints() / 10);
1618 break;
1619 }
1620 }
1621 for (std::vector<Country*>::iterator k = _game->getSavedGame()->getCountries()->begin(); k != _game->getSavedGame()->getCountries()->end(); ++k)
1622 {
1623 if ((*k)->getRules()->insideCountry((*b)->getLongitude(), (*b)->getLatitude()))
1624 {
1625 (*k)->addActivityAlien(_game->getRuleset()->getAlienMission("STR_ALIEN_BASE")->getPoints() / 10);
1626 break;
1627 }
1628 }
1629 }
1630
1631 // Handle resupply of alien bases.
1632 std::for_each(_game->getSavedGame()->getAlienBases()->begin(), _game->getSavedGame()->getAlienBases()->end(),
1633 GenerateSupplyMission(*_game->getRuleset(), *_game->getSavedGame()));
1634
1635 // Autosave 3 times a month
1636 int day = _game->getSavedGame()->getTime()->getDay();
1637 if (day == 10 || day == 20)
1638 {
1639 if (_game->getSavedGame()->isIronman())
1640 {
1641 popup(new SaveGameState(_game, OPT_GEOSCAPE, SAVE_IRONMAN));
1642 }
1643 else if (Options::autosave)
1644 {
1645 popup(new SaveGameState(_game, OPT_GEOSCAPE, SAVE_AUTO_GEOSCAPE));
1646 }
1647 }
1648 }
1649
1650 /**
1651 * Takes care of any game logic that has to
1652 * run every game month, like funding.
1653 */
time1Month()1654 void GeoscapeState::time1Month()
1655 {
1656 _game->getSavedGame()->addMonth();
1657
1658 int monthsPassed = _game->getSavedGame()->getMonthsPassed();
1659 bool newRetaliation = false;
1660
1661 // Determine alien mission for this month.
1662 determineAlienMissions();
1663 if (monthsPassed > 5)
1664 determineAlienMissions();
1665
1666 setupTerrorMission();
1667
1668 if (monthsPassed >= 14 - (int)(_game->getSavedGame()->getDifficulty())
1669 || _game->getSavedGame()->isResearched("STR_THE_MARTIAN_SOLUTION"))
1670 {
1671 newRetaliation = true;
1672 }
1673
1674 // Handle Psi-Training and initiate a new retaliation mission, if applicable
1675 bool psi = false;
1676 for(std::vector<Base*>::const_iterator b = _game->getSavedGame()->getBases()->begin(); b != _game->getSavedGame()->getBases()->end(); ++b)
1677 {
1678 if (newRetaliation)
1679 {
1680 for (std::vector<Region*>::iterator i = _game->getSavedGame()->getRegions()->begin(); i != _game->getSavedGame()->getRegions()->end(); ++i)
1681 {
1682 if ((*i)->getRules()->insideRegion((*b)->getLongitude(), (*b)->getLatitude()))
1683 {
1684 if (!_game->getSavedGame()->getAlienMission((*i)->getRules()->getType(), "STR_ALIEN_RETALIATION"))
1685 {
1686 const RuleAlienMission &rule = *_game->getRuleset()->getAlienMission("STR_ALIEN_RETALIATION");
1687 AlienMission *mission = new AlienMission(rule);
1688 mission->setId(_game->getSavedGame()->getId("ALIEN_MISSIONS"));
1689 mission->setRegion((*i)->getRules()->getType(), *_game->getRuleset());
1690 // get races for retaliation missions
1691 std::vector<std::string> races = _game->getRuleset()->getAlienRacesList();
1692 for (std::vector<std::string>::iterator i = races.begin(); i != races.end();)
1693 {
1694 if (_game->getRuleset()->getAlienRace(*i)->canRetaliate())
1695 {
1696 i++;
1697 }
1698 else
1699 {
1700 i = races.erase(i);
1701 }
1702 }
1703 size_t race = RNG::generate(0, races.size()-1);
1704 mission->setRace(races[race]);
1705 mission->start(150);
1706 _game->getSavedGame()->getAlienMissions().push_back(mission);
1707 newRetaliation = false;
1708 }
1709 break;
1710 }
1711 }
1712 }
1713 if ((*b)->getAvailablePsiLabs() > 0 && !Options::anytimePsiTraining)
1714 {
1715 psi = true;
1716 for(std::vector<Soldier*>::const_iterator s = (*b)->getSoldiers()->begin(); s != (*b)->getSoldiers()->end(); ++s)
1717 {
1718 if ((*s)->isInPsiTraining())
1719 {
1720 (*s)->trainPsi();
1721 (*s)->calcStatString(_game->getRuleset()->getStatStrings(), (Options::psiStrengthEval && _game->getSavedGame()->isResearched(_game->getRuleset()->getPsiRequirements())));
1722 }
1723 }
1724 }
1725 }
1726
1727 // Handle funding
1728 timerReset();
1729 _game->getSavedGame()->monthlyFunding();
1730 popup(new MonthlyReportState(_game, psi, _globe));
1731
1732 // Handle Xcom Operatives discovering bases
1733 if (!_game->getSavedGame()->getAlienBases()->empty() && RNG::percent(20))
1734 {
1735 for(std::vector<AlienBase*>::const_iterator b = _game->getSavedGame()->getAlienBases()->begin(); b != _game->getSavedGame()->getAlienBases()->end(); ++b)
1736 {
1737 if (!(*b)->isDiscovered())
1738 {
1739 (*b)->setDiscovered(true);
1740 popup(new AlienBaseState(_game, *b, this));
1741 break;
1742 }
1743 }
1744 }
1745 }
1746
1747 /**
1748 * Slows down the timer back to minimum speed,
1749 * for when important events occur.
1750 */
timerReset()1751 void GeoscapeState::timerReset()
1752 {
1753 SDL_Event ev;
1754 ev.button.button = SDL_BUTTON_LEFT;
1755 Action act(&ev, _game->getScreen()->getXScale(), _game->getScreen()->getYScale(), _game->getScreen()->getCursorTopBlackBand(), _game->getScreen()->getCursorLeftBlackBand());
1756 _btn5Secs->mousePress(&act, this);
1757 }
1758
1759 /**
1760 * Adds a new popup window to the queue
1761 * (this prevents popups from overlapping)
1762 * and pauses the game timer respectively.
1763 * @param state Pointer to popup state.
1764 */
popup(State * state)1765 void GeoscapeState::popup(State *state)
1766 {
1767 _pause = true;
1768 _popups.push_back(state);
1769 }
1770
1771 /**
1772 * Returns a pointer to the Geoscape globe for
1773 * access by other substates.
1774 * @return Pointer to globe.
1775 */
getGlobe() const1776 Globe *GeoscapeState::getGlobe() const
1777 {
1778 return _globe;
1779 }
1780
1781 /**
1782 * Processes any left-clicks on globe markers,
1783 * or right-clicks to scroll the globe.
1784 * @param action Pointer to an action.
1785 */
1786
globeClick(Action * action)1787 void GeoscapeState::globeClick(Action *action)
1788 {
1789 int mouseX = (int)floor(action->getAbsoluteXMouse()), mouseY = (int)floor(action->getAbsoluteYMouse());
1790
1791 // Clicking markers on the globe
1792 if (action->getDetails()->button.button == SDL_BUTTON_LEFT)
1793 {
1794 std::vector<Target*> v = _globe->getTargets(mouseX, mouseY, false);
1795 if (!v.empty())
1796 {
1797 _game->pushState(new MultipleTargetsState(_game, v, 0, this));
1798 }
1799 }
1800
1801 if (_game->getSavedGame()->getDebugMode())
1802 {
1803 double lon, lat;
1804 _globe->cartToPolar(mouseX, mouseY, &lon, &lat);
1805 double lonDeg = lon / M_PI * 180, latDeg = lat / M_PI * 180;
1806 std::wostringstream ss;
1807 ss << "rad: " << lon << " , " << lat << std::endl;
1808 ss << "deg: " << lonDeg << " , " << latDeg << std::endl;
1809 _txtDebug->setText(ss.str());
1810 }
1811 }
1812
1813 /**
1814 * Opens the Intercept window.
1815 * @param action Pointer to an action.
1816 */
btnInterceptClick(Action *)1817 void GeoscapeState::btnInterceptClick(Action *)
1818 {
1819 _game->pushState(new InterceptState(_game, _globe));
1820 }
1821
1822 /**
1823 * Goes to the Basescape screen.
1824 * @param action Pointer to an action.
1825 */
btnBasesClick(Action *)1826 void GeoscapeState::btnBasesClick(Action *)
1827 {
1828 timerReset();
1829 if (!_game->getSavedGame()->getBases()->empty())
1830 {
1831 _game->pushState(new BasescapeState(_game, _game->getSavedGame()->getSelectedBase(), _globe));
1832 }
1833 else
1834 {
1835 _game->pushState(new BasescapeState(_game, 0, _globe));
1836 }
1837 }
1838
1839 /**
1840 * Goes to the Graphs screen.
1841 * @param action Pointer to an action.
1842 */
btnGraphsClick(Action *)1843 void GeoscapeState::btnGraphsClick(Action *)
1844 {
1845 _game->pushState(new GraphsState(_game));
1846 }
1847
1848 /**
1849 * Goes to the Ufopaedia window.
1850 * @param action Pointer to an action.
1851 */
btnUfopaediaClick(Action *)1852 void GeoscapeState::btnUfopaediaClick(Action *)
1853 {
1854 Ufopaedia::open(_game);
1855 }
1856
1857 /**
1858 * Opens the Options window.
1859 * @param action Pointer to an action.
1860 */
btnOptionsClick(Action *)1861 void GeoscapeState::btnOptionsClick(Action *)
1862 {
1863 _game->pushState(new PauseState(_game, OPT_GEOSCAPE));
1864 }
1865
1866 /**
1867 * Goes to the Funding screen.
1868 * @param action Pointer to an action.
1869 */
btnFundingClick(Action *)1870 void GeoscapeState::btnFundingClick(Action *)
1871 {
1872 _game->pushState(new FundingState(_game));
1873 }
1874
1875 /**
1876 * Starts rotating the globe to the left.
1877 * @param action Pointer to an action.
1878 */
btnRotateLeftPress(Action *)1879 void GeoscapeState::btnRotateLeftPress(Action *)
1880 {
1881 _globe->rotateLeft();
1882 }
1883
1884 /**
1885 * Stops rotating the globe to the left.
1886 * @param action Pointer to an action.
1887 */
btnRotateLeftRelease(Action *)1888 void GeoscapeState::btnRotateLeftRelease(Action *)
1889 {
1890 _globe->rotateStopLon();
1891 }
1892
1893 /**
1894 * Starts rotating the globe to the right.
1895 * @param action Pointer to an action.
1896 */
btnRotateRightPress(Action *)1897 void GeoscapeState::btnRotateRightPress(Action *)
1898 {
1899 _globe->rotateRight();
1900 }
1901
1902 /**
1903 * Stops rotating the globe to the right.
1904 * @param action Pointer to an action.
1905 */
btnRotateRightRelease(Action *)1906 void GeoscapeState::btnRotateRightRelease(Action *)
1907 {
1908 _globe->rotateStopLon();
1909 }
1910
1911 /**
1912 * Starts rotating the globe upwards.
1913 * @param action Pointer to an action.
1914 */
btnRotateUpPress(Action *)1915 void GeoscapeState::btnRotateUpPress(Action *)
1916 {
1917 _globe->rotateUp();
1918 }
1919
1920 /**
1921 * Stops rotating the globe upwards.
1922 * @param action Pointer to an action.
1923 */
btnRotateUpRelease(Action *)1924 void GeoscapeState::btnRotateUpRelease(Action *)
1925 {
1926 _globe->rotateStopLat();
1927 }
1928
1929 /**
1930 * Starts rotating the globe downwards.
1931 * @param action Pointer to an action.
1932 */
btnRotateDownPress(Action *)1933 void GeoscapeState::btnRotateDownPress(Action *)
1934 {
1935 _globe->rotateDown();
1936 }
1937
1938 /**
1939 * Stops rotating the globe downwards.
1940 * @param action Pointer to an action.
1941 */
btnRotateDownRelease(Action *)1942 void GeoscapeState::btnRotateDownRelease(Action *)
1943 {
1944 _globe->rotateStopLat();
1945 }
1946
1947 /**
1948 * Zooms into the globe.
1949 * @param action Pointer to an action.
1950 */
btnZoomInLeftClick(Action *)1951 void GeoscapeState::btnZoomInLeftClick(Action *)
1952 {
1953 _globe->zoomIn();
1954 }
1955
1956 /**
1957 * Zooms the globe maximum.
1958 * @param action Pointer to an action.
1959 */
btnZoomInRightClick(Action *)1960 void GeoscapeState::btnZoomInRightClick(Action *)
1961 {
1962 _globe->zoomMax();
1963 }
1964
1965 /**
1966 * Zooms out of the globe.
1967 * @param action Pointer to an action.
1968 */
btnZoomOutLeftClick(Action *)1969 void GeoscapeState::btnZoomOutLeftClick(Action *)
1970 {
1971 _globe->zoomOut();
1972 }
1973
1974 /**
1975 * Zooms the globe minimum.
1976 * @param action Pointer to an action.
1977 */
btnZoomOutRightClick(Action *)1978 void GeoscapeState::btnZoomOutRightClick(Action *)
1979 {
1980 _globe->zoomMin();
1981 }
1982
1983 /**
1984 * Zoom in effect for dogfights.
1985 */
zoomInEffect()1986 void GeoscapeState::zoomInEffect()
1987 {
1988 if (_globe->zoomDogfightIn())
1989 {
1990 _zoomInEffectDone = true;
1991 _zoomInEffectTimer->stop();
1992 }
1993 }
1994
1995 /**
1996 * Zoom out effect for dogfights.
1997 */
zoomOutEffect()1998 void GeoscapeState::zoomOutEffect()
1999 {
2000 if (_globe->zoomDogfightOut())
2001 {
2002 _zoomOutEffectDone = true;
2003 _zoomOutEffectTimer->stop();
2004 init();
2005 }
2006 }
2007
2008 /**
2009 * Dogfight logic. Moved here to have the code clean.
2010 */
handleDogfights()2011 void GeoscapeState::handleDogfights()
2012 {
2013 // If all dogfights are minimized rotate the globe, etc.
2014 if (_dogfights.size() == _minimizedDogfights)
2015 {
2016 _pause = false;
2017 _gameTimer->think(this, 0);
2018 }
2019 // Handle dogfights logic.
2020 _minimizedDogfights = 0;
2021 std::list<DogfightState*>::iterator d = _dogfights.begin();
2022 while(d != _dogfights.end())
2023 {
2024 if ((*d)->isMinimized())
2025 {
2026 _minimizedDogfights++;
2027 }
2028 else
2029 {
2030 _globe->rotateStop();
2031 }
2032 (*d)->think();
2033 if ((*d)->dogfightEnded())
2034 {
2035 if ((*d)->isMinimized())
2036 {
2037 _minimizedDogfights--;
2038 }
2039 delete *d;
2040 d = _dogfights.erase(d);
2041 }
2042 else
2043 {
2044 ++d;
2045 }
2046 }
2047 if (_dogfights.empty())
2048 {
2049 _zoomOutEffectTimer->start();
2050 }
2051 }
2052
2053 /**
2054 * Gets the number of minimized dogfights.
2055 * @return Number of minimized dogfights.
2056 */
minimizedDogfightsCount()2057 int GeoscapeState::minimizedDogfightsCount()
2058 {
2059 int minimizedDogfights = 0;
2060 for(std::list<DogfightState*>::iterator d = _dogfights.begin(); d != _dogfights.end(); ++d)
2061 {
2062 if ((*d)->isMinimized())
2063 {
2064 ++minimizedDogfights;
2065 }
2066 }
2067 return minimizedDogfights;
2068 }
2069
2070 /**
2071 * Starts a new dogfight.
2072 */
startDogfight()2073 void GeoscapeState::startDogfight()
2074 {
2075 if (_globe->getZoom() < 3)
2076 {
2077 if (!_zoomInEffectTimer->isRunning())
2078 {
2079 _globe->saveZoomDogfight();
2080 _globe->rotateStop();
2081 _zoomInEffectTimer->start();
2082 }
2083 }
2084 else
2085 {
2086 _dogfightStartTimer->stop();
2087 _zoomInEffectTimer->stop();
2088 timerReset();
2089 while(!_dogfightsToBeStarted.empty())
2090 {
2091 _dogfights.push_back(_dogfightsToBeStarted.back());
2092 _dogfightsToBeStarted.pop_back();
2093 _dogfights.back()->setInterceptionNumber(getFirstFreeDogfightSlot());
2094 _dogfights.back()->setInterceptionsCount(_dogfights.size() + _dogfightsToBeStarted.size());
2095 }
2096 // Set correct number of interceptions for every dogfight.
2097 for(std::list<DogfightState*>::iterator d = _dogfights.begin(); d != _dogfights.end(); ++d)
2098 {
2099 (*d)->setInterceptionsCount(_dogfights.size());
2100 }
2101 }
2102 }
2103
2104 /**
2105 * Returns the first free dogfight slot.
2106 * @return free slot
2107 */
getFirstFreeDogfightSlot()2108 int GeoscapeState::getFirstFreeDogfightSlot()
2109 {
2110 int slotNo = 1;
2111 for(std::list<DogfightState*>::iterator d = _dogfights.begin(); d != _dogfights.end(); ++d)
2112 {
2113 if ((*d)->getInterceptionNumber() == slotNo)
2114 {
2115 ++slotNo;
2116 }
2117 }
2118 return slotNo;
2119 }
2120
2121 /**
2122 * Handle base defense
2123 * @param base Base to defend.
2124 * @param ufo Ufo attacking base.
2125 */
handleBaseDefense(Base * base,Ufo * ufo)2126 void GeoscapeState::handleBaseDefense(Base *base, Ufo *ufo)
2127 {
2128 // Whatever happens in the base defense, the UFO has finished its duty
2129 ufo->setStatus(Ufo::DESTROYED);
2130
2131 if (base->getAvailableSoldiers(true) > 0)
2132 {
2133 SavedBattleGame *bgame = new SavedBattleGame();
2134 _game->getSavedGame()->setBattleGame(bgame);
2135 bgame->setMissionType("STR_BASE_DEFENSE");
2136 BattlescapeGenerator bgen = BattlescapeGenerator(_game);
2137 bgen.setBase(base);
2138 bgen.setAlienRace(ufo->getAlienRace());
2139 bgen.run();
2140 _pause = true;
2141 _game->pushState(new BriefingState(_game, 0, base));
2142 }
2143 else
2144 {
2145 // Please garrison your bases in future
2146 popup(new BaseDestroyedState(_game, base));
2147 }
2148 }
2149
2150 /**
2151 * Determine the alien missions to start this month.
2152 * In the vanilla game each month a terror mission and one other are started in
2153 * random regions.
2154 */
determineAlienMissions(bool atGameStart)2155 void GeoscapeState::determineAlienMissions(bool atGameStart)
2156 {
2157 if (!atGameStart)
2158 {
2159 //
2160 // One randomly selected mission.
2161 //
2162 AlienStrategy &strategy = _game->getSavedGame()->getAlienStrategy();
2163 const std::string &targetRegion = strategy.chooseRandomRegion(_game->getRuleset());
2164 const std::string &targetMission = strategy.chooseRandomMission(targetRegion);
2165 // Choose race for this mission.
2166 const RuleAlienMission &missionRules = *_game->getRuleset()->getAlienMission(targetMission);
2167 const std::string &missionRace = missionRules.generateRace(_game->getSavedGame()->getMonthsPassed());
2168 AlienMission *otherMission = new AlienMission(missionRules);
2169 otherMission->setId(_game->getSavedGame()->getId("ALIEN_MISSIONS"));
2170 otherMission->setRegion(targetRegion, *_game->getRuleset());
2171 otherMission->setRace(missionRace);
2172 otherMission->start();
2173 _game->getSavedGame()->getAlienMissions().push_back(otherMission);
2174 // Make sure this combination never comes up again.
2175 strategy.removeMission(targetRegion, targetMission);
2176 }
2177 else
2178 {
2179 //
2180 // Sectoid Research at base's region.
2181 //
2182 AlienStrategy &strategy = _game->getSavedGame()->getAlienStrategy();
2183 std::string targetRegion =
2184 _game->getSavedGame()->locateRegion(*_game->getSavedGame()->getBases()->front())->getRules()->getType();
2185 // Choose race for this mission.
2186 std::string research = _game->getRuleset()->getAlienMissionList().front();
2187 const RuleAlienMission &missionRules = *_game->getRuleset()->getAlienMission(research);
2188 AlienMission *otherMission = new AlienMission(missionRules);
2189 otherMission->setId(_game->getSavedGame()->getId("ALIEN_MISSIONS"));
2190 otherMission->setRegion(targetRegion, *_game->getRuleset());
2191 std::string sectoid = missionRules.getTopRace(_game->getSavedGame()->getMonthsPassed());
2192 otherMission->setRace(sectoid);
2193 otherMission->start(150);
2194 _game->getSavedGame()->getAlienMissions().push_back(otherMission);
2195 // Make sure this combination never comes up again.
2196 strategy.removeMission(targetRegion, research);
2197 }
2198 }
2199
setupTerrorMission()2200 void GeoscapeState::setupTerrorMission()
2201 {
2202 //Determine a random region with at least one city and no currently running terror mission.
2203 RuleRegion* region = 0;
2204 int counter = 0;
2205 std::vector<std::string> regions = _game->getRuleset()->getRegionsList();
2206 do
2207 {
2208 // we try 40 times to pick a valid zone for a terror mission
2209 if (counter == 40) return;
2210 region = _game->getRuleset()->getRegion(regions[RNG::generate(0, regions.size()-1)]);
2211 counter++;
2212 }
2213 while (region->getCities()->empty() || _game->getSavedGame()->getAlienMission(region->getType(), "STR_ALIEN_TERROR") != 0);
2214 // Choose race for terror mission.
2215 const RuleAlienMission &terrorRules = *_game->getRuleset()->getAlienMission("STR_ALIEN_TERROR");
2216 const std::string &terrorRace = terrorRules.generateRace(_game->getSavedGame()->getMonthsPassed());
2217 AlienMission *terrorMission = new AlienMission(terrorRules);
2218 terrorMission->setId(_game->getSavedGame()->getId("ALIEN_MISSIONS"));
2219 terrorMission->setRegion(region->getType(), *_game->getRuleset());
2220 terrorMission->setRace(terrorRace);
2221 terrorMission->start(150);
2222 _game->getSavedGame()->getAlienMissions().push_back(terrorMission);
2223 }
2224 /**
2225 * Handler for clicking on a timer button.
2226 * @param action pointer to the mouse action.
2227 */
btnTimerClick(Action * action)2228 void GeoscapeState::btnTimerClick(Action *action)
2229 {
2230 SDL_Event ev;
2231 ev.type = SDL_MOUSEBUTTONDOWN;
2232 ev.button.button = SDL_BUTTON_LEFT;
2233 Action a = Action(&ev, 0.0, 0.0, 0, 0);
2234 action->getSender()->mousePress(&a, this);
2235 }
2236
2237 /**
2238 * Updates the scale.
2239 * @param dX delta of X;
2240 * @param dY delta of Y;
2241 */
resize(int & dX,int & dY)2242 void GeoscapeState::resize(int &dX, int &dY)
2243 {
2244 if (_game->getSavedGame()->getSavedBattle())
2245 return;
2246 dX = Options::baseXResolution;
2247 dY = Options::baseYResolution;
2248 int divisor = 1;
2249 switch (Options::geoscapeScale)
2250 {
2251 case SCALE_SCREEN_DIV_3:
2252 divisor = 3;
2253 break;
2254 case SCALE_SCREEN_DIV_2:
2255 divisor = 2;
2256 break;
2257 case SCALE_SCREEN:
2258 break;
2259 default:
2260 dX = 0;
2261 dY = 0;
2262 return;
2263 }
2264
2265 Options::baseXResolution = std::max(Screen::ORIGINAL_WIDTH, Options::displayWidth / divisor);
2266 Options::baseYResolution = std::max(Screen::ORIGINAL_HEIGHT, Options::displayHeight / divisor);
2267
2268 dX = Options::baseXResolution - dX;
2269 dY = Options::baseYResolution - dY;
2270
2271 _globe->resize();
2272
2273 for (std::vector<Surface*>::const_iterator i = _surfaces.begin(); i != _surfaces.end(); ++i)
2274 {
2275 if (*i != _globe)
2276 {
2277 (*i)->setX((*i)->getX() + dX);
2278 (*i)->setY((*i)->getY() + dY/2);
2279 }
2280 }
2281
2282 _bg->setX((_globe->getWidth() - _bg->getWidth()) / 2);
2283 _bg->setY((_globe->getHeight() - _bg->getHeight()) / 2);
2284
2285 int height = (Options::baseYResolution - Screen::ORIGINAL_HEIGHT) / 2 + 10;
2286 _sideTop->setHeight(height);
2287 _sideTop->setY(_sidebar->getY() - height - 1);
2288 _sideBottom->setHeight(height);
2289 _sideBottom->setY(_sidebar->getY() + _sidebar->getHeight() + 1);
2290
2291 _sideLine->setHeight(Options::baseYResolution);
2292 _sideLine->setY(0);
2293 _sideLine->drawRect(0, 0, _sideLine->getWidth(), _sideLine->getHeight(), 15);
2294 }
2295
2296 }
2297