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 #include "DogfightState.h"
20 #include <sstream>
21 #include "../Engine/Game.h"
22 #include "../Resource/ResourcePack.h"
23 #include "../Engine/Palette.h"
24 #include "../Engine/Screen.h"
25 #include "../Engine/Language.h"
26 #include "../Engine/SurfaceSet.h"
27 #include "../Engine/Surface.h"
28 #include "../Interface/ImageButton.h"
29 #include "../Interface/Text.h"
30 #include "../Engine/Timer.h"
31 #include "Globe.h"
32 #include "../Savegame/SavedGame.h"
33 #include "../Savegame/Craft.h"
34 #include "../Ruleset/RuleCraft.h"
35 #include "../Savegame/CraftWeapon.h"
36 #include "../Ruleset/RuleCraftWeapon.h"
37 #include "../Savegame/Ufo.h"
38 #include "../Ruleset/RuleUfo.h"
39 #include "../Engine/Music.h"
40 #include "../Engine/RNG.h"
41 #include "../Engine/Sound.h"
42 #include "../Savegame/Base.h"
43 #include "../Savegame/CraftWeaponProjectile.h"
44 #include "../Savegame/Country.h"
45 #include "../Ruleset/RuleCountry.h"
46 #include "../Savegame/Region.h"
47 #include "../Ruleset/RuleRegion.h"
48 #include "../Savegame/AlienMission.h"
49 #include "../Ruleset/Ruleset.h"
50 #include "../Savegame/AlienStrategy.h"
51 #include "../Engine/Options.h"
52 #include <cstdlib>
53
54 namespace OpenXcom
55 {
56
57 // UFO blobs graphics ...
58 const int DogfightState::_ufoBlobs[8][13][13] =
59 {
60 /*0 STR_VERY_SMALL */
61 {
62 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
63 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
64 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
65 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
66 {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
67 {0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0},
68 {0, 0, 0, 0, 1, 3, 5, 3, 1, 0, 0, 0, 0},
69 {0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0},
70 {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
71 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
72 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
73 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
74 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
75 },
76 /*1 STR_SMALL */
77 {
78 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
79 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
80 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
81 {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
82 {0, 0, 0, 0, 1, 2, 2, 2, 1, 0, 0, 0, 0},
83 {0, 0, 0, 1, 2, 3, 4, 3, 2, 1, 0, 0, 0},
84 {0, 0, 0, 1, 2, 4, 5, 4, 2, 1, 0, 0, 0},
85 {0, 0, 0, 1, 2, 3, 4, 3, 2, 1, 0, 0, 0},
86 {0, 0, 0, 0, 1, 2, 2, 2, 1, 0, 0, 0, 0},
87 {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
88 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
89 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
90 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
91 },
92 /*2 STR_MEDIUM_UC */
93 {
94 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
95 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
96 {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
97 {0, 0, 0, 1, 1, 2, 2, 2, 1, 1, 0, 0, 0},
98 {0, 0, 0, 1, 2, 3, 3, 3, 2, 1, 0, 0, 0},
99 {0, 0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0, 0},
100 {0, 0, 1, 2, 3, 5, 5, 5, 3, 2, 1, 0, 0},
101 {0, 0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0, 0},
102 {0, 0, 0, 1, 2, 3, 3, 3, 2, 1, 0, 0, 0},
103 {0, 0, 0, 1, 1, 2, 2, 2, 1, 1, 0, 0, 0},
104 {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
105 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
106 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
107 },
108 /*3 STR_LARGE */
109 {
110 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
111 {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
112 {0, 0, 0, 1, 1, 2, 2, 2, 1, 1, 0, 0, 0},
113 {0, 0, 1, 2, 2, 3, 3, 3, 2, 2, 1, 0, 0},
114 {0, 0, 1, 2, 3, 4, 4, 4, 3, 2, 1, 0, 0},
115 {0, 1, 2, 3, 4, 5, 5, 5, 4, 3, 2, 1, 0},
116 {0, 1, 2, 3, 4, 5, 5, 5, 4, 3, 2, 1, 0},
117 {0, 1, 2, 3, 4, 5, 5, 5, 4, 3, 2, 1, 0},
118 {0, 0, 1, 2, 3, 4, 4, 4, 3, 2, 1, 0, 0},
119 {0, 0, 1, 2, 2, 3, 3, 3, 2, 2, 1, 0, 0},
120 {0, 0, 0, 1, 1, 2, 2, 2, 1, 1, 0, 0, 0},
121 {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
122 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
123 },
124 /*4 STR_VERY_LARGE */
125 {
126 {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0},
127 {0, 0, 0, 1, 1, 2, 2, 2, 1, 1, 0, 0, 0},
128 {0, 0, 1, 2, 2, 3, 3, 3, 2, 2, 1, 0, 0},
129 {0, 1, 2, 3, 3, 4, 4, 4, 3, 3, 2, 1, 0},
130 {0, 1, 2, 3, 4, 5, 5, 5, 4, 3, 2, 1, 0},
131 {1, 2, 3, 4, 5, 5, 5, 5, 5, 4, 3, 2, 1},
132 {1, 2, 3, 4, 5, 5, 5, 5, 5, 4, 3, 2, 1},
133 {1, 2, 3, 4, 5, 5, 5, 5, 5, 4, 3, 2, 1},
134 {0, 1, 2, 3, 4, 5, 5, 5, 4, 3, 2, 1, 0},
135 {0, 1, 2, 3, 3, 4, 4, 4, 3, 3, 2, 1, 0},
136 {0, 0, 1, 2, 2, 3, 3, 3, 2, 2, 1, 0, 0},
137 {0, 0, 0, 1, 1, 2, 2, 2, 1, 1, 0, 0, 0},
138 {0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0}
139 },
140 /*5 STR_HUGE */
141 {
142 {0, 0, 0, 1, 1, 2, 2, 2, 1, 1, 0, 0, 0},
143 {0, 0, 1, 2, 2, 3, 3, 3, 2, 2, 1, 0, 0},
144 {0, 1, 2, 3, 3, 4, 4, 4, 3, 3, 2, 1, 0},
145 {1, 2, 3, 4, 4, 5, 5, 5, 4, 4, 3, 2, 1},
146 {1, 2, 3, 4, 5, 5, 5, 5, 5, 4, 3, 2, 1},
147 {2, 3, 4, 5, 5, 5, 5, 5, 5, 5, 4, 3, 2},
148 {2, 3, 4, 5, 5, 5, 5, 5, 5, 5, 4, 3, 2},
149 {2, 3, 4, 5, 5, 5, 5, 5, 5, 5, 4, 3, 2},
150 {1, 2, 3, 4, 5, 5, 5, 5, 5, 4, 3, 2, 1},
151 {1, 2, 3, 4, 4, 5, 5, 5, 4, 4, 3, 2, 1},
152 {0, 1, 2, 3, 3, 4, 4, 4, 3, 3, 2, 1, 0},
153 {0, 0, 1, 2, 2, 3, 3, 3, 2, 2, 1, 0, 0},
154 {0, 0, 0, 1, 1, 2, 2, 2, 1, 1, 0, 0, 0}
155 },
156 /*6 STR_VERY_HUGE :p */
157 {
158 {0, 0, 0, 2, 2, 3, 3, 3, 2, 2, 0, 0, 0},
159 {0, 0, 2, 3, 3, 4, 4, 4, 3, 3, 2, 0, 0},
160 {0, 2, 3, 4, 4, 5, 5, 5, 4, 4, 3, 2, 0},
161 {2, 3, 4, 5, 5, 5, 5, 5, 5, 5, 4, 3, 2},
162 {2, 3, 4, 5, 5, 5, 5, 5, 5, 5, 4, 3, 2},
163 {3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 3},
164 {3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 3},
165 {3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 3},
166 {2, 3, 4, 5, 5, 5, 5, 5, 5, 5, 4, 3, 2},
167 {2, 3, 4, 5, 5, 5, 5, 5, 5, 5, 4, 3, 2},
168 {0, 2, 3, 4, 4, 5, 5, 5, 4, 4, 3, 2, 0},
169 {0, 0, 2, 3, 3, 4, 4, 4, 3, 3, 2, 0, 0},
170 {0, 0, 0, 2, 2, 3, 3, 3, 2, 2, 0, 0, 0}
171 },
172 /*7 STR_ENOURMOUS */
173 {
174 {0, 0, 0, 3, 3, 4, 4, 4, 3, 3, 0, 0, 0},
175 {0, 0, 3, 4, 4, 5, 5, 5, 4, 4, 3, 0, 0},
176 {0, 3, 4, 5, 5, 5, 5, 5, 5, 5, 4, 3, 0},
177 {3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 3},
178 {3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 3},
179 {4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4},
180 {4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4},
181 {4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4},
182 {3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 3},
183 {3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 3},
184 {0, 3, 4, 5, 5, 5, 5, 5, 5, 5, 4, 3, 0},
185 {0, 0, 3, 4, 4, 5, 5, 5, 4, 4, 3, 0, 0},
186 {0, 0, 0, 3, 3, 4, 4, 4, 3, 3, 0, 0, 0}
187 }
188 };
189
190 // Projectile blobs
191 const int DogfightState::_projectileBlobs[4][6][3] =
192 {
193 /*0 STR_STINGRAY_MISSILE ?*/
194 {
195 {0, 1, 0},
196 {1, 9, 1},
197 {1, 4, 1},
198 {0, 3, 0},
199 {0, 2, 0},
200 {0, 1, 0}
201 },
202 /*1 STR_AVALANCHE_MISSILE ?*/
203 {
204 {1, 2, 1},
205 {2, 9, 2},
206 {2, 5, 2},
207 {1, 3, 1},
208 {0, 2, 0},
209 {0, 1, 0}
210 },
211 /*2 STR_CANNON_ROUND ?*/
212 {
213 {0, 0, 0},
214 {0, 7, 0},
215 {0, 2, 0},
216 {0, 1, 0},
217 {0, 0, 0},
218 {0, 0, 0}
219 },
220 /*3 STR_FUSION_BALL ?*/
221 {
222 {2, 4, 2},
223 {4, 9, 4},
224 {2, 4, 2},
225 {0, 0, 0},
226 {0, 0, 0},
227 {0, 0, 0}
228 }
229 };
230 /**
231 * Initializes all the elements in the Dogfight window.
232 * @param game Pointer to the core game.
233 * @param globe Pointer to the Geoscape globe.
234 * @param craft Pointer to the craft intercepting.
235 * @param ufo Pointer to the UFO being intercepted.
236 */
DogfightState(Game * game,Globe * globe,Craft * craft,Ufo * ufo)237 DogfightState::DogfightState(Game *game, Globe *globe, Craft *craft, Ufo *ufo) : State(game), _globe(globe), _craft(craft), _ufo(ufo), _timeout(50), _currentDist(640), _targetDist(560), _end(false), _destroyUfo(false), _destroyCraft(false), _ufoBreakingOff(false), _weapon1Enabled(true), _weapon2Enabled(true), _minimized(false), _endDogfight(false), _animatingHit(false), _ufoSize(0), _craftHeight(0), _currentCraftDamageColor(13), _interceptionsCount(0), _interceptionNumber(0), _x(0), _y(0), _minimizedIconX(0), _minimizedIconY(0)
238 {
239 _screen = false;
240
241 _craft->setInDogfight(true);
242 _timeScale = 50 + Options::dogfightSpeed;
243
244 // Create objects
245 _window = new Surface(160, 96, _x, _y);
246 _battle = new Surface(77, 74, _x + 3, _y + 3);
247 _weapon1 = new InteractiveSurface(15, 17, _x + 4, _y + 52);
248 _range1 = new Surface(21, 74, _x + 19, _y + 3);
249 _weapon2 = new InteractiveSurface(15, 17, _x + 64, _y + 52);
250 _range2 = new Surface(21, 74, _x + 43, _y + 3);
251 _damage = new Surface(22, 25, _x + 93, _y + 40);
252
253 _btnMinimize = new InteractiveSurface(12, 12, _x, _y);
254 _preview = new InteractiveSurface(160, 96, _x, _y);
255 _btnStandoff = new ImageButton(36, 15, _x + 83, _y + 4);
256 _btnCautious = new ImageButton(36, 15, _x + 120, _y + 4);
257 _btnStandard = new ImageButton(36, 15, _x + 83, _y + 20);
258 _btnAggressive = new ImageButton(36, 15, _x + 120, _y + 20);
259 _btnDisengage = new ImageButton(36, 15, _x + 120, _y + 36);
260 _btnUfo = new ImageButton(36, 17, _x + 120, _y + 52);
261 _txtAmmo1 = new Text(16, 9, _x + 4, _y + 70);
262 _txtAmmo2 = new Text(16, 9, _x + 64, _y + 70);
263 _txtDistance = new Text(40, 9, _x + 116, _y + 72);
264 _txtStatus = new Text(150, 9, _x + 4, _y + 85);
265 _btnMinimizedIcon = new InteractiveSurface(32, 20, _minimizedIconX, _minimizedIconY);
266 _txtInterceptionNumber = new Text(16, 9, _minimizedIconX + 18, _minimizedIconY + 6);
267
268 _animTimer = new Timer(Options::dogfightSpeed + 10);
269 _moveTimer = new Timer(Options::dogfightSpeed);
270 _w1Timer = new Timer(0);
271 _w2Timer = new Timer(0);
272 _mode = _btnStandoff;
273 _ufoWtimer = new Timer(0);
274 _ufoEscapeTimer = new Timer(0);
275 _craftDamageAnimTimer = new Timer(500);
276
277 // Set palette
278 setPalette("PAL_GEOSCAPE");
279
280 add(_window);
281 add(_battle);
282 add(_weapon1);
283 add(_range1);
284 add(_weapon2);
285 add(_range2);
286 add(_damage);
287 add(_btnMinimize);
288 add(_btnStandoff);
289 add(_btnCautious);
290 add(_btnStandard);
291 add(_btnAggressive);
292 add(_btnDisengage);
293 add(_btnUfo);
294 add(_txtAmmo1);
295 add(_txtAmmo2);
296 add(_txtDistance);
297 add(_preview);
298 add(_txtStatus);
299 add(_btnMinimizedIcon);
300 add(_txtInterceptionNumber);
301
302 // Set up objects
303 Surface *graphic;
304 graphic = _game->getResourcePack()->getSurface("INTERWIN.DAT");
305 graphic->setX(0);
306 graphic->setY(0);
307 graphic->getCrop()->x = 0;
308 graphic->getCrop()->y = 0;
309 graphic->getCrop()->w = 160;
310 graphic->getCrop()->h = 96;
311 _window->drawRect(graphic->getCrop(), 15);
312 graphic->blit(_window);
313
314 _preview->drawRect(graphic->getCrop(), 15);
315 graphic->getCrop()->y = 96;
316 graphic->getCrop()->h = 15;
317 graphic->blit(_preview);
318 graphic->setY(67);
319 graphic->getCrop()->y = 111;
320 graphic->getCrop()->h = 29;
321 graphic->blit(_preview);
322 if (ufo->getRules()->getModSprite() == "")
323 {
324 graphic->setY(15);
325 graphic->getCrop()->y = 140 + 52 * _ufo->getRules()->getSprite();
326 graphic->getCrop()->h = 52;
327 }
328 else
329 {
330 graphic = _game->getResourcePack()->getSurface(ufo->getRules()->getModSprite());
331 graphic->setX(0);
332 graphic->setY(15);
333 }
334 graphic->blit(_preview);
335 _preview->setVisible(false);
336 _preview->onMouseClick((ActionHandler)&DogfightState::previewClick);
337
338 _btnMinimize->onMouseClick((ActionHandler)&DogfightState::btnMinimizeClick);
339
340 _btnStandoff->copy(_window);
341 _btnStandoff->setColor(Palette::blockOffset(5)+1);
342 _btnStandoff->setGroup(&_mode);
343 _btnStandoff->onMouseClick((ActionHandler)&DogfightState::btnStandoffClick);
344
345 _btnCautious->copy(_window);
346 _btnCautious->setColor(Palette::blockOffset(5)+1);
347 _btnCautious->setGroup(&_mode);
348 _btnCautious->onMouseClick((ActionHandler)&DogfightState::btnCautiousClick);
349
350 _btnStandard->copy(_window);
351 _btnStandard->setColor(Palette::blockOffset(5)+1);
352 _btnStandard->setGroup(&_mode);
353 _btnStandard->onMouseClick((ActionHandler)&DogfightState::btnStandardClick);
354
355 _btnAggressive->copy(_window);
356 _btnAggressive->setColor(Palette::blockOffset(5)+1);
357 _btnAggressive->setGroup(&_mode);
358 _btnAggressive->onMouseClick((ActionHandler)&DogfightState::btnAggressiveClick);
359
360 _btnDisengage->copy(_window);
361 _btnDisengage->setColor(Palette::blockOffset(5)+1);
362 _btnDisengage->onMouseClick((ActionHandler)&DogfightState::btnDisengageClick);
363 _btnDisengage->setGroup(&_mode);
364
365 _btnUfo->copy(_window);
366 _btnUfo->setColor(Palette::blockOffset(5)+1);
367 _btnUfo->onMouseClick((ActionHandler)&DogfightState::btnUfoClick);
368
369 _txtAmmo1->setColor(Palette::blockOffset(5)+9);
370
371 _txtAmmo2->setColor(Palette::blockOffset(5)+9);
372
373 _txtDistance->setColor(Palette::blockOffset(5)+9);
374 _txtDistance->setText(L"640");
375
376 _txtStatus->setColor(Palette::blockOffset(5)+9);
377 _txtStatus->setText(tr("STR_STANDOFF"));
378
379 SurfaceSet *set = _game->getResourcePack()->getSurfaceSet("INTICON.PCK");
380
381 // Create the minimized dogfight icon.
382 Surface *frame = set->getFrame(_craft->getRules()->getSprite());
383 frame->setX(0);
384 frame->setY(0);
385 frame->blit(_btnMinimizedIcon);
386 _btnMinimizedIcon->onMouseClick((ActionHandler)&DogfightState::btnMinimizedIconClick);
387 _btnMinimizedIcon->setVisible(false);
388
389 // Draw correct number on the minimized dogfight icon.
390 std::wostringstream ss1;
391 ss1 << _craft->getInterceptionOrder();
392 _txtInterceptionNumber->setColor(Palette::blockOffset(5));
393 _txtInterceptionNumber->setText(ss1.str());
394 _txtInterceptionNumber->setVisible(false);
395
396 for (int i = 0; i < _craft->getRules()->getWeapons(); ++i)
397 {
398 CraftWeapon *w = _craft->getWeapons()->at(i);
399 if (w == 0)
400 continue;
401
402 Surface *weapon = 0, *range = 0;
403 Text *ammo = 0;
404 int x1, x2;
405 if (i == 0)
406 {
407 weapon = _weapon1;
408 range = _range1;
409 ammo = _txtAmmo1;
410 x1 = 2;
411 x2 = 0;
412 }
413 else
414 {
415 weapon = _weapon2;
416 range = _range2;
417 ammo = _txtAmmo2;
418 x1 = 0;
419 x2 = 18;
420 }
421
422 // Draw weapon icon
423 frame = set->getFrame(w->getRules()->getSprite() + 5);
424
425 frame->setX(0);
426 frame->setY(0);
427 frame->blit(weapon);
428
429 // Draw ammo
430 std::wostringstream ss;
431 ss << w->getAmmo();
432 ammo->setText(ss.str());
433
434 // Draw range (1 km = 1 pixel)
435 Uint8 color = Palette::blockOffset(7) - 1;
436 range->lock();
437
438 int rangeY = range->getHeight() - w->getRules()->getRange(), connectY = 57;
439 for (int x = x1; x <= x1 + 18; x += 2)
440 {
441 range->setPixel(x, rangeY, color);
442 }
443
444 int minY = 0, maxY = 0;
445 if (rangeY < connectY)
446 {
447 minY = rangeY;
448 maxY = connectY;
449 }
450 else if (rangeY > connectY)
451 {
452 minY = connectY;
453 maxY = rangeY;
454 }
455 for (int y = minY; y <= maxY; ++y)
456 {
457 range->setPixel(x1 + x2, y, color);
458 }
459 for (int x = x2; x <= x2 + 2; ++x)
460 {
461 range->setPixel(x, connectY, color);
462 }
463 range->unlock();
464 }
465
466 if (!(_craft->getRules()->getWeapons() > 0 && _craft->getWeapons()->at(0) != 0))
467 {
468 _weapon1->setVisible(false);
469 _range1->setVisible(false);
470 _txtAmmo1->setVisible(false);
471 }
472 if (!(_craft->getRules()->getWeapons() > 1 && _craft->getWeapons()->at(1) != 0))
473 {
474 _weapon2->setVisible(false);
475 _range2->setVisible(false);
476 _txtAmmo2->setVisible(false);
477 }
478
479 // Draw damage indicator.
480 frame = set->getFrame(_craft->getRules()->getSprite() + 11);
481 frame->setX(0);
482 frame->setY(0);
483 frame->blit(_damage);
484
485 _animTimer->onTimer((StateHandler)&DogfightState::animate);
486 _animTimer->start();
487
488 _moveTimer->onTimer((StateHandler)&DogfightState::move);
489 _moveTimer->start();
490
491 _w1Timer->onTimer((StateHandler)&DogfightState::fireWeapon1);
492
493 _w2Timer->onTimer((StateHandler)&DogfightState::fireWeapon2);
494
495 _ufoWtimer->onTimer((StateHandler)&DogfightState::ufoFireWeapon);
496 _ufoFireInterval = (_ufo->getRules()->getWeaponReload() - (int)(_game->getSavedGame()->getDifficulty()));
497 _ufoFireInterval = (RNG::generate(0, _ufoFireInterval) + _ufoFireInterval) * _timeScale;
498 _ufoWtimer->setInterval(_ufoFireInterval);
499
500 _ufoEscapeTimer->onTimer((StateHandler)&DogfightState::ufoBreakOff);
501 int ufoBreakOffInterval = (_ufo->getRules()->getBreakOffTime() + RNG::generate(0, _ufo->getRules()->getBreakOffTime()) - 15 * (int)(_game->getSavedGame()->getDifficulty())) * _timeScale;
502 _ufoEscapeTimer->setInterval(ufoBreakOffInterval);
503
504 _craftDamageAnimTimer->onTimer((StateHandler)&DogfightState::animateCraftDamage);
505
506 // Set UFO size - going to be moved to Ufo class to implement simultanous dogfights.
507 std::string ufoSize = _ufo->getRules()->getSize();
508 if(ufoSize.compare("STR_VERY_SMALL") == 0)
509 {
510 _ufoSize = 0;
511 }
512 else if(ufoSize.compare("STR_SMALL") == 0)
513 {
514 _ufoSize = 1;
515 }
516 else if(ufoSize.compare("STR_MEDIUM_UC") == 0)
517 {
518 _ufoSize = 2;
519 }
520 else if(ufoSize.compare("STR_LARGE") == 0)
521 {
522 _ufoSize = 3;
523 }
524 else
525 {
526 _ufoSize = 4;
527 }
528
529 // Get crafts height. Used for damage indication.
530 int x =_damage->getWidth() / 2;
531 for(int y = 0; y < _damage->getHeight(); ++y)
532 {
533 Uint8 pixelColor = _damage->getPixel(x, y);
534 if(pixelColor >= Palette::blockOffset(10) || pixelColor < Palette::blockOffset(11))
535 {
536 ++_craftHeight;
537 }
538 }
539
540 drawCraftDamage();
541
542 // Used for weapon toggling.
543 _weapon1->onMouseClick((ActionHandler)&DogfightState::weapon1Click);
544 _weapon2->onMouseClick((ActionHandler)&DogfightState::weapon2Click);
545 }
546
547 /**
548 * Deletes timers.
549 */
~DogfightState()550 DogfightState::~DogfightState()
551 {
552 delete _animTimer;
553 delete _moveTimer;
554 delete _w1Timer;
555 delete _w2Timer;
556 delete _ufoWtimer;
557 delete _ufoEscapeTimer;
558 delete _craftDamageAnimTimer;
559 while(!_projectiles.empty())
560 {
561 delete _projectiles.back();
562 _projectiles.pop_back();
563 }
564 if (_craft)
565 _craft->setInDogfight(false);
566 }
567
568 /**
569 * Runs the dogfighter timers.
570 */
think()571 void DogfightState::think()
572 {
573 if(!_endDogfight)
574 {
575 _moveTimer->think(this, 0);
576 if(!_endDogfight && !_minimized) // check _endDogfight again, because moveTimer can change it
577 {
578 _animTimer->think(this, 0);
579 _w1Timer->think(this, 0);
580 _w2Timer->think(this, 0);
581 _ufoWtimer->think(this, 0);
582 _ufoEscapeTimer->think(this, 0);
583 _craftDamageAnimTimer->think(this, 0);
584 }
585 else if(!_endDogfight && (_craft->getDestination() != _ufo || _ufo->getStatus() == Ufo::LANDED))
586 {
587 endDogfight();
588 }
589 }
590 }
591
592 /**
593 * Animates interceptor damage by changing the color and redrawing the image.
594 */
animateCraftDamage()595 void DogfightState::animateCraftDamage()
596 {
597 if(_minimized)
598 {
599 return;
600 }
601 --_currentCraftDamageColor;
602 if(_currentCraftDamageColor < 13)
603 {
604 _currentCraftDamageColor = 14;
605 }
606 drawCraftDamage();
607 }
608
609 /**
610 * Draws interceptor damage according to percentage of HP's left.
611 */
drawCraftDamage()612 void DogfightState::drawCraftDamage()
613 {
614 if(_minimized)
615 {
616 return;
617 }
618 if(_craft->getDamagePercentage() != 0)
619 {
620 if(!_craftDamageAnimTimer->isRunning())
621 {
622 _craftDamageAnimTimer->start();
623 }
624 int damagePercentage = _craft->getDamagePercentage();
625 int rowsToColor = (int)floor((double)_craftHeight * (double)(damagePercentage / 100.));
626 if(rowsToColor == 0)
627 {
628 return;
629 }
630 int rowsColored = 0;
631 bool rowColored = false;
632 for(int y = 0; y < _damage->getHeight(); ++y)
633 {
634 rowColored = false;
635 for(int x = 0; x < _damage->getWidth(); ++x)
636 {
637 int pixelColor = _damage->getPixel(x, y);
638 if(pixelColor == 13 || pixelColor == 14)
639 {
640 _damage->setPixel(x, y, _currentCraftDamageColor);
641 rowColored = true;
642 }
643 if(pixelColor >= Palette::blockOffset(10) && pixelColor < Palette::blockOffset(11))
644 {
645 _damage->setPixel(x, y, _currentCraftDamageColor);
646 rowColored = true;
647 }
648 }
649 if(rowColored)
650 {
651 ++rowsColored;
652 }
653 if(rowsColored == rowsToColor)
654 {
655 break;
656 }
657 }
658 }
659 }
660
661 /**
662 * Animates the window with a palette effect.
663 */
animate()664 void DogfightState::animate()
665 {
666 if(_minimized)
667 {
668 return;
669 }
670 // Animate radar waves and other stuff.
671 for(int x = 0; x < _window->getWidth(); ++x)
672 {
673 for(int y = 0; y < _window->getHeight(); ++y)
674 {
675 Uint8 radarPixelColor = _window->getPixel(x, y);
676 if(radarPixelColor >= Palette::blockOffset(7) && radarPixelColor < Palette::blockOffset(7) + 16)
677 {
678 ++radarPixelColor;
679 if(radarPixelColor >= Palette::blockOffset(7) + 16)
680 {
681 radarPixelColor = Palette::blockOffset(7);
682 }
683 _window->setPixel(x, y, radarPixelColor);
684 }
685 }
686 }
687
688 _battle->clear();
689
690 // Draw UFO.
691 if(!_ufo->isDestroyed())
692 {
693 drawUfo();
694 }
695
696 // Draw projectiles.
697 for(std::vector<CraftWeaponProjectile*>::iterator it = _projectiles.begin(); it != _projectiles.end(); ++it)
698 {
699 drawProjectile((*it));
700 }
701
702 // Clears text after a while
703 if (_timeout == 0)
704 {
705 _txtStatus->setText(L"");
706 }
707 else
708 {
709 _timeout--;
710 }
711
712 // Animate UFO hit.
713 bool lastHitAnimFrame = false;
714 if(_animatingHit && _ufo->getHitFrame() > 0)
715 {
716 _ufo->setHitFrame(_ufo->getHitFrame() - 1);
717 if(_ufo->getHitFrame() == 0)
718 {
719 _animatingHit = false;
720 lastHitAnimFrame = true;
721 }
722 }
723
724 // Animate UFO crash landing.
725 if(_ufo->isCrashed() && _ufo->getHitFrame() == 0 && !lastHitAnimFrame)
726 {
727 --_ufoSize;
728 }
729 }
730
731 /**
732 * Moves the craft towards the UFO according to
733 * the current interception mode. Handles projectile movements as well.
734 */
move()735 void DogfightState::move()
736 {
737 bool finalRun = false;
738 // Check if craft is not low on fuel when window minimized, and
739 // Check if crafts destination hasn't been changed when window minimized.
740 Ufo* u = dynamic_cast<Ufo*>(_craft->getDestination());
741 if (u != _ufo || _craft->getLowFuel() || (_minimized && _ufo->isCrashed()))
742 {
743 endDogfight();
744 return;
745 }
746
747 if(_minimized && _ufo->getSpeed() > _craft->getSpeed())
748 {
749 _craft->setSpeed(_craft->getRules()->getMaxSpeed());
750 if(_ufo->getSpeed() > _craft->getSpeed())
751 {
752 _ufoBreakingOff = true;
753 finalRun = true;
754 }
755 }
756 // Check if UFO is not breaking off.
757 if(_ufo->getSpeed() == _ufo->getRules()->getMaxSpeed())
758 {
759 _craft->setSpeed(_craft->getRules()->getMaxSpeed());
760 // Crappy craft is chasing UFO.
761 if(_ufo->getSpeed() > _craft->getSpeed())
762 {
763 _ufoBreakingOff = true;
764 finalRun = true;
765 setStatus("STR_UFO_OUTRUNNING_INTERCEPTOR");
766 }
767 else //ufo cannot break off, because it's too slow
768 {
769 _ufoBreakingOff = false;
770 }
771
772 }
773 bool projectileInFlight = false;
774 if(!_minimized)
775 {
776 int distanceChange = 0;
777
778 // Update distance
779 if(!_ufoBreakingOff)
780 {
781 if (_currentDist < _targetDist && !_ufo->isCrashed() && !_craft->isDestroyed())
782 {
783 distanceChange = 4;
784 if (_currentDist + distanceChange >_targetDist)
785 {
786 distanceChange = _targetDist - _currentDist;
787 }
788 }
789 else if (_currentDist > _targetDist && !_ufo->isCrashed() && !_craft->isDestroyed())
790 {
791 distanceChange = -2;
792 }
793
794 // don't let the interceptor mystically push or pull its fired projectiles
795 for(std::vector<CraftWeaponProjectile*>::iterator it = _projectiles.begin(); it != _projectiles.end(); ++it)
796 {
797 if ((*it)->getGlobalType() != CWPGT_BEAM && (*it)->getDirection() == D_UP) (*it)->setPosition((*it)->getPosition() + distanceChange);
798 }
799 }
800 else
801 {
802 distanceChange = 4;
803
804 // UFOs can try to outrun our missiles, don't adjust projectile positions here
805 // If UFOs ever fire anything but beams, those positions need to be adjust here though.
806 }
807
808 _currentDist += distanceChange;
809
810 std::wostringstream ss;
811 ss << _currentDist;
812 _txtDistance->setText(ss.str());
813
814 // Move projectiles and check for hits.
815 for(std::vector<CraftWeaponProjectile*>::iterator it = _projectiles.begin(); it != _projectiles.end(); ++it)
816 {
817 CraftWeaponProjectile *p = (*it);
818 p->move();
819 // Projectiles fired by interceptor.
820 if(p->getDirection() == D_UP)
821 {
822 // Projectile reached the UFO - determine if it's been hit.
823 if(((p->getPosition() >= _currentDist) || (p->getGlobalType() == CWPGT_BEAM && p->toBeRemoved())) && !_ufo->isCrashed())
824 {
825 // UFO hit.
826 if (RNG::percent(p->getAccuracy()))
827 {
828 // Formula delivered by Volutar
829 int damage = RNG::generate(p->getDamage() / 2, p->getDamage());
830 _ufo->setDamage(_ufo->getDamage() + damage);
831 if(_ufo->isCrashed())
832 {
833 _ufo->setShotDownByCraftId(_craft->getId());
834 _ufoBreakingOff = false;
835 _ufo->setSpeed(0);
836 }
837 if (_ufo->getHitFrame() == 0)
838 {
839 _animatingHit = true;
840 _ufo->setHitFrame(3);
841 }
842
843 setStatus("STR_UFO_HIT");
844 _game->getResourcePack()->getSound("GEO.CAT", 12)->play(); //12
845 p->remove();
846 }
847 // Missed.
848 else
849 {
850 if(p->getGlobalType() == CWPGT_BEAM)
851 {
852 p->remove();
853 }
854 else
855 {
856 p->setMissed(true);
857 }
858 }
859 }
860 // Check if projectile passed it's maximum range.
861 if(p->getGlobalType() == CWPGT_MISSILE)
862 {
863 if (p->getPosition() / 8 >= p->getRange())
864 {
865 p->remove();
866 }
867 else if (!_ufo->isCrashed())
868 {
869 projectileInFlight = true;
870 }
871 }
872 }
873 // Projectiles fired by UFO.
874 else if(p->getDirection() == D_DOWN)
875 {
876 if(p->getGlobalType() == CWPGT_MISSILE || (p->getGlobalType() == CWPGT_BEAM && p->toBeRemoved()))
877 {
878 if(RNG::percent(p->getAccuracy()))
879 {
880 // Formula delivered by Volutar
881 int damage = RNG::generate(0, _ufo->getRules()->getWeaponPower());
882 if (damage)
883 {
884 _craft->setDamage(_craft->getDamage() + damage);
885 drawCraftDamage();
886 setStatus("STR_INTERCEPTOR_DAMAGED");
887 _game->getResourcePack()->getSound("GEO.CAT", 10)->play(); //10
888 if (_mode == _btnCautious && _craft->getDamagePercentage() >= 50)
889 {
890 _targetDist = STANDOFF_DIST;
891 }
892 }
893 }
894 p->remove();
895 }
896 }
897 }
898
899 // Remove projectiles that hit or missed their target.
900 for(std::vector<CraftWeaponProjectile*>::iterator it = _projectiles.begin(); it != _projectiles.end();)
901 {
902 if((*it)->toBeRemoved() == true || ((*it)->getMissed() == true && (*it)->getPosition() <= 0))
903 {
904 delete *it;
905 it = _projectiles.erase(it);
906 }
907 else
908 {
909 ++it;
910 }
911 }
912
913 // Handle weapons and craft distance.
914 for (int i = 0; i < _craft->getRules()->getWeapons(); ++i)
915 {
916 CraftWeapon *w = _craft->getWeapons()->at(i);
917 if (w == 0)
918 {
919 continue;
920 }
921 Timer *wTimer = 0;
922 if (i == 0)
923 {
924 wTimer = _w1Timer;
925 }
926 else
927 {
928 wTimer = _w2Timer;
929 }
930
931 // Handle weapon firing
932 if (!wTimer->isRunning() && _currentDist <= w->getRules()->getRange() * 8 && w->getAmmo() > 0 && _mode != _btnStandoff
933 && _mode != _btnDisengage && !_ufo->isCrashed() && !_craft->isDestroyed())
934 {
935 wTimer->start();
936 if (i == 0)
937 {
938 fireWeapon1();
939 }
940 else
941 {
942 fireWeapon2();
943 }
944 }
945 else if (wTimer->isRunning() && (_currentDist > w->getRules()->getRange() * 8 || (w->getAmmo() == 0 && !projectileInFlight) || _mode == _btnStandoff
946 || _mode == _btnDisengage || _ufo->isCrashed() || _craft->isDestroyed()))
947 {
948 wTimer->stop();
949 // Handle craft distance according to option set by user and available ammo.
950 if (w->getAmmo() == 0 && !_craft->isDestroyed())
951 {
952 if (_mode == _btnCautious)
953 {
954 minimumDistance();
955 }
956 else if (_mode == _btnStandard)
957 {
958 maximumDistance();
959 }
960 }
961 }
962 }
963
964 // Handle UFO firing.
965 if(!_ufoWtimer->isRunning() && _currentDist <= _ufo->getRules()->getWeaponRange() * 8 && !_ufo->isCrashed() && !_craft->isDestroyed())
966 {
967 if (_ufo->getShootingAt() == 0)
968 {
969 _ufo->setShootingAt(_interceptionNumber);
970 _ufoWtimer->start();
971 ufoFireWeapon();
972 }
973 }
974 else if(_ufoWtimer->isRunning() && (_currentDist > _ufo->getRules()->getWeaponRange() * 8 || _ufo->isCrashed() || _craft->isDestroyed()))
975 {
976 _ufo->setShootingAt(0);
977 _ufoWtimer->stop();
978 }
979 }
980
981 // Check when battle is over.
982 if (_end == true && (((_currentDist > 640 || _minimized) && (_mode == _btnDisengage || _ufoBreakingOff == true)) || (_timeout == 0 && (_ufo->isCrashed() || _craft->isDestroyed()))))
983 {
984 if (_ufoBreakingOff)
985 {
986 _ufo->move();
987 _craft->setDestination(_ufo);
988 }
989 if (!_destroyCraft && (_destroyUfo || _mode == _btnDisengage))
990 {
991 _craft->returnToBase();
992 }
993 endDogfight();
994 }
995
996 // End dogfight if craft is destroyed.
997 if(!_end && _craft->isDestroyed())
998 {
999 setStatus("STR_INTERCEPTOR_DESTROYED");
1000 _timeout += 30;
1001 _game->getResourcePack()->getSound("GEO.CAT", 13)->play();
1002 finalRun = true;
1003 _destroyCraft = true;
1004 _ufo->setShootingAt(0);
1005 _ufoWtimer->stop();
1006 _w1Timer->stop();
1007 _w2Timer->stop();
1008 }
1009
1010 // End dogfight if UFO is crashed or destroyed.
1011 if (!_end && _ufo->isCrashed())
1012 {
1013 AlienMission *mission = _ufo->getMission();
1014 mission->ufoShotDown(*_ufo, *_game, *_globe);
1015 // Check for retaliation trigger.
1016 if (!RNG::percent(4 * (24 - (int)(_game->getSavedGame()->getDifficulty()))))
1017 {
1018 // Spawn retaliation mission.
1019 std::string targetRegion;
1020 if (RNG::percent(50 - 6 * (int)(_game->getSavedGame()->getDifficulty())))
1021 {
1022 // Attack on UFO's mission region
1023 targetRegion = _ufo->getMission()->getRegion();
1024 }
1025 else
1026 {
1027 // Try to find and attack the originating base.
1028 targetRegion = _game->getSavedGame()->locateRegion(*_craft->getBase())->getRules()->getType();
1029 // TODO: If the base is removed, the mission is canceled.
1030 }
1031 // Difference from original: No retaliation until final UFO lands (Original: Is spawned).
1032 if (!_game->getSavedGame()->getAlienMission(targetRegion, "STR_ALIEN_RETALIATION"))
1033 {
1034 const RuleAlienMission &rule = *_game->getRuleset()->getAlienMission("STR_ALIEN_RETALIATION");
1035 AlienMission *mission = new AlienMission(rule);
1036 mission->setId(_game->getSavedGame()->getId("ALIEN_MISSIONS"));
1037 mission->setRegion(targetRegion, *_game->getRuleset());
1038 mission->setRace(_ufo->getAlienRace());
1039 mission->start();
1040 _game->getSavedGame()->getAlienMissions().push_back(mission);
1041 }
1042 }
1043 _ufoEscapeTimer->stop();
1044 if (_ufo->isDestroyed())
1045 {
1046 if(_ufo->getShotDownByCraftId() == _craft->getId())
1047 {
1048 for(std::vector<Country*>::iterator country = _game->getSavedGame()->getCountries()->begin(); country != _game->getSavedGame()->getCountries()->end(); ++country)
1049 {
1050 if((*country)->getRules()->insideCountry(_ufo->getLongitude(), _ufo->getLatitude()))
1051 {
1052 (*country)->addActivityXcom(_ufo->getRules()->getScore()*2);
1053 break;
1054 }
1055 }
1056 for(std::vector<Region*>::iterator region = _game->getSavedGame()->getRegions()->begin(); region != _game->getSavedGame()->getRegions()->end(); ++region)
1057 {
1058 if((*region)->getRules()->insideRegion(_ufo->getLongitude(), _ufo->getLatitude()))
1059 {
1060 (*region)->addActivityXcom(_ufo->getRules()->getScore()*2);
1061 break;
1062 }
1063 }
1064 setStatus("STR_UFO_DESTROYED");
1065 _game->getResourcePack()->getSound("GEO.CAT", 10)->play(); //11
1066 }
1067 _destroyUfo = true;
1068 }
1069 else
1070 {
1071 if(_ufo->getShotDownByCraftId() == _craft->getId())
1072 {
1073 setStatus("STR_UFO_CRASH_LANDS");
1074 _game->getResourcePack()->getSound("GEO.CAT", 10)->play(); //10
1075 for(std::vector<Country*>::iterator country = _game->getSavedGame()->getCountries()->begin(); country != _game->getSavedGame()->getCountries()->end(); ++country)
1076 {
1077 if((*country)->getRules()->insideCountry(_ufo->getLongitude(), _ufo->getLatitude()))
1078 {
1079 (*country)->addActivityXcom(_ufo->getRules()->getScore());
1080 break;
1081 }
1082 }
1083 for(std::vector<Region*>::iterator region = _game->getSavedGame()->getRegions()->begin(); region != _game->getSavedGame()->getRegions()->end(); ++region)
1084 {
1085 if((*region)->getRules()->insideRegion(_ufo->getLongitude(), _ufo->getLatitude()))
1086 {
1087 (*region)->addActivityXcom(_ufo->getRules()->getScore());
1088 break;
1089 }
1090 }
1091 }
1092 if (!_globe->insideLand(_ufo->getLongitude(), _ufo->getLatitude()))
1093 {
1094 _ufo->setStatus(Ufo::DESTROYED);
1095 _destroyUfo = true;
1096 }
1097 else
1098 {
1099 _ufo->setSecondsRemaining(RNG::generate(24, 96)*3600);
1100 _ufo->setAltitude("STR_GROUND");
1101 if (_ufo->getCrashId() == 0)
1102 {
1103 _ufo->setCrashId(_game->getSavedGame()->getId("STR_CRASH_SITE"));
1104 }
1105 }
1106 }
1107 _timeout += 30;
1108 if(_ufo->getShotDownByCraftId() != _craft->getId())
1109 {
1110 _timeout += 50;
1111 _ufo->setHitFrame(3);
1112 }
1113 finalRun = true;
1114 }
1115
1116 if (!_end && _ufo->getStatus() == Ufo::LANDED)
1117 {
1118 _timeout += 30;
1119 finalRun = true;
1120 _ufo->setShootingAt(0);
1121 _ufoWtimer->stop();
1122 _w1Timer->stop();
1123 _w2Timer->stop();
1124 }
1125
1126 if (!projectileInFlight && finalRun)
1127 {
1128 _end = true;
1129 }
1130 }
1131
1132 /**
1133 * Fires a shot from the first weapon
1134 * equipped on the craft.
1135 */
fireWeapon1()1136 void DogfightState::fireWeapon1()
1137 {
1138 if(_weapon1Enabled)
1139 {
1140 CraftWeapon *w1 = _craft->getWeapons()->at(0);
1141 if (w1->setAmmo(w1->getAmmo() - 1))
1142 {
1143
1144 std::wostringstream ss;
1145 ss << w1->getAmmo();
1146 _txtAmmo1->setText(ss.str());
1147
1148 CraftWeaponProjectile *p = w1->fire();
1149 p->setDirection(D_UP);
1150 p->setHorizontalPosition(HP_LEFT);
1151 _projectiles.push_back(p);
1152
1153 _game->getResourcePack()->getSound("GEO.CAT", w1->getRules()->getSound())->play();
1154 }
1155 }
1156 }
1157
1158 /**
1159 * Fires a shot from the second weapon
1160 * equipped on the craft.
1161 */
fireWeapon2()1162 void DogfightState::fireWeapon2()
1163 {
1164 if(_weapon2Enabled)
1165 {
1166 CraftWeapon *w2 = _craft->getWeapons()->at(1);
1167 if (w2->setAmmo(w2->getAmmo() - 1))
1168 {
1169
1170 std::wostringstream ss;
1171 ss << w2->getAmmo();
1172 _txtAmmo2->setText(ss.str());
1173
1174 CraftWeaponProjectile *p = w2->fire();
1175 p->setDirection(D_UP);
1176 p->setHorizontalPosition(HP_RIGHT);
1177 _projectiles.push_back(p);
1178
1179 _game->getResourcePack()->getSound("GEO.CAT", w2->getRules()->getSound())->play();
1180 }
1181 }
1182 }
1183
1184 /**
1185 * Each time a UFO will try to fire it's cannons
1186 * a calculation is made. There's only 10% chance
1187 * that it will actually fire.
1188 */
ufoFireWeapon()1189 void DogfightState::ufoFireWeapon()
1190 {
1191 _ufoFireInterval = (_ufo->getRules()->getWeaponReload() - (int)(_game->getSavedGame()->getDifficulty()));
1192 _ufoFireInterval = (RNG::generate(0, _ufoFireInterval) + _ufoFireInterval) * _timeScale;
1193 _ufoWtimer->setInterval(_ufoFireInterval);
1194
1195 setStatus("STR_UFO_RETURN_FIRE");
1196 CraftWeaponProjectile *p = new CraftWeaponProjectile();
1197 p->setType(CWPT_PLASMA_BEAM);
1198 p->setAccuracy(60);
1199 p->setDamage(_ufo->getRules()->getWeaponPower());
1200 p->setDirection(D_DOWN);
1201 p->setHorizontalPosition(HP_CENTER);
1202 p->setPosition(_currentDist - (_ufo->getRules()->getRadius() / 2));
1203 _projectiles.push_back(p);
1204 _game->getResourcePack()->getSound("GEO.CAT", 8)->play();
1205 }
1206
1207 /**
1208 * Sets the craft to the minimum distance
1209 * required to fire a weapon.
1210 */
minimumDistance()1211 void DogfightState::minimumDistance()
1212 {
1213 int max = 0;
1214 for (std::vector<CraftWeapon*>::iterator i = _craft->getWeapons()->begin(); i < _craft->getWeapons()->end(); ++i)
1215 {
1216 if (*i == 0)
1217 continue;
1218 if ((*i)->getRules()->getRange() > max && (*i)->getAmmo() > 0)
1219 {
1220 max = (*i)->getRules()->getRange();
1221 }
1222 }
1223 if (max == 0)
1224 {
1225 _targetDist = STANDOFF_DIST;
1226 }
1227 else
1228 {
1229 _targetDist = max * 8;
1230 }
1231 }
1232
1233 /**
1234 * Sets the craft to the maximum distance
1235 * required to fire a weapon.
1236 */
maximumDistance()1237 void DogfightState::maximumDistance()
1238 {
1239 int min = 1000;
1240 for (std::vector<CraftWeapon*>::iterator i = _craft->getWeapons()->begin(); i < _craft->getWeapons()->end(); ++i)
1241 {
1242 if (*i == 0)
1243 continue;
1244 if ((*i)->getRules()->getRange() < min && (*i)->getAmmo() > 0)
1245 {
1246 min = (*i)->getRules()->getRange();
1247 }
1248 }
1249 if (min == 1000)
1250 {
1251 _targetDist = STANDOFF_DIST;
1252 }
1253 else
1254 {
1255 _targetDist = min * 8;
1256 }
1257 }
1258
1259 /**
1260 * Updates the status text and restarts
1261 * the text timeout counter.
1262 * @param status New status text.
1263 */
setStatus(const std::string & status)1264 void DogfightState::setStatus(const std::string &status)
1265 {
1266 _txtStatus->setText(tr(status));
1267 _timeout = 50;
1268 }
1269
1270 /**
1271 * Minimizes the dogfight window.
1272 * @param action Pointer to an action.
1273 */
btnMinimizeClick(Action *)1274 void DogfightState::btnMinimizeClick(Action *)
1275 {
1276 if (!_ufo->isCrashed() && !_craft->isDestroyed() && !_ufoBreakingOff)
1277 {
1278 if (_currentDist >= STANDOFF_DIST)
1279 {
1280 setMinimized(true);
1281 _window->setVisible(false);
1282 _preview->setVisible(false);
1283 _btnStandoff->setVisible(false);
1284 _btnCautious->setVisible(false);
1285 _btnStandard->setVisible(false);
1286 _btnAggressive->setVisible(false);
1287 _btnDisengage->setVisible(false);
1288 _btnUfo->setVisible(false);
1289 _btnMinimize->setVisible(false);
1290 _battle->setVisible(false);
1291 _weapon1->setVisible(false);
1292 _range1->setVisible(false);
1293 _weapon2->setVisible(false);
1294 _range2->setVisible(false);
1295 _damage->setVisible(false);
1296 _txtAmmo1->setVisible(false);
1297 _txtAmmo2->setVisible(false);
1298 _txtDistance->setVisible(false);
1299 _preview->setVisible(false);
1300 _txtStatus->setVisible(false);
1301 _btnMinimizedIcon->setVisible(true);
1302 _txtInterceptionNumber->setVisible(true);
1303 _ufoEscapeTimer->stop();
1304 }
1305 else
1306 {
1307 setStatus("STR_MINIMISE_AT_STANDOFF_RANGE_ONLY");
1308 }
1309 }
1310 }
1311
1312 /**
1313 * Switches to Standoff mode (maximum range).
1314 * @param action Pointer to an action.
1315 */
btnStandoffClick(Action *)1316 void DogfightState::btnStandoffClick(Action *)
1317 {
1318 if (!_ufo->isCrashed() && !_craft->isDestroyed() && !_ufoBreakingOff)
1319 {
1320 _end = false;
1321 setStatus("STR_STANDOFF");
1322 _targetDist = STANDOFF_DIST;
1323 }
1324 }
1325
1326 /**
1327 * Switches to Cautious mode (maximum weapon range).
1328 * @param action Pointer to an action.
1329 */
btnCautiousClick(Action *)1330 void DogfightState::btnCautiousClick(Action *)
1331 {
1332 if (!_ufo->isCrashed() && !_craft->isDestroyed() && !_ufoBreakingOff)
1333 {
1334 _end = false;
1335 setStatus("STR_CAUTIOUS_ATTACK");
1336 if (_craft->getRules()->getWeapons() > 0 && _craft->getWeapons()->at(0) != 0)
1337 {
1338 _w1Timer->setInterval(_craft->getWeapons()->at(0)->getRules()->getCautiousReload() * _timeScale);
1339 }
1340 if (_craft->getRules()->getWeapons() > 1 && _craft->getWeapons()->at(1) != 0)
1341 {
1342 _w2Timer->setInterval(_craft->getWeapons()->at(1)->getRules()->getCautiousReload() * _timeScale);
1343 }
1344 minimumDistance();
1345 _ufoEscapeTimer->start();
1346 }
1347 }
1348
1349 /**
1350 * Switches to Standard mode (minimum weapon range).
1351 * @param action Pointer to an action.
1352 */
btnStandardClick(Action *)1353 void DogfightState::btnStandardClick(Action *)
1354 {
1355 if (!_ufo->isCrashed() && !_craft->isDestroyed() && !_ufoBreakingOff)
1356 {
1357 _end = false;
1358 setStatus("STR_STANDARD_ATTACK");
1359 if (_craft->getRules()->getWeapons() > 0 && _craft->getWeapons()->at(0) != 0)
1360 {
1361 _w1Timer->setInterval(_craft->getWeapons()->at(0)->getRules()->getStandardReload() * _timeScale);
1362 }
1363 if (_craft->getRules()->getWeapons() > 1 && _craft->getWeapons()->at(1) != 0)
1364 {
1365 _w2Timer->setInterval(_craft->getWeapons()->at(1)->getRules()->getStandardReload() * _timeScale);
1366 }
1367 maximumDistance();
1368 _ufoEscapeTimer->start();
1369 }
1370 }
1371
1372 /**
1373 * Switches to Aggressive mode (minimum range).
1374 * @param action Pointer to an action.
1375 */
btnAggressiveClick(Action *)1376 void DogfightState::btnAggressiveClick(Action *)
1377 {
1378 if (!_ufo->isCrashed() && !_craft->isDestroyed() && !_ufoBreakingOff)
1379 {
1380 _end = false;
1381 setStatus("STR_AGGRESSIVE_ATTACK");
1382 if (_craft->getRules()->getWeapons() > 0 && _craft->getWeapons()->at(0) != 0)
1383 {
1384 _w1Timer->setInterval(_craft->getWeapons()->at(0)->getRules()->getAggressiveReload() * _timeScale);
1385 }
1386 if (_craft->getRules()->getWeapons() > 1 && _craft->getWeapons()->at(1) != 0)
1387 {
1388 _w2Timer->setInterval(_craft->getWeapons()->at(1)->getRules()->getAggressiveReload() * _timeScale);
1389 }
1390 _targetDist = 64;
1391 _ufoEscapeTimer->start();
1392 }
1393 }
1394
1395 /**
1396 * Disengages from the UFO.
1397 * @param action Pointer to an action.
1398 */
btnDisengageClick(Action *)1399 void DogfightState::btnDisengageClick(Action *)
1400 {
1401 if (!_ufo->isCrashed() && !_craft->isDestroyed() && !_ufoBreakingOff)
1402 {
1403 _end = true;
1404 setStatus("STR_DISENGAGING");
1405 _targetDist = 800;
1406 _ufoEscapeTimer->stop();
1407 }
1408 }
1409
1410 /**
1411 * Shows a front view of the UFO.
1412 * @param action Pointer to an action.
1413 */
btnUfoClick(Action *)1414 void DogfightState::btnUfoClick(Action *)
1415 {
1416 _preview->setVisible(true);
1417 // Disable all other buttons to prevent misclicks
1418 _btnStandoff->setVisible(false);
1419 _btnCautious->setVisible(false);
1420 _btnStandard->setVisible(false);
1421 _btnAggressive->setVisible(false);
1422 _btnDisengage->setVisible(false);
1423 _btnUfo->setVisible(false);
1424 _btnMinimize->setVisible(false);
1425 _weapon1->setVisible(false);
1426 _weapon2->setVisible(false);
1427 }
1428
1429 /**
1430 * Hides the front view of the UFO.
1431 * @param action Pointer to an action.
1432 */
previewClick(Action *)1433 void DogfightState::previewClick(Action *)
1434 {
1435 _preview->setVisible(false);
1436 // Reenable all other buttons to prevent misclicks
1437 _btnStandoff->setVisible(true);
1438 _btnCautious->setVisible(true);
1439 _btnStandard->setVisible(true);
1440 _btnAggressive->setVisible(true);
1441 _btnDisengage->setVisible(true);
1442 _btnUfo->setVisible(true);
1443 _btnMinimize->setVisible(true);
1444 _weapon1->setVisible(true);
1445 _weapon2->setVisible(true);
1446 }
1447
1448 /*
1449 * Sets UFO to break off mode. Started via timer.
1450 */
ufoBreakOff()1451 void DogfightState::ufoBreakOff()
1452 {
1453 if(!_ufo->isCrashed() && !_ufo->isDestroyed() && !_craft->isDestroyed())
1454 {
1455 _ufo->setSpeed(_ufo->getRules()->getMaxSpeed());
1456 _ufoBreakingOff = true;
1457 }
1458 }
1459
1460 /*
1461 * Draws the UFO blob on the radar screen.
1462 * Currently works only for original sized blobs
1463 * 13 x 13 pixels.
1464 */
drawUfo()1465 void DogfightState::drawUfo()
1466 {
1467 if(_ufoSize < 0 || _ufo->isDestroyed() || _minimized)
1468 {
1469 return;
1470 }
1471 int currentUfoXposition = _battle->getWidth() / 2 - 6;
1472 int currentUfoYposition = _battle->getHeight() - (_currentDist / 8) - 6;
1473 for(int y = 0; y < 13; ++y)
1474 {
1475 for(int x = 0; x < 13; ++x)
1476 {
1477 Uint8 pixelOffset = _ufoBlobs[_ufoSize + _ufo->getHitFrame()][y][x];
1478 if(pixelOffset == 0)
1479 {
1480 continue;
1481 }
1482 else
1483 {
1484 if(_ufo->isCrashed() || _ufo->getHitFrame() > 0)
1485 {
1486 pixelOffset *= 2;
1487 }
1488 Uint8 radarPixelColor = _window->getPixel(currentUfoXposition + x + 3, currentUfoYposition + y + 3); // + 3 cause of the window frame
1489 Uint8 color = radarPixelColor - pixelOffset;
1490 if(color < 108)
1491 {
1492 color = 108;
1493 }
1494 _battle->setPixel(currentUfoXposition + x, currentUfoYposition + y, color);
1495 }
1496 }
1497 }
1498 }
1499
1500 /*
1501 * Draws projectiles on the radar screen.
1502 * Depending on what type of projectile it is, it's
1503 * shape will be different. Currently works for
1504 * original sized blobs 3 x 6 pixels.
1505 */
drawProjectile(const CraftWeaponProjectile * p)1506 void DogfightState::drawProjectile(const CraftWeaponProjectile* p) {
1507 if(_minimized)
1508 {
1509 return;
1510 }
1511 int xPos = _battle->getWidth() / 2 + p->getHorizontalPosition();
1512 // Draw missiles.
1513 if(p->getGlobalType() == CWPGT_MISSILE)
1514 {
1515 xPos -= 1;
1516 int yPos = _battle->getHeight() - p->getPosition() / 8;
1517 for(int x = 0; x < 3; ++x)
1518 {
1519 for(int y = 0; y < 6; ++y)
1520 {
1521 int pixelOffset = _projectileBlobs[p->getType()][y][x];
1522 if(pixelOffset == 0)
1523 {
1524 continue;
1525 }
1526 else
1527 {
1528 Uint8 radarPixelColor = _window->getPixel(xPos + x + 3, yPos + y + 3); // + 3 cause of the window frame
1529 Uint8 color = radarPixelColor - pixelOffset;
1530 if(color < 108)
1531 {
1532 color = 108;
1533 }
1534 _battle->setPixel(xPos + x, yPos + y, color);
1535 }
1536 }
1537 }
1538 }
1539 // Draw beams.
1540 else if(p->getGlobalType() == CWPGT_BEAM)
1541 {
1542 int yStart = _battle->getHeight() - 2;
1543 int yEnd = _battle->getHeight() - (_currentDist / 8);
1544 Uint8 pixelOffset = p->getState();
1545 for(int y = yStart; y > yEnd; --y)
1546 {
1547 Uint8 radarPixelColor = _window->getPixel(xPos + 3, y + 3);
1548 Uint8 color = radarPixelColor - pixelOffset;
1549 if(color < 108)
1550 {
1551 color = 108;
1552 }
1553 _battle->setPixel(xPos, y, color);
1554 }
1555 }
1556 }
1557
1558 /**
1559 * Toggles usage of weapon number 1.
1560 * @param action Pointer to an action.
1561 */
weapon1Click(Action *)1562 void DogfightState::weapon1Click(Action *)
1563 {
1564 _weapon1Enabled = !_weapon1Enabled;
1565 recolor(0, _weapon1Enabled);
1566 }
1567
1568 /**
1569 * Toggles usage of weapon number 2.
1570 * @param action Pointer to an action.
1571 */
weapon2Click(Action *)1572 void DogfightState::weapon2Click(Action *)
1573 {
1574 _weapon2Enabled = !_weapon2Enabled;
1575 recolor(1, _weapon2Enabled);
1576 }
1577
1578 /**
1579 * Changes colors of weapon icons, range indicators and ammo texts base on current weapon state.
1580 * @param weaponNo - number of weapon for which colors must be changed.
1581 * @param currentState - state of weapon (enabled = true, disabled = false).
1582 */
recolor(const int weaponNo,const bool currentState)1583 void DogfightState::recolor(const int weaponNo, const bool currentState)
1584 {
1585 InteractiveSurface *weapon = 0;
1586 Text *ammo = 0;
1587 Surface *range = 0;
1588 int weaponAndAmmoOffset = 24, rangeOffset = 7;
1589 if(weaponNo == 0)
1590 {
1591 weapon = _weapon1;
1592 ammo = _txtAmmo1;
1593 range = _range1;
1594 }
1595 else if(weaponNo == 1)
1596 {
1597 weapon = _weapon2;
1598 ammo = _txtAmmo2;
1599 range = _range2;
1600 }
1601 else
1602 {
1603 return;
1604 }
1605
1606 if(currentState)
1607 {
1608 weapon->offset(-weaponAndAmmoOffset);
1609 ammo->offset(-weaponAndAmmoOffset);
1610 range->offset(-rangeOffset);
1611 }
1612 else
1613 {
1614 weapon->offset(weaponAndAmmoOffset);
1615 ammo->offset(weaponAndAmmoOffset);
1616 range->offset(rangeOffset);
1617 }
1618 }
1619
1620 /**
1621 * Returns true if state is minimized. Otherwise returns false.
1622 * @return Is the dogfight minimized?
1623 */
isMinimized() const1624 bool DogfightState::isMinimized() const
1625 {
1626 return _minimized;
1627 }
1628
1629 /**
1630 * Sets the state to minimized/maximized status.
1631 * @param minimized Is the dogfight minimized?
1632 */
setMinimized(const bool minimized)1633 void DogfightState::setMinimized(const bool minimized)
1634 {
1635 _minimized = minimized;
1636 }
1637
1638 /**
1639 * Maximizes the interception window.
1640 * @param action Pointer to an action.
1641 */
btnMinimizedIconClick(Action *)1642 void DogfightState::btnMinimizedIconClick(Action *)
1643 {
1644 setMinimized(false);
1645 _window->setVisible(true);
1646 _btnStandoff->setVisible(true);
1647 _btnCautious->setVisible(true);
1648 _btnStandard->setVisible(true);
1649 _btnAggressive->setVisible(true);
1650 _btnDisengage->setVisible(true);
1651 _btnUfo->setVisible(true);
1652 _btnMinimize->setVisible(true);
1653 _battle->setVisible(true);
1654 _weapon1->setVisible(true);
1655 _range1->setVisible(true);
1656 _weapon2->setVisible(true);
1657 _range2->setVisible(true);
1658 _damage->setVisible(true);
1659 _txtAmmo1->setVisible(true);
1660 _txtAmmo2->setVisible(true);
1661 _txtDistance->setVisible(true);
1662 _txtStatus->setVisible(true);
1663 _btnMinimizedIcon->setVisible(false);
1664 _txtInterceptionNumber->setVisible(false);
1665 _preview->setVisible(false);
1666 }
1667
1668 /**
1669 * Sets interception number. Used to draw proper number when window minimized.
1670 * @param number ID number.
1671 */
setInterceptionNumber(const int number)1672 void DogfightState::setInterceptionNumber(const int number)
1673 {
1674 _interceptionNumber = number;
1675 }
1676
1677 /**
1678 * Sets interceptions count. Used to properly position the window.
1679 * @param count Amount of interception windows.
1680 */
setInterceptionsCount(const size_t count)1681 void DogfightState::setInterceptionsCount(const size_t count)
1682 {
1683 _interceptionsCount = count;
1684 calculateWindowPosition();
1685 moveWindow();
1686 }
1687
1688 /**
1689 * Calculates dogfight window position according to
1690 * number of active interceptions.
1691 */
calculateWindowPosition()1692 void DogfightState::calculateWindowPosition()
1693 {
1694
1695 _minimizedIconX = 5;
1696 _minimizedIconY = (5 * _interceptionNumber) + (16 * (_interceptionNumber - 1));
1697
1698 if(_interceptionsCount == 1)
1699 {
1700 _x = 80;
1701 _y = 52;
1702 }
1703 else if(_interceptionsCount == 2)
1704 {
1705 if(_interceptionNumber == 1)
1706 {
1707 _x = 80;
1708 _y = 0;
1709 }
1710 else // 2
1711 {
1712 _x = 80;
1713 //_y = (_game->getScreen()->getHeight() / 2) - 96;
1714 _y = 200 - _window->getHeight();//96;
1715 }
1716 }
1717 else if(_interceptionsCount == 3)
1718 {
1719 if(_interceptionNumber == 1)
1720 {
1721 _x = 80;
1722 _y = 0;
1723 }
1724 else if(_interceptionNumber == 2)
1725 {
1726 _x = 0;
1727 //_y = (_game->getScreen()->getHeight() / 2) - 96;
1728 _y = 200 - _window->getHeight();//96;
1729 }
1730 else // 3
1731 {
1732 //_x = (_game->getScreen()->getWidth() / 2) - 160;
1733 //_y = (_game->getScreen()->getHeight() / 2) - 96;
1734 _x = 320 - _window->getWidth();//160;
1735 _y = 200 - _window->getHeight();//96;
1736 }
1737 }
1738 else
1739 {
1740 if(_interceptionNumber == 1)
1741 {
1742 _x = 0;
1743 _y = 0;
1744 }
1745 else if(_interceptionNumber == 2)
1746 {
1747 //_x = (_game->getScreen()->getWidth() / 2) - 160;
1748 _x = 320 - _window->getWidth();//160;
1749 _y = 0;
1750 }
1751 else if(_interceptionNumber == 3)
1752 {
1753 _x = 0;
1754 //_y = (_game->getScreen()->getHeight() / 2) - 96;
1755 _y = 200 - _window->getHeight();//96;
1756 }
1757 else // 4
1758 {
1759 //_x = (_game->getScreen()->getWidth() / 2) - 160;
1760 //_y = (_game->getScreen()->getHeight() / 2) - 96;
1761 _x = 320 - _window->getWidth();//160;
1762 _y = 200 - _window->getHeight();//96;
1763 }
1764 }
1765 _x += _game->getScreen()->getDX();
1766 _y += _game->getScreen()->getDY();
1767 }
1768
1769 /**
1770 * Relocates all dogfight window elements to
1771 * calculated position. This is used when multiple
1772 * interceptions are running.
1773 */
moveWindow()1774 void DogfightState::moveWindow()
1775 {
1776 _window->setX(_x); _window->setY(_y);
1777 _battle->setX(_x + 3); _battle->setY(_y + 3);
1778 _weapon1->setX(_x + 4); _weapon1->setY(_y + 52);
1779 _range1->setX(_x + 19); _range1->setY(_y + 3);
1780 _weapon2->setX(_x + 64); _weapon2->setY(_y + 52);
1781 _range2->setX(_x + 43); _range2->setY(_y + 3);
1782 _damage->setX(_x + 93); _damage->setY(_y + 40);
1783 _btnMinimize->setX(_x); _btnMinimize->setY(_y);
1784 _preview->setX(_x); _preview->setY(_y);
1785 _btnStandoff->setX(_x + 83); _btnStandoff->setY(_y + 4);
1786 _btnCautious->setX(_x + 120); _btnCautious->setY(_y + 4);
1787 _btnStandard->setX(_x + 83); _btnStandard->setY(_y + 20);
1788 _btnAggressive->setX(_x + 120); _btnAggressive->setY(_y + 20);
1789 _btnDisengage->setX(_x + 120); _btnDisengage->setY(_y + 36);
1790 _btnUfo->setX(_x + 120); _btnUfo->setY(_y + 52);
1791 _txtAmmo1->setX(_x + 4); _txtAmmo1->setY(_y + 70);
1792 _txtAmmo2->setX(_x + 64); _txtAmmo2->setY(_y + 70);
1793 _txtDistance->setX(_x + 116); _txtDistance->setY(_y + 72);
1794 _txtStatus->setX(_x + 4); _txtStatus->setY(_y + 85);
1795 _btnMinimizedIcon->setX(_minimizedIconX); _btnMinimizedIcon->setY(_minimizedIconY);
1796 _txtInterceptionNumber->setX(_minimizedIconX + 18); _txtInterceptionNumber->setY(_minimizedIconY + 6);
1797 }
1798
1799 /**
1800 * Checks whether the dogfight should end.
1801 * @return Returns true if the dogfight should end, otherwise returns false.
1802 */
dogfightEnded() const1803 bool DogfightState::dogfightEnded() const
1804 {
1805 return _endDogfight;
1806 }
1807
1808 /**
1809 * Returns the UFO associated to this dogfight.
1810 * @return Returns pointer to UFO object associated to this dogfight.
1811 */
getUfo() const1812 Ufo* DogfightState::getUfo() const
1813 {
1814 return _ufo;
1815 }
1816
1817 /**
1818 * Ends the dogfight.
1819 */
endDogfight()1820 void DogfightState::endDogfight()
1821 {
1822 if (_craft)
1823 _craft->setInDogfight(false);
1824 _endDogfight = true;
1825 }
1826
1827 /**
1828 * Returns interception number.
1829 * @return interception number
1830 */
getInterceptionNumber() const1831 int DogfightState::getInterceptionNumber() const
1832 {
1833 return _interceptionNumber;
1834 }
1835
1836 }
1837