1 /*
2 * Kuklomenos
3 * Copyright (C) 2008-2009 Martin Bays <mbays@sdf.lonestar.org>
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 as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see http://www.gnu.org/licenses/.
17 */
18
19 #include <cstdlib>
20 #include <algorithm>
21
22 #include "invaders.h"
23 #include "state.h"
24 #include "geom.h"
25 #include "random.h"
26 #include "collision.h"
27
28 using namespace std;
29
update(int time)30 void Invader::update(int time)
31 {
32 CartCoord startPos = cpos();
33 doUpdate(time);
34 RelCartCoord velocity = (cpos()-startPos)*(1.0/time);
35 setCollTrajectory(startPos, velocity);
36 }
37
spawnInvader(Invader * invader)38 void Invader::spawnInvader(Invader* invader)
39 {
40 spawns.push_back(invader);
41 }
42
collObj() const43 const CollisionObject& CircularInvader::collObj() const
44 {
45 return cc;
46 }
setCollTrajectory(CartCoord startPos,RelCartCoord velocity)47 void CircularInvader::setCollTrajectory(CartCoord startPos, RelCartCoord velocity)
48 {
49 cc.startPos = startPos;
50 cc.velocity = velocity;
51 }
52
innerColour() const53 Uint32 Invader::innerColour() const
54 {
55 // same as colour(), but with alpha set to 0x60:
56 return (colour() >> 8 << 8) + 0x60;
57 }
58
collObj() const59 const CollisionObject& SpirallingPolygonalInvader::collObj() const
60 {
61 return cp;
62 }
setCollTrajectory(CartCoord startPos,RelCartCoord velocity)63 void SpirallingPolygonalInvader::setCollTrajectory(CartCoord startPos,
64 RelCartCoord velocity)
65 {
66 cp.startPos = startPos;
67 cp.velocity = velocity;
68 cp.angle = pos.angle;
69 }
70
doUpdate(int time)71 void KamikazeInvader::doUpdate(int time)
72 {
73 BasicInvader::doUpdate(time);
74
75 if (kamikaze == 0)
76 {
77 if (rani(3000) <= time && rani(ARENA_RAD/2) > pos.dist)
78 kamikaze = 1;
79 }
80 else
81 if ((timer+=time) > 200)
82 {
83 timer = 0;
84 switch (kamikaze)
85 {
86 case 1:
87 if (ds == 0)
88 kamikaze = 2;
89 else
90 ds += (ds > 0 ? -1 : 1);
91 break;
92 case 2:
93 if (dd < 8)
94 dd++;
95 }
96 }
97 }
98
doUpdate(int time)99 void SplittingInvader::doUpdate(int time)
100 {
101 BasicInvader::doUpdate(time);
102
103 if (pos.dist <= spawnDist + ARENA_RAD/3)
104 radius = 5.0 + 2.0 * cosf(
105 (PI/2)*(1 - (pos.dist-spawnDist)/(ARENA_RAD/3)));
106
107 if (pos.dist <= spawnDist)
108 {
109 if (hp > 1)
110 for (int dds = -1; dds <= 1; dds += 2)
111 spawnInvader(new EggInvader(pos, ds+dds));
112 else
113 spawnInvader(new EggInvader(pos, ds));
114 die();
115 }
116 }
117
draw(SDL_Surface * surface,const View & view,View * boundView,bool noAA) const118 void SplittingInvader::draw(SDL_Surface* surface, const View& view,
119 View* boundView, bool noAA) const
120 {
121 const float eggRad =
122 5.0 * sinf((PI/2) * (max(0.0f, 2 - pos.dist/spawnDist)));
123 Circle(cpos(), eggRad, 0xff000000 + (int)(0xb0 * (eggRad/5.0)),
124 true).draw( surface, view, boundView, noAA);
125
126 Circle(cpos(), radius, (colour() >> 8 << 8) + (int)(0x60 - 0x10 * eggRad),
127 true).draw(surface, view, boundView, noAA);
128 Circle(cpos(), radius, colour()).draw(surface, view, boundView, noAA);
129
130 if (super)
131 drawSuper(surface, view, boundView, noAA);
132 }
133
colour() const134 Uint32 BasicInvader::colour() const
135 {
136 switch(hp)
137 {
138 case 3: return 0x00ff00ff;
139 case 2: return 0xffff00ff;
140 default: return 0xff0000ff;
141 }
142 }
143
colour() const144 Uint32 KamikazeInvader::colour() const
145 {
146 switch(hp)
147 {
148 case 2: return (kamikaze ? 0xffff00ff : 0xa0a000ff);
149 default: return (kamikaze ? 0xff0000ff : 0xa00000ff);
150 }
151 }
152
innerColour() const153 Uint32 KamikazeInvader::innerColour() const
154 {
155 switch(hp)
156 {
157 case 2: return (kamikaze ? 0xffff00a0 : 0xa0a00060);
158 default: return (kamikaze ? 0xff0000a0 : 0xa0000060);
159 }
160 }
161
colour() const162 Uint32 FoulEggLayingInvader::colour() const
163 {
164 return 0xffa000ff;
165 /*
166 switch(hp)
167 {
168 case 5: return 0xff00ffff;
169 case 4: return 0xcf00cfff;
170 case 3: return 0xaf00afff;
171 case 2: return 0x8f008fff;
172 default: return 0x6f006fff;
173 }
174 */
175 }
176
177
dead() const178 bool HPInvader::dead() const
179 {
180 return hp <= 0;
181 }
182
cpos() const183 CartCoord SpirallingInvader::cpos() const
184 {
185 return focus + pos;
186 }
187
hit(int weight)188 int HPInvader::hit(int weight)
189 {
190 int used = min(weight, hp+armour);
191 hp -= max(0, used-armour);
192 return used;
193 }
194
die()195 int HPInvader::die()
196 {
197 return hit(hp+armour);
198 }
199
doUpdate(int time)200 void SpirallingInvader::doUpdate(int time)
201 {
202 pos.angle += ds*0.0001*(100/pos.dist)*time;
203 pos.dist += dd*-0.0075*time;
204 }
205
fleeOnWin()206 void SpirallingInvader::fleeOnWin()
207 {
208 dd *= -3;
209 }
210
dodge()211 void SpirallingInvader::dodge()
212 {
213 ds = -ds;
214 }
215
draw(SDL_Surface * surface,const View & view,View * boundView,bool noAA) const216 void CircularInvader::draw(SDL_Surface* surface, const View& view, View*
217 boundView, bool noAA) const
218 {
219 Circle(cpos(), radius, innerColour(), true).draw(surface,
220 view, boundView, noAA);
221 Circle(cpos(), radius, colour()).draw(surface, view, boundView, noAA);
222 }
223
drawSuper(SDL_Surface * surface,const View & view,View * boundView,bool noAA) const224 void SpirallingInvader::drawSuper(SDL_Surface* surface, const View& view,
225 View* boundView, bool noAA) const
226 {
227 Line(cpos() + RelPolarCoord(pos.angle, 1.5),
228 cpos() + RelPolarCoord(pos.angle, -1.5),
229 0xffffffff).draw(surface, view, boundView, noAA);
230 Line(cpos() + RelPolarCoord(pos.angle+1, 1.5),
231 cpos() + RelPolarCoord(pos.angle+1, -1.5),
232 0xffffffff).draw(surface, view, boundView, noAA);
233 }
234
draw(SDL_Surface * surface,const View & view,View * boundView,bool noAA) const235 void BasicInvader::draw(SDL_Surface* surface, const View& view, View*
236 boundView, bool noAA) const
237 {
238 CircularInvader::draw(surface, view, boundView, noAA);
239 if (super)
240 drawSuper(surface, view, boundView, noAA);
241 }
242
243
SpirallingPolygonalInvader(int inumPoints,RelPolarCoord ipos,float ids,float idd,CartCoord ifocus)244 SpirallingPolygonalInvader::SpirallingPolygonalInvader(int inumPoints,
245 RelPolarCoord ipos, float ids, float idd, CartCoord ifocus) :
246 SpirallingInvader(ipos, ids, idd, ifocus),
247 numPoints(inumPoints),
248 points(new RelCartCoord[numPoints]),
249 cp(inumPoints, points)
250 {
251 }
252
SpirallingPolygonalInvader(const SpirallingPolygonalInvader & other)253 SpirallingPolygonalInvader::SpirallingPolygonalInvader(
254 const SpirallingPolygonalInvader& other) :
255 SpirallingInvader(other.pos, other.ds, other.dd, other.focus),
256 numPoints(other.numPoints),
257 points(new RelCartCoord[numPoints]),
258 cp(other.numPoints, points)
259 {
260 for (int i = 0; i < numPoints; i++)
261 points[i] = other.points[i];
262 }
263
operator =(const SpirallingPolygonalInvader & other)264 SpirallingPolygonalInvader& SpirallingPolygonalInvader::operator=(
265 const SpirallingPolygonalInvader& other)
266 {
267 if (this != &other)
268 {
269 pos = other.pos; ds = other.ds; dd = other.dd; focus = other.focus;
270 numPoints = other.numPoints;
271 points = new RelCartCoord[numPoints];
272 cp = other.cp;
273 for (int i = 0; i < numPoints; i++)
274 points[i] = other.points[i];
275 }
276 return *this;
277 }
278
~SpirallingPolygonalInvader()279 SpirallingPolygonalInvader::~SpirallingPolygonalInvader()
280 {
281 delete[] points;
282 }
283
getAbsPoints(CartCoord * absPoints) const284 void SpirallingPolygonalInvader::getAbsPoints(CartCoord* absPoints) const
285 {
286 for (int i=0; i<numPoints; i++)
287 absPoints[i] = cpos() + points[i].rotated(pos.angle);
288 }
289
draw(SDL_Surface * surface,const View & view,View * boundView,bool noAA) const290 void SpirallingPolygonalInvader::draw(SDL_Surface* surface, const View& view,
291 View* boundView, bool noAA) const
292 {
293 CartCoord* absPoints = new CartCoord[numPoints];
294
295 getAbsPoints(absPoints);
296
297 const Uint32 innerCol = innerColour();
298 if (innerCol != 0)
299 Polygon(absPoints, numPoints, innerCol, true).draw(
300 surface, view, boundView, noAA);
301
302 Polygon(absPoints, numPoints, colour()).draw(surface, view, boundView, noAA);
303
304 delete[] absPoints;
305 }
306
draw(SDL_Surface * surface,const View & view,View * boundView,bool noAA) const307 void FoulEggLayingInvader::draw(SDL_Surface* surface, const View& view,
308 View* boundView, bool noAA) const
309 {
310 SpirallingPolygonalInvader::draw(surface, view, boundView, noAA);
311
312 CartCoord* absPoints = new CartCoord[numPoints];
313 getAbsPoints(absPoints);
314
315 if (eggRadius > 0)
316 Circle(absPoints[4] + (
317 RelCartCoord(0, -eggRadius).rotated(pos.angle)),
318 eggRadius, 0xff0000ff).draw(surface, view, boundView, noAA);
319
320 delete[] absPoints;
321 }
322
323
setPoints(int time)324 void FoulEggLayingInvader::setPoints(int time)
325 {
326 // animation code, currently not non-trivially used - TODO: remove it
327 for (int i=0; i < numPoints; i++)
328 {
329 //const int x = 2+3*hp;
330 //const int y = 3+1*hp;
331 const int x = 6;
332 const int y = 4;
333 RelCartCoord aimed =
334 (i == 0) ? RelCartCoord(x, -y) :
335 ((i == 1) ? RelCartCoord(x, y) :
336 ((i == 2) ? RelCartCoord(-x, y) :
337 ((i == 3) ? RelCartCoord(-x, -y) :
338 (RelCartCoord(0, -2*y)))));
339 if (time == -1)
340 {
341 // Just set:
342 points[i] = aimed;
343 }
344 else
345 {
346 RelCartCoord d = aimed - points[i];
347 if (!(d.dx == 0 && d.dy == 0))
348 points[i] += d * min(1.0f, ((float)time/50)/d.lengthsq());
349 }
350 }
351 }
352
353 float FoulEggLayingInvader::eggRate = 0.0002;
354 float FoulEggLayingInvader::layRadius = 5;
doUpdate(int time)355 void FoulEggLayingInvader::doUpdate(int time)
356 {
357 SpirallingPolygonalInvader::doUpdate(time);
358
359 eggRadius += time*eggRate;
360
361 if (eggRadius >= layRadius)
362 {
363 RelPolarCoord p(pos.angle, pos.dist + points[4].dy - eggRadius);
364 Invader* egg = new EggInvader(p, ds);
365 spawnInvader(egg);
366
367 eggRadius = 0;
368 }
369
370 //setPoints(time);
371 }
372
hit(int weight)373 int FoulEggLayingInvader::hit(int weight)
374 {
375 eggRadius = 0;
376 if (weight >= 3)
377 {
378 hp = 0;
379 return 3;
380 }
381 return weight;
382 }
383
EggInvader(RelPolarCoord ipos,float ids,bool super)384 EggInvader::EggInvader(RelPolarCoord ipos, float ids, bool super) :
385 BasicInvader(1, ipos, ids, 3+super*1.5, 5, super)
386 {}
KamikazeInvader(RelPolarCoord ipos,float ids,bool super)387 KamikazeInvader::KamikazeInvader(RelPolarCoord ipos, float ids, bool super) :
388 BasicInvader(2, ipos, ids, 2+super*1.5, 6, super), kamikaze(0), timer(0)
389 {}
SplittingInvader(RelPolarCoord ipos,float ids,bool super)390 SplittingInvader::SplittingInvader(RelPolarCoord ipos, float ids, bool super) :
391 BasicInvader(3, ipos, ids, 1+super*1.5, 7, super)
392 {
393 spawnDist = ARENA_RAD/10 + rani(4*ARENA_RAD/10);
394 }
InfestingInvader(Node * itargetNode,bool super)395 InfestingInvader::InfestingInvader(Node* itargetNode, bool super) :
396 HPInvader(3,1), CircularInvader(6),
397 SpirallingInvader(
398 RelPolarCoord(itargetNode->pos.angle + ranf(0.5)-0.25, ARENA_RAD),
399 0, 0.5 + super*0.2),
400 healRate(0.1 + super*0.025),
401 partialHP(0), shownHP(3), infesting(false),
402 super(super),
403 maxHP(3),
404 glowPhase(0),
405 targetNode(itargetNode)
406 {
407 targetNode->targettingInfester = this;
408 }
CapturePod(Node * itargetNode,RelPolarCoord ipos,bool super)409 CapturePod::CapturePod(Node* itargetNode, RelPolarCoord ipos, bool super) :
410 HPInvader(super ? 3 : 1), CircularInvader(2),
411 SpirallingInvader(
412 ipos,
413 0, -1.25 + super*-0.5),
414 targetNode(itargetNode),
415 super(super),
416 primeRate(super ? 1.0/25 : 1.0/30)
417 {}
418
doUpdate(int time)419 void InfestingInvader::doUpdate(int time)
420 {
421 SpirallingInvader::doUpdate(time);
422
423 pos.angle = targetNode->pos.angle;
424
425 if (!infesting && pos.dist <= targetNode->pos.dist)
426 {
427 if (targetNode->infest(this))
428 {
429 pos.dist = targetNode->pos.dist;
430 dd = 0;
431 infesting = true;
432 }
433 else
434 die();
435 }
436 if (infesting && targetNode->status != NODEST_EVIL)
437 {
438 // Infested node has been recaptured
439 infesting = false;
440 die();
441 }
442 if (dd > 0 && targetNode->status == NODEST_DESTROYED)
443 {
444 dd *= -3;
445 }
446 if (hp < maxHP)
447 {
448 partialHP += healRate*0.001*time;
449 if (partialHP >= 1)
450 {
451 hp++;
452 partialHP = 0;
453 }
454 }
455 if (infesting && hp == maxHP)
456 {
457 partialHP += healRate*0.25*0.001*time;
458 if (partialHP >= 1)
459 {
460 // Become invulnerable to all but CapturePods:
461 hp++;
462 partialHP = 0;
463 armour = 3;
464 cc.radius += 2;
465 }
466 }
467 if (hp == maxHP+1)
468 glowPhase += time*0.003;
469
470 const float totalHP = hp + partialHP;
471 if (shownHP < totalHP)
472 shownHP = std::min(totalHP, shownHP +
473 std::max(0.004f, 0.01f*(totalHP - shownHP)) * time);
474 else
475 shownHP = std::max(totalHP, shownHP +
476 std::min(-0.004f, 0.01f*(totalHP - shownHP)) * time);
477 }
478
fleeOnWin()479 void InfestingInvader::fleeOnWin()
480 {
481 dd = -3;
482 if (infesting)
483 targetNode->uninfest();
484 targetNode->targettingInfester = NULL;
485 infesting = false;
486 }
487
doUpdate(int time)488 void CapturePod::doUpdate(int time)
489 {
490 SpirallingInvader::doUpdate(time);
491
492 pos.angle = targetNode->pos.angle;
493
494 if (pos.dist >= targetNode->pos.dist)
495 {
496 targetNode->capture(this);
497 die();
498 }
499 }
500
evil() const501 bool CapturePod::evil() const { return false; }
hitsYou() const502 bool CapturePod::hitsYou() const { return false; }
hitsInvaders() const503 float CapturePod::hitsInvaders() const
504 {
505 return radius;
506 }
killScore() const507 int CapturePod::killScore() const { return 0; }
508
fleeOnWin()509 void CapturePod::fleeOnWin()
510 {
511 }
512
colour() const513 Uint32 InfestingInvader::colour() const
514 {
515 return 0x0000c0ff;
516 }
colour() const517 Uint32 CapturePod::colour() const
518 {
519 return 0x0000c0ff + ((0xff00-0xc000)*(hp-1)/2);
520 }
draw(SDL_Surface * surface,const View & view,View * boundView,bool noAA) const521 void InfestingInvader::draw(SDL_Surface* surface, const View& view, View*
522 boundView, bool noAA) const
523 {
524 Circle(cpos(), radius, (colour() >> 8 << 8) + 0xc0,
525 true).draw(surface, view, boundView, noAA);
526
527 // healing
528 if (shownHP < 3)
529 {
530 Circle(cpos(), std::min(1.0f, 3 - shownHP)*4*radius/5,
531 0xffff0050,
532 true).draw(surface, view, boundView, noAA);
533 Circle(cpos(), std::min(1.0f, 3 - shownHP)*4*radius/5,
534 0x808000ff,
535 false).draw(surface, view, boundView, noAA);
536 }
537 if (shownHP < 2)
538 {
539 Circle(cpos(), std::min(1.0f, 2 - shownHP)*3*radius/5,
540 0xff000070,
541 true).draw(surface, view, boundView, noAA);
542 Circle(cpos(), std::min(1.0f, 2 - shownHP)*3*radius/5,
543 0x900000ff,
544 false).draw(surface, view, boundView, noAA);
545 }
546
547 // boundary
548 Circle(cpos(), radius, colour(),
549 false).draw(surface, view, boundView, noAA);
550
551 // partial shield
552 if (infesting && shownHP > maxHP && shownHP < maxHP + 1)
553 Circle(cpos(), radius * (shownHP - maxHP),
554 0x01010100*(int)(0x40 + 0x90 * (shownHP - maxHP)) + 0xff,
555 false).draw(surface, view, boundView, noAA);
556
557 // full shield
558 if (shownHP >= maxHP+1)
559 Circle(cpos(), radius+2, 0xffffff00 + (0xff -
560 (int)(0x40 * (1 + glowPhase.sinf()))),
561 false).draw(surface, view, boundView, noAA);
562
563 if (super)
564 drawSuper(surface, view, boundView, noAA);
565 }
onDeath() const566 void InfestingInvader::onDeath() const
567 {
568 if (infesting)
569 targetNode->uninfest();
570 targetNode->targettingInfester = NULL;
571 }
572
FoulEggLayingInvader(RelPolarCoord ipos,float ids,int ihp)573 FoulEggLayingInvader::FoulEggLayingInvader(RelPolarCoord ipos, float ids,
574 int ihp) :
575 HPInvader(ihp), SpirallingPolygonalInvader(5, ipos, ids, 0),
576 eggRadius(0)
577 {
578 setPoints(-1);
579 }
580