1 /*
2 Copyright (C) 2004 by James Gregory
3 Part of the GalaxyHack project
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY.
9
10 See the COPYING file for more details.
11 */
12
13 #include "RTSUnit_Base.h"
14 #include "Globals.h"
15 #include "Group.h"
16 #include "Inlines.h"
17 #include "Projectile.h"
18 #include "Random.h"
19
20 using std::find;
21
AutoFireUnit(const string & iName,int iMySide,int iMyGroup)22 AutoFireUnit::AutoFireUnit(const string& iName, int iMySide, int iMyGroup):
23 RTSUnit_Base(iName, iMySide, iMyGroup) {}
24
LoadWeapCoords(istream_iterator<char> & iter,istream_iterator<char> & fileEnd)25 void AutoFireUnit::LoadWeapCoords(istream_iterator<char>& iter, istream_iterator<char>& fileEnd) {
26 smallPositions.clear();
27
28 iter = find(iter, fileEnd, ':');
29 ++iter;
30 ++iter;
31
32 CoordsInt tempCoords;
33
34 for (int i = 0; i != smallNumber; ++i) {
35 tempCoords.x = IterToInt(iter, fileEnd);
36 ++iter;
37 tempCoords.y = IterToInt(iter, fileEnd);
38 ++iter;
39 smallPositions.push_back(tempCoords);
40 }
41
42 for (int i = 0; i != smallNumber; ++i) {
43 if (smallPositions[i].x < 0 || smallPositions[i].x >= width || smallPositions[i].y < 0 || smallPositions[i].y >= height) {
44 string error = "Position for a small weapon is outside of the unit for " + name;
45 throw runtime_error(error.c_str());
46 }
47
48 if (myType == UT_CaShUnit) {
49 for (int j = 0; j != smallNumber; ++j) {
50 if (i == j)
51 continue;
52 if (smallPositions[i] == smallPositions[j]) {
53 string error = "Capital ship " + name + " has more than one small weapon in the same position";
54 throw runtime_error(error.c_str());
55 }
56 }
57 }
58 }
59
60 iter = find(iter, fileEnd, ':');
61 ++iter;
62
63 bigPosition.x = IterToInt(iter, fileEnd);
64 ++iter;
65 bigPosition.y = IterToInt(iter, fileEnd);
66
67 if (bigPosition.x < 0 || bigPosition.x >= width || bigPosition.y < 0 || bigPosition.y >= height) {
68 string error = "Position for big weapon is outside of the unit for " + name;
69 throw runtime_error(error.c_str());
70 }
71 }
72
InitSmall()73 void AutoFireUnit::InitSmall() {
74 smallTargets.resize(smallNumber);
75 smallTimer.resize(smallNumber);
76 smallAiming.resize(smallNumber);
77
78 //smallStage must be initialized with ready
79 smallStage.resize(smallNumber, w_Ready);
80 }
81
SetupSmallForFiring(int nSmall,vector<CoordsInt> & inRange)82 void AutoFireUnit::SetupSmallForFiring(int nSmall, vector<CoordsInt>& inRange) {
83 smallStage[nSmall] = w_Aiming;
84
85 int ran = Random() % inRange.size();
86
87 //x is side, y is group
88 smallTargets[nSmall].x = inRange[ran].x;
89 smallTargets[nSmall].y = inRange[ran].y;
90
91 smallTimer[nSmall] = frameCounter;
92 smallAiming[nSmall] = Random() % maxAiming;
93 }
94
95 //we ignore theCommands
FireSmall(AICommands & theCommands)96 void AutoFireUnit::FireSmall(AICommands& theCommands) {
97 //loop through this unit's weapons
98 for (int i = 0; i != smallNumber; ++i) {
99 //if aiming time is up...
100 if (smallStage[i] == w_Firing) {
101 smallStage[i] = w_Reloading;
102 smallTimer[i] = frameCounter;
103
104 float startx;
105 if (bFlip)
106 startx = myx + width - smallPositions[i].x;
107 else
108 startx = myx + smallPositions[i].x;
109
110 projectiles.push_back(Projectile(startx, myy + smallPositions[i].y, smallTargets[i], smallType, sides[mySide].laserColor));
111 }
112 }
113 }
114
Upkeep()115 void AutoFireUnit::Upkeep() {
116 RTSUnit_Base::Upkeep();
117
118 //check for small reloading
119 for (int i = 0; i != smallNumber; ++i) {
120 if (smallStage[i] == w_Reloading && frameCounter - smallTimer[i] > weaponLookup[smallType].reload)
121 smallStage[i] = w_Ready;
122 }
123
124 //check for small aiming (time decided when we choose
125 //a target)
126 for (int i = 0; i != smallNumber; ++i) {
127 if (smallStage[i] == w_Aiming && frameCounter - smallTimer[i] > smallAiming[i])
128 smallStage[i] = w_Firing;
129 }
130 }
131
SelectSmallTargets()132 void AutoFireUnit::SelectSmallTargets() {
133 vector<CoordsInt> inRange;
134 FindInRange(inRange, weaponLookup[smallType].range);
135
136 if (inRange.size() > 0) {
137 //loop through this unit's weapons
138 for (int i = 0; i != smallNumber; ++i) {
139 //if the weapon is ready for new orders...
140 if (smallStage[i] == w_Ready)
141 SetupSmallForFiring(i, inRange);
142 }
143 }
144 }
145
FindInRange(vector<CoordsInt> & inRange,int range)146 void AutoFireUnit::FindInRange(vector<CoordsInt>& inRange, int range) {
147 for (int i = 0; i != sides.size(); ++i) {
148 //only enemy sides
149 if (sides[i].myFlag == sides[mySide].myFlag)
150 continue;
151
152 for (int j = 0; j != sides[i].groups.size(); ++j) {
153 if (!sides[i].groups[j].GetAlive())
154 continue;
155
156 CoordsInt d = sides[mySide].groups[myGroup].GetDxDyClose(i, j);
157
158 //comparison done on squared distances so
159 //we don't have to do square roots
160 //because they get squared it doesn't matter if these
161 //answers are negative or positive
162 if (range * range >= d.x*d.x + d.y*d.y) {
163 CoordsInt tempCoords = {i, j};
164 inRange.push_back(tempCoords);
165 }
166 }
167 }
168 }
169
GetWeakSpot() const170 CoordsInt AutoFireUnit::GetWeakSpot() const {
171 CoordsInt ret;
172 //relative to centre of unit
173 ret.x = Random() % (width >> 1) - (width >> 2);
174 ret.y = Random() % (height >> 1) - (height >> 2);
175 return ret;
176 }
177
GetWeapCoords(vector<CoordsInt> & giveSmall,CoordsInt & giveBig) const178 void AutoFireUnit::GetWeapCoords(vector<CoordsInt>& giveSmall, CoordsInt& giveBig) const {
179 giveSmall = smallPositions;
180 giveBig = bigPosition;
181 }
182
183 ///
184
CapitalShip(int iMySide,int iMyGroup,const string & iName,CapShipType iCSType)185 CapitalShip::CapitalShip(int iMySide, int iMyGroup, const string& iName, CapShipType iCSType):
186 AutoFireUnit(iName, iMySide, iMyGroup) {
187 //load if found either in struct or on hard drive
188 if (sides[mySide].sdStruct.unitData.find(name) != sides[mySide].sdStruct.unitData.end()
189 || DoesFileExist(GetFleetDir(mySide) + name + ".dat"))
190 LoadData();
191
192 else {
193 myType = UT_CaShUnit;
194 myCSType = iCSType;
195 DefaultTypeDepStats();
196 LoadPicture();
197 }
198 }
199
Upkeep()200 void CapitalShip::Upkeep() {
201 AutoFireUnit::Upkeep();
202
203 if (armourCurrent < 1) {
204 alive = false;
205 explodeTimer = csExplodeFrames * framesPerAnimFrame;
206 }
207 }
208
Explode()209 void CapitalShip::Explode() {
210 if (sides[mySide].groups[myGroup].GetOnScreen()) {
211 if (explodeTimer > framesPerAnimFrame * 8) {
212 switch (myCSType) {
213 case CST_Heavy:
214 JSDL.Blt(genPictures[GENPIC_HCSEXPLODE1], USRect);
215 break;
216 case CST_Medium:
217 JSDL.Blt(genPictures[GENPIC_MCSEXPLODE1], USRect);
218 break;
219 case CST_Light:
220 JSDL.Blt(genPictures[GENPIC_LCSEXPLODE1], USRect);
221 break;
222 }
223 } else if (explodeTimer > framesPerAnimFrame * 7) {
224 switch (myCSType) {
225 case CST_Heavy:
226 JSDL.Blt(genPictures[GENPIC_HCSEXPLODE2], USRect);
227 break;
228 case CST_Medium:
229 JSDL.Blt(genPictures[GENPIC_MCSEXPLODE2], USRect);
230 break;
231 case CST_Light:
232 JSDL.Blt(genPictures[GENPIC_LCSEXPLODE2], USRect);
233 break;
234 }
235 } else if (explodeTimer > framesPerAnimFrame * 6) {
236 switch (myCSType) {
237 case CST_Heavy:
238 JSDL.Blt(genPictures[GENPIC_HCSEXPLODE3], USRect);
239 break;
240 case CST_Medium:
241 JSDL.Blt(genPictures[GENPIC_MCSEXPLODE3], USRect);
242 break;
243 case CST_Light:
244 JSDL.Blt(genPictures[GENPIC_LCSEXPLODE3], USRect);
245 break;
246 }
247 } else if (explodeTimer > framesPerAnimFrame * 5) {
248 switch (myCSType) {
249 case CST_Heavy:
250 JSDL.Blt(genPictures[GENPIC_HCSEXPLODE4], USRect);
251 break;
252 case CST_Medium:
253 JSDL.Blt(genPictures[GENPIC_MCSEXPLODE4], USRect);
254 break;
255 case CST_Light:
256 JSDL.Blt(genPictures[GENPIC_LCSEXPLODE4], USRect);
257 break;
258 }
259 } else if (explodeTimer > framesPerAnimFrame * 4) {
260 switch (myCSType) {
261 case CST_Heavy:
262 JSDL.Blt(genPictures[GENPIC_HCSEXPLODE5], USRect);
263 break;
264 case CST_Medium:
265 JSDL.Blt(genPictures[GENPIC_MCSEXPLODE5], USRect);
266 break;
267 case CST_Light:
268 JSDL.Blt(genPictures[GENPIC_LCSEXPLODE5], USRect);
269 break;
270 }
271 } else if (explodeTimer > framesPerAnimFrame * 3) {
272 switch (myCSType) {
273 case CST_Heavy:
274 JSDL.Blt(genPictures[GENPIC_HCSEXPLODE6], USRect);
275 break;
276 case CST_Medium:
277 JSDL.Blt(genPictures[GENPIC_MCSEXPLODE6], USRect);
278 break;
279 case CST_Light:
280 JSDL.Blt(genPictures[GENPIC_LCSEXPLODE6], USRect);
281 break;
282 }
283 } else if (explodeTimer > framesPerAnimFrame * 2) {
284 switch (myCSType) {
285 case CST_Heavy:
286 JSDL.Blt(genPictures[GENPIC_HCSEXPLODE7], USRect);
287 break;
288 case CST_Medium:
289 JSDL.Blt(genPictures[GENPIC_MCSEXPLODE7], USRect);
290 break;
291 case CST_Light:
292 JSDL.Blt(genPictures[GENPIC_LCSEXPLODE7], USRect);
293 break;
294 }
295 } else if (explodeTimer > framesPerAnimFrame) {
296 switch (myCSType) {
297 case CST_Heavy:
298 JSDL.Blt(genPictures[GENPIC_HCSEXPLODE8], USRect);
299 break;
300 case CST_Medium:
301 JSDL.Blt(genPictures[GENPIC_MCSEXPLODE8], USRect);
302 break;
303 case CST_Light:
304 JSDL.Blt(genPictures[GENPIC_LCSEXPLODE8], USRect);
305 break;
306 }
307 } else {
308 switch (myCSType) {
309 case CST_Heavy:
310 JSDL.Blt(genPictures[GENPIC_HCSEXPLODE9], USRect);
311 break;
312 case CST_Medium:
313 JSDL.Blt(genPictures[GENPIC_MCSEXPLODE9], USRect);
314 break;
315 case CST_Light:
316 JSDL.Blt(genPictures[GENPIC_LCSEXPLODE9], USRect);
317 break;
318 }
319 }
320 }
321 }
322
ChangeCSType(CapShipType newType)323 void CapitalShip::ChangeCSType(CapShipType newType) {
324 myCSType = newType;
325
326 //includes 3 character size extension
327 string oldPicName = picName;
328 string newPicName = picName.substr(0, picName.size() - 3);
329 DefaultTypeDepStats();
330 //just got overwritten by defaulting stats
331 picName = oldPicName;
332 ChangeUnitPic(newPicName);
333 }
334
DefaultTypeDepStats()335 void CapitalShip::DefaultTypeDepStats() {
336 engineName = globalSettings.defaultCSEngine;
337
338 bigPosition.x = 0;
339 bigPosition.y = 0;
340
341 picName = globalSettings.defaultCSPic;
342
343 switch (myCSType) {
344 case CST_Heavy:
345 width = HCSWidth;
346 height = HCSHeight;
347 capacity = HCSCap;
348 armourName = globalSettings.defaultHCSArmour;
349 break;
350
351 case CST_Medium:
352 width = MCSWidth;
353 height = MCSHeight;
354 capacity = MCSCap;
355 armourName = globalSettings.defaultMCSArmour;
356 break;
357
358 case CST_Light:
359 width = LCSWidth;
360 height = LCSHeight;
361 capacity = LCSCap;
362 armourName = globalSettings.defaultLCSArmour;
363 break;
364 }
365
366 RTSUnit_Base::DefaultTypeDepStats();
367 }
368
GetFrCapacity() const369 int CapitalShip::GetFrCapacity() const {
370 switch (myCSType) {
371 case CST_Heavy:
372 return HCSFrCap;
373 break;
374
375 case CST_Medium:
376 return MCSFrCap;
377 break;
378
379 case CST_Light:
380 return LCSFrCap;
381 break;
382 }
383 }
384
385 //these are default coords for new cap ships, they are overwritten by those found in unit files
SetSmallNumber()386 void CapitalShip::SetSmallNumber() {
387 smallPositions.clear();
388
389 if (smallType == WT_None)
390 smallNumber = 0;
391 else {
392 CoordsInt tempCoords;
393
394 switch (myCSType) {
395 case CST_Heavy:
396 smallNumber = HCSCap;
397
398 tempCoords.x = 70;
399 tempCoords.y = 26;
400 smallPositions.push_back(tempCoords);
401 tempCoords.x = 200;
402 smallPositions.push_back(tempCoords);
403 tempCoords.x = 330;
404 smallPositions.push_back(tempCoords);
405 tempCoords.x = 450;
406 smallPositions.push_back(tempCoords);
407
408 tempCoords.x = 70;
409 tempCoords.y = 82;
410 smallPositions.push_back(tempCoords);
411 tempCoords.x = 180;
412 smallPositions.push_back(tempCoords);
413 tempCoords.x = 300;
414 smallPositions.push_back(tempCoords);
415 tempCoords.x = 450;
416 smallPositions.push_back(tempCoords);
417
418 tempCoords.x = 450;
419 tempCoords.y = HCSHeight / 2;
420 smallPositions.push_back(tempCoords);
421 break;
422
423 case CST_Medium:
424 smallNumber = MCSCap;
425
426 tempCoords.x = 70;
427 tempCoords.y = 26;
428 smallPositions.push_back(tempCoords);
429 tempCoords.x = 180;
430 smallPositions.push_back(tempCoords);
431 tempCoords.x = 300;
432 tempCoords.y = 30;
433 smallPositions.push_back(tempCoords);
434
435 tempCoords.x = 70;
436 tempCoords.y = 82;
437 smallPositions.push_back(tempCoords);
438 tempCoords.x = 180;
439 smallPositions.push_back(tempCoords);
440 tempCoords.x = 300;
441 tempCoords.y = 76;
442 smallPositions.push_back(tempCoords);
443 break;
444
445 case CST_Light:
446 smallNumber = LCSCap;
447
448 tempCoords.x = 50;
449 tempCoords.y = LCSHeight / 2;
450 smallPositions.push_back(tempCoords);
451 tempCoords.x = 150;
452 smallPositions.push_back(tempCoords);
453 tempCoords.x = 250;
454 smallPositions.push_back(tempCoords);
455 break;
456 }
457 }
458 }
459
Frigate(int iMySide,int iMyGroup,const string & iName)460 Frigate::Frigate(int iMySide, int iMyGroup, const string& iName):
461 AutoFireUnit(iName, iMySide, iMyGroup) {
462 //load if found either in struct or on hard drive
463 if (sides[mySide].sdStruct.unitData.find(name) != sides[mySide].sdStruct.unitData.end()
464 || DoesFileExist(GetFleetDir(mySide) + name + ".dat"))
465 LoadData();
466
467 else {
468 myType = UT_FrUnit;
469 DefaultTypeDepStats();
470 LoadPicture();
471 }
472 }
473
DefaultTypeDepStats()474 void Frigate::DefaultTypeDepStats() {
475 width = FrWidth;
476 height = FrHeight;
477 picName = globalSettings.defaultFrPic;
478 engineName = globalSettings.defaultFrEngine;
479 armourName = globalSettings.defaultFrArmour;
480
481 bigPosition.x = 75;
482 bigPosition.y = 25;
483
484 RTSUnit_Base::DefaultTypeDepStats();
485 }
486
Upkeep()487 void Frigate::Upkeep() {
488 AutoFireUnit::Upkeep();
489
490 if (armourCurrent < 1) {
491 alive = false;
492 explodeTimer = frExplodeFrames * framesPerAnimFrame;
493 }
494 }
495
Explode()496 void Frigate::Explode() {
497 if (sides[mySide].groups[myGroup].GetOnScreen()) {
498 if (explodeTimer > framesPerAnimFrame * 6)
499 JSDL.Blt(genPictures[GENPIC_FREXPLODE1], USRect);
500 else if (explodeTimer > framesPerAnimFrame * 5)
501 JSDL.Blt(genPictures[GENPIC_FREXPLODE2], USRect);
502 else if (explodeTimer > framesPerAnimFrame * 4)
503 JSDL.Blt(genPictures[GENPIC_FREXPLODE3], USRect);
504 else if (explodeTimer > framesPerAnimFrame * 3)
505 JSDL.Blt(genPictures[GENPIC_FREXPLODE4], USRect);
506 else if (explodeTimer > framesPerAnimFrame * 2)
507 JSDL.Blt(genPictures[GENPIC_FREXPLODE5], USRect);
508 else if (explodeTimer > framesPerAnimFrame)
509 JSDL.Blt(genPictures[GENPIC_FREXPLODE6], USRect);
510 else
511 JSDL.Blt(genPictures[GENPIC_FREXPLODE7], USRect);
512 }
513 }
514
SetSmallNumber()515 void Frigate::SetSmallNumber() {
516 smallPositions.clear();
517
518 if (smallType == WT_None)
519 smallNumber = 0;
520 else {
521 smallNumber = FrSmNumber;
522 CoordsInt tempCoords;
523
524 tempCoords.x = 30;
525 tempCoords.y = FrHeight / 2;
526 smallPositions.push_back(tempCoords);
527 tempCoords.x = 70;
528 tempCoords.y = FrHeight / 2;
529 smallPositions.push_back(tempCoords);
530 }
531 }
532
DrawSelfPixels()533 void Frigate::DrawSelfPixels() {
534 if (weaponLookup[bigType].category == WCAT_Large && bigStage == w_Firing && alive) {
535 CoordsInt tmp = sides[bigTarget.x].groups[bigTarget.y].GetUnitCenter(bigTargetUnit);
536
537 int startx;
538 if (bFlip)
539 startx = static_cast<int>(myx) + width - bigPosition.x;
540 else
541 startx = static_cast<int>(myx) + bigPosition.x;
542
543 int finx = tmp.x + targetWeakSpot.x;
544 int finy = tmp.y + targetWeakSpot.y;
545
546 Projectile_Base::DrawBigLaser(startx, static_cast<int>(myy) + bigPosition.y, finx, finy, sides[mySide].laserColor);
547 }
548 }
549
550