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