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 <vector>
20 #include <set>
21 #include <algorithm>
22 #include <cstdio>
23 #include <SDL/SDL.h>
24 #include <SDL_gfxPrimitivesDirty.h>
25
26 #include "state.h"
27 #include "shot.h"
28 #include "player.h"
29 #include "geom.h"
30 #include "gfx.h"
31 #include "random.h"
32 #include "settings.h"
33 #include "conffile.h"
34 #include "node.h"
35 #include "ai.h"
36 #include "sound.h"
37
38 const int pentatonicScale[14] = { 0, 2, 5, 7, 9, 12, 14, 17, 19, 21, 24, 26, 29 };
39 const int majorScale[14] = { 0, 2, 4, 5, 7, 9, 11, 12, 14, 16, 17, 19, 21, 23 };
40 const int minorScale[14] = { 0, 2, 3, 5, 7, 8, 10, 12, 14, 15, 19, 20, 22 };
41 const int* scales[3] = { pentatonicScale, majorScale, minorScale };
42
43 // beatRatios: beat frequency of inner is beatRatio * frequency of outer
44 const float beatRatios[5] = { 1.0/1.0, 3.0/2.0, 4.0/3.0, 5.0/4.0, 5.0/3.0 };
45
GameState(int speed)46 GameState::GameState(int speed) :
47 targettedNode(NULL), mutilationWave(-1), preMutilationPhase(0),
48 extractPreMutCutoff(350), freeViewMode(false),
49 extracted(0), extractDecayRate(0.0002), you(), zoomdist(0), invaderRate(0),
50 speed(speed), extractMax(500), end(END_NOT), ai(NULL)
51 {
52 setRating();
53 invaderCooldown = invaderRate;
54
55 std::vector<NodeColour> colours;
56 for (int i = 0; i < 6; i++)
57 colours.push_back( NodeColour(i) );
58
59 float baseAngleInner = 0;
60 float baseAngleOuter = 0;
61 int innerDir = 1;
62 if (this->rating > 5.0)
63 {
64 // Let's mix things up a bit...
65 colours = randomSort(colours);
66 baseAngleInner = ranf(4.0);
67 baseAngleOuter = ranf(4.0);
68 if (rani(2) == 0)
69 innerDir = -1;
70 }
71
72 const int* scale = scales[rani(3)];
73 const int key = rani(12) - 8;
74 const float beatRatio = beatRatios[rani(5)];
75
76 set<int> notesUsed;
77
78 int i=0;
79 for (std::vector<NodeColour>::iterator it = colours.begin();
80 it != colours.end();
81 i++, it++)
82 {
83 const NodeColour colour = *it;
84
85 int note = rani(14);
86 while (notesUsed.find(note) != notesUsed.end())
87 note = rani(14);
88 notesUsed.insert(note);
89
90 const int pitch = int(1000 *
91 pow(2, - (key+scale[note]) / 12.0) );
92 if (i < 3)
93 nodes.push_back(
94 Node(RelPolarCoord(baseAngleInner+2+i*4.0/3,
95 ARENA_RAD * 5/9),
96 innerDir*1.0, colour,
97 beatRatio*1.0/3000, rani(16)/4.0, pitch));
98 else
99 nodes.push_back(
100 Node(RelPolarCoord(baseAngleOuter+i*4.0/3,
101 ARENA_RAD * 2/3),
102 -innerDir*1.0, colour,
103 1.0/3000, rani(16)/4.0, pitch));
104 }
105 }
106
isNullInvp(Invader * p)107 bool isNullInvp(Invader* p)
108 {
109 return (p == NULL);
110 }
111
youHaveNode(NodeColour colour)112 bool GameState::youHaveNode(NodeColour colour)
113 {
114 for (std::vector<Node>::iterator it = nodes.begin();
115 it != nodes.end();
116 it++)
117 if (it->status == NODEST_YOU && it->nodeColour == colour)
118 return true;
119 return false;
120 }
youHaveShotNode(int type)121 bool GameState::youHaveShotNode(int type)
122 {
123 switch (type)
124 {
125 case 0: return youHaveNode(NODEC_GREEN); break;
126 case 1: return youHaveNode(NODEC_YELLOW); break;
127 case 2: return youHaveNode(NODEC_RED); break;
128 case 3: return youHaveNode(NODEC_BLUE); break;
129 default: return false;
130 }
131 }
evilHasNode(NodeColour colour)132 bool GameState::evilHasNode(NodeColour colour)
133 {
134 for (std::vector<Node>::iterator it = nodes.begin();
135 it != nodes.end();
136 it++)
137 if (it->status == NODEST_EVIL && it->nodeColour == colour)
138 return true;
139 return false;
140 }
141
shotHeat(int type)142 int GameState::shotHeat(int type)
143 {
144 if (type == 3)
145 return you.shootMaxHeat-1;
146
147 int baseHeat;
148 const bool super = youHaveShotNode(type);
149 switch (type)
150 {
151 case 0: baseHeat = 4000; break;
152 case 1: baseHeat = 7000; break;
153 case 2: baseHeat = 10000; break;
154 default: baseHeat = 0;
155 }
156 return super ? 7*baseHeat/10 : baseHeat;
157 }
shotDelay(int type)158 int GameState::shotDelay(int type)
159 {
160 int delay;
161 const bool super = youHaveShotNode(type);
162 switch (type)
163 {
164 case 0: delay = super ? 100 : 200; break;
165 case 1: delay = super ? 250 : 300; break;
166 case 2: delay = super ? 300 : 400; break;
167 case 3: delay = 10000; break;
168 default: delay = 0;
169 }
170 return delay;
171 }
172
updateMutilation(int time)173 void GameState::updateMutilation(int time)
174 {
175 if (mutilationWave > 0)
176 {
177 mutilationWave = std::max(0.0f,
178 mutilationWave - time*0.001f*ARENA_RAD/3);
179 if (mutilationWave < 0)
180 mutilationWave = 0;
181
182 for (std::vector<Node>::iterator it = nodes.begin();
183 it != nodes.end();
184 it++)
185 if (it->pos.dist >= mutilationWave-it->radius)
186 if (it->status != NODEST_NONE &&
187 it->status != NODEST_DESTROYED)
188 {
189 it->status = NODEST_NONE;
190
191 soundEvents.newEvent(it->cpos() - ARENA_CENTRE,
192 mutChunk, 128, it->pitch,
193 true);
194 }
195
196 for (std::vector<Invader*>::iterator it = invaders.begin();
197 it != invaders.end();
198 it++)
199 if (((*it)->cpos() - ARENA_CENTRE).lengthsq() >=
200 mutilationWave*mutilationWave)
201 {
202 int damage = (*it)->die();
203 if ((*it)->dead())
204 {
205 soundEvents.newEvent((*it)->cpos() - ARENA_CENTRE,
206 invDieChunk,
207 128, 500+100*damage + int(200*gaussian()),
208 true);
209 soundEvents.newEvent((*it)->cpos() - ARENA_CENTRE,
210 mutChunk,
211 128, 500+100*damage + int(200*gaussian()),
212 true);
213 }
214 }
215
216 for (std::vector<Shot>::iterator it = shots.begin();
217 it != shots.end();
218 it++)
219 if ((it->pos - ARENA_CENTRE).lengthsq() >=
220 mutilationWave*mutilationWave)
221 {
222 it->die();
223 deadShots = true;
224 }
225
226 while (!you.dead && mutilationWave <= you.radius())
227 {
228 soundEvents.newEvent(0, shieldChunk,
229 128, 1000 + 100*int(you.shield));
230 you.shield -= 1;
231 if (you.shield < 0)
232 {
233 you.dead = true;
234 soundEvents.newEvent(0, mutChunk,
235 128, 2000);
236 }
237 else
238 soundEvents.newEvent(0, mutChunk,
239 128, 1000 + 100*int(you.shield));
240 end = END_EXTRACTED;
241 }
242 }
243 else if (extracted > extractPreMutCutoff && mutilationWave == -1)
244 {
245 preMutilationPhase += time*0.002;
246 if (preMutilationPhase < time*0.002)
247 {
248 float stage = float(extracted - extractPreMutCutoff) /
249 (extractMax - extractPreMutCutoff );
250 soundEvents.newEvent(0, mutChunk,
251 int(24 + 48 * stage),
252 int(1400 - 400 * stage));
253 }
254 }
255 }
256
update(int time,bool noInput)257 void GameState::update(int time, bool noInput)
258 {
259 if (time <= 0)
260 return;
261
262 deadShots = false;
263
264 if (ai)
265 ai->update(time);
266
267 updateObjects(time);
268
269 if (freeViewMode)
270 {
271 if (!noInput)
272 handleFreeViewInput(time);
273 }
274 else
275 {
276 if ( (!noInput || ai) && !you.dead)
277 handleGameInput(time);
278 updateZoom(time);
279 }
280
281 evilAI(time);
282
283 soundEvents.update(RelPolarCoord(you.aim.angle, zoomdist));
284
285 cleanup();
286 }
287
updateObjects(int time)288 void GameState::updateObjects(int time)
289 {
290 you.update(time, youHaveNode(NODEC_CYAN));
291
292 if (!end && you.dead)
293 end = END_DEAD;
294
295 extracted -= extractDecayRate*(end == END_WIN ? 10 : 1)*time;
296 extracted = std::max(0.0, extracted);
297
298 int destroyedCount = 0;
299 for (std::vector<Node>::iterator it = nodes.begin();
300 it != nodes.end();
301 it++)
302 {
303 it->update(time);
304
305 if (it->status == NODEST_EVIL)
306 {
307 extracted += it->extract(time, evilHasNode(NODEC_CYAN));
308 if (extracted > extractMax && mutilationWave == -1)
309 // initiate wave of mutilation
310 mutilationWave = ARENA_RAD;
311 }
312 else if (it->status == NODEST_DESTROYED)
313 destroyedCount++;
314 }
315 if (destroyedCount >= 4 && end == END_NOT)
316 {
317 // you win - set invaders fleeing away
318 end = END_WIN;
319 for (std::vector<Invader*>::iterator it = invaders.begin();
320 it != invaders.end();
321 it++)
322 (*it)->fleeOnWin();
323 }
324
325 updateMutilation(time);
326
327 std::vector<Invader*> topush;
328
329 for (std::vector<Invader*>::iterator it = invaders.begin();
330 it != invaders.end();
331 it++)
332 {
333 Invader* inv = *it;
334 inv->update(time);
335
336 while (!inv->spawns.empty())
337 {
338 Invader* spawned = inv->spawns.back();
339 inv->spawns.pop_back();
340 topush.push_back(spawned);
341 }
342
343 if (inv->hitsYou() &&
344 inv->collObj().circleIntersects(ARENA_CENTRE, you.radius()))
345 {
346 inv->die();
347 if (!you.dead)
348 {
349 soundEvents.newEvent(inv->cpos()-ARENA_CENTRE, shieldChunk,
350 96, 1000 + 100*int(you.shield));
351 you.shield -= 1;
352 if (you.shield < 0 && !settings.invuln)
353 you.dead = true;
354 }
355 }
356
357 // check for leaving arena
358 if (((inv)->cpos() - ARENA_CENTRE).lengthsq() >=
359 ARENA_RAD*ARENA_RAD)
360 inv->die();
361 }
362
363 // check for collisions between invaders
364 // (note that this must come after updating all invaders, since we need
365 // the collision objects to be set)
366 for (std::vector<Invader*>::iterator it = invaders.begin();
367 it != invaders.end();
368 it++)
369 {
370 Invader* inv = *it;
371 if (inv->hitsInvaders() > 0)
372 {
373 Invader* hitInvader = NULL;
374 const float r = inv->hitsInvaders();
375 for (std::vector<Invader*>::iterator it2 = invaders.begin();
376 it2 != invaders.end();
377 it2++)
378 {
379 if (*it2 == inv)
380 continue;
381 if ((*it2)->collObj().circleIntersects(inv->cpos(), r))
382 {
383 hitInvader = *it2;
384 break;
385 }
386 }
387
388 if (hitInvader != NULL)
389 {
390 int damage = hitInvader->die();
391 if (hitInvader->dead())
392 {
393 soundEvents.newEvent(hitInvader->cpos() - ARENA_CENTRE,
394 invDieChunk,
395 96, 800+200*damage + int(100*gaussian()));
396 you.score += hitInvader->killScore();
397 }
398 inv->hit(damage);
399 }
400 }
401 }
402
403 for (std::vector<Shot>::iterator it = shots.begin();
404 it != shots.end();
405 it++)
406 {
407 float hitTime = -1;
408 Invader* hitInvader = NULL;
409
410 for (std::vector<Invader*>::iterator invit = invaders.begin();
411 invit != invaders.end();
412 invit++)
413 {
414 if (!(*invit)->hitsShots())
415 continue;
416
417 RelCartCoord v = it->vel;
418 float t = (*invit)->collObj().pointHits(it->pos, v, time);
419 if (t >= 0 && (hitTime == -1 || t < hitTime))
420 {
421 hitTime = t;
422 hitInvader = *invit;
423 }
424 }
425 for (std::vector<Node>::iterator nodeit = nodes.begin();
426 nodeit != nodes.end();
427 nodeit++)
428 {
429 if (nodeit->primed < 1)
430 continue;
431
432 RelCartCoord v = it->vel;
433 float t = nodeit->collObj().pointHits(it->pos, v, time);
434 if (t >= 0 && (hitTime == -1 || t < hitTime))
435 {
436 hitTime = t;
437 hitInvader = &*nodeit;
438 }
439 }
440
441 if (hitInvader != NULL)
442 {
443 int damage = hitInvader->hit(it->weight);
444 if (hitInvader->dead())
445 {
446 soundEvents.newEvent(it->pos - ARENA_CENTRE, invDieChunk,
447 96, 800+200*damage + int(100*gaussian()));
448 you.score += hitInvader->killScore();
449 }
450 else
451 soundEvents.newEvent(it->pos - ARENA_CENTRE, invHitChunk,
452 96, 800+200*damage + int(100*gaussian()));
453 it->hit(damage);
454 if (Shot::is_dead(*it))
455 deadShots = true;
456 }
457
458 it->update(time);
459 RelCartCoord d = it->pos - ARENA_CENTRE;
460 if (d.lengthsq() >= ARENA_RAD*ARENA_RAD)
461 {
462 it->dead = 1;
463 deadShots = true;
464 }
465 }
466
467 // add newly spawned invaders
468 for (std::vector<Invader*>::iterator it = topush.begin();
469 it != topush.end();
470 it++)
471 {
472 invaders.push_back(*it);
473 }
474 }
475
handleGameInput(int time)476 void GameState::handleGameInput(int time)
477 {
478 bool keyRotLeft;
479 bool keyRotRight;
480 bool keyDeAim;
481 bool keyDeZoom;
482 bool keyShoot1;
483 bool keyShoot2;
484 bool keyShoot3;
485 bool keyShootPod;
486
487 if (!ai)
488 {
489 keyRotLeft = settings.keybindings[C_LEFT].isPressed();
490 keyRotRight = settings.keybindings[C_RIGHT].isPressed();
491 keyDeAim = settings.keybindings[C_DEAIM].isPressed();
492 keyDeZoom = settings.keybindings[C_DEZOOM].isPressed();
493 keyShoot1 = settings.keybindings[C_SHOOT_GREEN].isPressed();
494 keyShoot2 = settings.keybindings[C_SHOOT_YELLOW].isPressed();
495 keyShoot3 = settings.keybindings[C_SHOOT_RED].isPressed();
496 keyShootPod = settings.keybindings[C_SHOOT_POD].isPressed();
497 }
498 else
499 {
500 keyRotLeft = ai->keys & ai->K_LEFT;
501 keyRotRight = ai->keys & ai->K_RIGHT;
502 keyDeAim = ai->keys & ai->K_DEAIM;
503 keyDeZoom = ai->keys & ai->K_DEZOOM;
504 keyShoot1 = ai->keys & ai->K_SHOOT1;
505 keyShoot2 = ai->keys & ai->K_SHOOT2;
506 keyShoot3 = ai->keys & ai->K_SHOOT3;
507 keyShootPod = ai->keys & ai->K_POD;
508 }
509
510 if (keyRotLeft || keyRotRight)
511 {
512 if (keyRotLeft)
513 you.aim.angle += time*.015*settings.turnRateFactor*you.aimAccuracy();
514 else
515 you.aim.angle += time*-.015*settings.turnRateFactor*you.aimAccuracy();
516 you.aim.dist = std::max(AIM_MIN, (float)(you.aim.dist-time*.04));
517 }
518 if (keyDeAim)
519 you.aim.dist = std::max(AIM_MIN, (float)(you.aim.dist-time*.1));
520 else if (keyDeZoom)
521 {
522 you.aim.dist = std::max(AIM_MIN, (float)(you.aim.dist-time*.04));
523 zoomdist = std::max(ZOOM_MIN, (float)(zoomdist-time*.2));
524 }
525 if (!(keyDeAim || keyDeZoom || keyRotLeft || keyRotRight))
526 {
527 // exponential decay from AIM_MIN to AIM_MAX:
528 // dist = max - (max-min)*2^{-time*rate}
529 // so d(dist)/d(time) is proportional to (max-dist).
530 // In fact, we define:
531 // max - dist = min(max, (max-dist_0) * exp(-time*aimRate))
532 const float aimRate = youHaveNode(NODEC_PURPLE) ? 0.0006 : 0.0004;
533 you.aim.dist = std::min(AIM_MAX,
534 (float)(you.aim.dist + (AIM_MAX-you.aim.dist)*time*aimRate));
535 }
536
537 float mintheta = 4;
538 for (std::vector<Node>::iterator it = nodes.begin();
539 it != nodes.end();
540 it++)
541 {
542 Angle relAngle = you.aim.angle - it->pos.angle;
543 const float dtheta = std::min(float(relAngle), 4.0f-relAngle);
544 if (it->status != NODEST_DESTROYED &&
545 dtheta < mintheta)
546 {
547 mintheta = dtheta;
548 targettedNode = &*it;
549 }
550 }
551 if (mintheta > 0.5)
552 targettedNode = NULL;
553
554 you.shootTimer -= time;
555 you.podTimer -= time;
556
557 if ((keyShoot1 || keyShoot2 || keyShoot3)
558 && you.shootTimer <= 0)
559 {
560 int weight=0;
561 bool super;
562 if (keyShoot3 &&
563 you.shootHeat < you.shootMaxHeat - shotHeat(2))
564 {
565 weight = 3;
566 super = youHaveNode(NODEC_RED);
567 }
568 else if (keyShoot2 &&
569 you.shootHeat < you.shootMaxHeat - shotHeat(1))
570 {
571 weight = 2;
572 super = youHaveNode(NODEC_YELLOW);
573 }
574 else if (keyShoot1 &&
575 you.shootHeat < you.shootMaxHeat - shotHeat(0))
576 {
577 weight = 1;
578 super = youHaveNode(NODEC_GREEN);
579 }
580 if (weight > 0)
581 {
582 float noise = gaussian()*you.aimAccuracy();
583
584 Shot shot( ARENA_CENTRE,
585 RelPolarCoord(you.aim.angle+noise,
586 0.1+0.05*(3-weight) + super*0.05),
587 weight, super);
588 // we're generous, and assume that the command to shoot was given
589 // just after the last update, and the shot was fired as soon as
590 // the heat became low enough:
591 shot.update(std::min(time,
592 std::min(
593 (you.shootMaxHeat - you.shootHeat -
594 shotHeat(weight-1))/you.shootCoolrate,
595 -you.shootTimer)));
596
597 shots.push_back(shot);
598
599 const int pitch = 900+weight*100 + int(40*gaussian());
600 soundEvents.newEvent(shot.pos-ARENA_CENTRE, shotChunk,
601 32, pitch);
602 if (super)
603 soundEvents.newEvent(shot.pos-ARENA_CENTRE, shotChunk,
604 32, pitch*3/4);
605
606 you.shootHeat += shotHeat(weight-1);
607 you.shootTimer = shotDelay(weight-1);
608
609 //you.aim.angle += weight*noise/4;
610
611 you.aim.dist = std::max(AIM_MIN,
612 (float)(you.aim.dist-weight*6));
613 }
614 }
615 if (keyShootPod &&
616 targettedNode != NULL &&
617 you.podTimer <= 0 &&
618 you.shootHeat < you.shootMaxHeat - shotHeat(3))
619 {
620 const bool super = youHaveNode(NODEC_BLUE);
621 invaders.push_back(new CapturePod(targettedNode,
622 RelPolarCoord(you.aim.angle, 5),
623 super));
624 you.podTimer = shotDelay(3);
625 you.shootHeat += shotHeat(3);
626 you.doneLaunchedPod = true;
627
628 const int pitch = 1500 + int(40*gaussian());
629 soundEvents.newEvent(0, shotChunk,
630 32, pitch);
631 if (super)
632 soundEvents.newEvent(0, shotChunk,
633 32, pitch*3/4);
634 }
635
636 you.shootTimer = std::max(0, you.shootTimer);
637 you.podTimer = std::max(0, you.podTimer);
638 }
639
handleFreeViewInput(int time)640 void GameState::handleFreeViewInput(int time)
641 {
642 // "free view mode": not useful or anything, but kind of fun
643
644 static bool dirKeys[4];
645 dirKeys[0] = settings.keybindings[C_DEZOOM].isPressed() ||
646 settings.keybindings[C_M_UP].isPressed();
647 dirKeys[1] = settings.keybindings[C_LEFT].isPressed() ||
648 settings.keybindings[C_M_LEFT].isPressed();
649 dirKeys[2] = settings.keybindings[C_DEAIM].isPressed() ||
650 settings.keybindings[C_M_DOWN].isPressed();
651 dirKeys[3] = settings.keybindings[C_RIGHT].isPressed() ||
652 settings.keybindings[C_M_RIGHT].isPressed();
653 bool keyZoomIn = settings.keybindings[C_SHOOT_GREEN].isPressed();
654 bool keyZoomOut = settings.keybindings[C_SHOOT_YELLOW].isPressed();
655 bool keyRotLeft = settings.keybindings[C_SHOOT_RED].isPressed();
656 bool keyRotRight = settings.keybindings[C_SHOOT_POD].isPressed();
657
658 const float baseZoom = (float)screenGeom.rad / (float)ARENA_RAD;
659 const float maxZoom = baseZoom * 32;
660 const float minZoom = baseZoom;
661 bool moved = false;
662 for (int i = 0; i < 4; i++)
663 if (dirKeys[i])
664 {
665 moved = true;
666 freeView.centre += RelPolarCoord(i-freeView.angle,
667 0.3*time*baseZoom/freeView.zoom);
668 }
669
670 // half a second of zooming doubles/halves the zoom
671 if (keyZoomOut)
672 freeView.zoom -= freeView.zoom * time * 0.0014;
673 if (keyZoomIn)
674 freeView.zoom += freeView.zoom * time * 0.0014;
675
676 if (keyRotLeft)
677 freeView.angle += 0.00075*time;
678 if (keyRotRight)
679 freeView.angle -= 0.00075*time;
680
681 RelPolarCoord freeViewCentre = freeView.centre - ARENA_CENTRE;
682 if (moved)
683 freeView.zoom = std::max(freeView.zoom,
684 (float)screenGeom.rad / ((float)ARENA_RAD - freeViewCentre.dist));
685 freeView.zoom = std::min(maxZoom, std::max(minZoom, freeView.zoom));
686 freeViewCentre.dist = std::min(freeViewCentre.dist,
687 (float)ARENA_RAD - ((float)screenGeom.rad / freeView.zoom));
688 freeView.centre = ARENA_CENTRE + freeViewCentre;
689 }
690
updateZoom(int time)691 void GameState::updateZoom(int time)
692 {
693 float targetZoomdist = you.aim.dist;
694 if (targetZoomdist < 0)
695 targetZoomdist = 0;
696 if (targetZoomdist > ZOOMDIST_MAX)
697 targetZoomdist = ZOOMDIST_MAX;
698 if (end)
699 targetZoomdist = 0;
700 if (time >= 200)
701 zoomdist = targetZoomdist;
702 else
703 zoomdist += (targetZoomdist-zoomdist)*time/200;
704
705 if (end == END_DEAD || end == END_EXTRACTED && zoomdist < 1)
706 {
707 freeViewMode = true;
708 freeView = View(ARENA_CENTRE,
709 (float)screenGeom.rad / (float)ARENA_RAD, -you.aim.angle);
710 }
711 }
712
evilAI(int time)713 void GameState::evilAI(int time)
714 {
715 invaderCooldown -= time;
716 if (end != END_WIN &&
717 invaderCooldown <= 0 &&
718 rani(invaderRate/3) <= time)
719 {
720 // it's about time to spawn a new invader
721 Invader* p_inv = NULL;
722 int cost = 0;
723 while (cost == 0)
724 {
725 //int type = rani(5) == 0 ? 3 : rani(3);
726 int type = rani(4);
727 if (mutilationWave > -1)
728 type = 3;
729 RelPolarCoord pos;
730 int ds;
731 switch (type)
732 {
733 case 0: case 1: case 2:
734 {
735 bool super = evilHasNode(
736 type == 0 ? NODEC_RED :
737 type == 1 ? NODEC_YELLOW :
738 NODEC_GREEN);
739 cost = 1;
740 pos = RelPolarCoord(ranf()*4, ARENA_RAD-20);
741 ds = super ? rani(9)-4 : rani(5)-2;
742 switch (type)
743 {
744 case 0: p_inv = new EggInvader(pos, ds, super);
745 break;
746 case 1: p_inv = new KamikazeInvader(pos, ds,
747 super); break;
748 case 2: p_inv = new SplittingInvader(pos, ds,
749 super); break;
750 }
751 break;
752 }
753 case 3:
754 {
755 // blue meanies cost 3 times as much to Evil as
756 // normal invaders. In order to keep the average rate
757 // of normal invaders constant, this cost is charged
758 // even if, due to lack of targets, no meanie is
759 // actually spawned.
760 cost = 3;
761
762 std::vector<Node*> possibleTargets;
763 for (std::vector<Node>::iterator it = nodes.begin();
764 it != nodes.end();
765 it++)
766 {
767 if ((it->status == NODEST_NONE ||
768 it->status == NODEST_YOU) &&
769 it->targettingInfester == NULL)
770 possibleTargets.push_back(&*it);
771 }
772 if (possibleTargets.size() > 0)
773 {
774 const int i = rani(possibleTargets.size());
775 p_inv = new InfestingInvader(possibleTargets[i],
776 evilHasNode(NODEC_BLUE));
777 }
778 }
779 break;
780 case 4:
781 // FoulEggLayingInvader - unused
782 cost = 1;
783 pos = RelPolarCoord(ranf()*4, ARENA_RAD-(20+rani(5)*10));
784 ds = rani(5)-2;
785 p_inv = new FoulEggLayingInvader(pos, ds);
786 break;
787 }
788 }
789 if (p_inv)
790 invaders.push_back(p_inv);
791 invaderCooldown += invaderRate * cost;
792 }
793
794 if (evilHasNode(NODEC_PURPLE) &&
795 rani(7500) < time)
796 {
797 for (std::vector<Invader*>::iterator it = invaders.begin();
798 it != invaders.end();
799 it++)
800 (*it)->dodge();
801 }
802 }
803
cleanup()804 void GameState::cleanup()
805 {
806 if (deadShots)
807 {
808 shots.erase(remove_if(shots.begin(),
809 shots.end(), Shot::is_dead),
810 shots.end());
811 }
812
813 for (std::vector<Invader*>::iterator it = invaders.begin();
814 it != invaders.end();
815 it++)
816 {
817 if ((*it)->dead())
818 {
819 (*it)->onDeath();
820 delete *it;
821 (*it) = NULL;
822 }
823 }
824 invaders.erase(remove_if(invaders.begin(),
825 invaders.end(), isNullInvp),
826 invaders.end());
827 }
828
draw(SDL_Surface * surface)829 void GameState::draw(SDL_Surface* surface)
830 {
831 View view;
832 View boundView;
833
834 if (!freeViewMode)
835 {
836 const RelPolarCoord d(you.aim.angle, zoomdist);
837
838 const View zoomView(ARENA_CENTRE + d,
839 (float)screenGeom.rad/((float)ARENA_RAD-zoomdist),
840 settings.rotatingView ? -d.angle : 0);
841
842 const View outerView(ARENA_CENTRE,
843 (float)screenGeom.rad/(float)ARENA_RAD,
844 settings.rotatingView ? -d.angle : 0);
845
846 view = settings.zoomEnabled ? zoomView : outerView;
847 boundView = zoomView;
848
849 if (settings.zoomEnabled)
850 {
851 ((settings.useAA == AA_FORCE) ? aacircleColor : circleColor)
852 (surface, screenGeom.centre.x, screenGeom.centre.y,
853 screenGeom.rad, 0x505050ff);
854 }
855 else
856 {
857 Circle(zoomView.centre, ARENA_RAD-zoomdist,
858 0x505050ff).draw(surface, view, NULL, true);
859 }
860 }
861 else
862 {
863 boundView = view = freeView;
864 boundView.zoom /= 3;
865
866 ((settings.useAA == AA_FORCE) ? aacircleColor : circleColor)
867 (surface, screenGeom.centre.x, screenGeom.centre.y,
868 screenGeom.rad, 0x505050ff);
869 }
870
871 drawIndicators(surface, view);
872 drawGrid(surface, view);
873 drawTargettingLines(surface, view);
874 drawObjects(surface, view, &boundView);
875 drawNodeTargetting(surface, view);
876 }
877
drawGrid(SDL_Surface * surface,const View & view)878 void GameState::drawGrid(SDL_Surface* surface, const View& view)
879 {
880 Circle(ARENA_CENTRE, ARENA_RAD,
881 0x808080ff).draw(surface, view, NULL, true);
882
883 if (settings.showGrid)
884 {
885 for (int i=1; i<6; i++)
886 Circle(ARENA_CENTRE, i*ARENA_RAD/6,
887 0x30303000 + (i%2==0)*0x08080800 + 0xff
888 ).draw(surface, view, NULL, true);
889 for (int i=0; i<12; i++)
890 Line(ARENA_CENTRE, ARENA_CENTRE +
891 RelPolarCoord(i*4.0/12, ARENA_RAD),
892 0x30303000 + (i%2==0)*0x08080800 + 0xff
893 ).draw(surface, view, NULL, true);
894 }
895 }
896
drawTargettingLines(SDL_Surface * surface,const View & view)897 void GameState::drawTargettingLines(SDL_Surface* surface, const View& view)
898 {
899 if (!you.dead)
900 {
901 const Uint32 aimColour =
902 (ai) ? (youHaveNode(NODEC_PURPLE) ? 0x00010100 : 0x00010000) :
903 youHaveNode(NODEC_PURPLE) ? 0x01000100 : 0x01000000;
904
905 for (int dir = -1; dir < 3; dir+=2)
906 Line(ARENA_CENTRE, ARENA_CENTRE +
907 RelPolarCoord(you.aim.angle + dir*you.aimAccuracy(),
908 ARENA_RAD),
909 aimColour * 0x80 + 0xff).draw(surface, view, NULL, true);
910
911 if (fabsf(you.aimAccuracy()) <= .45)
912 for (int dir = -1; dir < 3; dir+=2)
913 Line(ARENA_CENTRE, ARENA_CENTRE +
914 RelPolarCoord(you.aim.angle + dir*2*you.aimAccuracy(),
915 ARENA_RAD),
916 aimColour * 0x50 + 0xff).draw(surface, view, NULL, true);
917 }
918 }
919
drawNodeTargetting(SDL_Surface * surface,const View & view)920 void GameState::drawNodeTargetting(SDL_Surface* surface, const View& view)
921 {
922 if (!you.dead && targettedNode != NULL)
923 {
924 const Uint32 c = ( (you.shootHeat < you.shootMaxHeat - shotHeat(3)) ?
925 0xff000000 : 0xd0600000 ) + ( (you.podTimer <= 0) ?
926 0xff : 0x60);
927 const CartCoord p = targettedNode->cpos();
928 const float r = targettedNode->radius;
929 Line(p + RelCartCoord(-9*r/5, 0), p + RelCartCoord(-7*r/5, 0),
930 c).draw(surface, view, NULL);
931 Line(p + RelCartCoord(9*r/5, 0), p + RelCartCoord(7*r/5, 0),
932 c).draw(surface, view, NULL);
933 Line(p + RelCartCoord(0, -9*r/5), p + RelCartCoord(0, -7*r/5),
934 c).draw(surface, view, NULL);
935 Line(p + RelCartCoord(0, 9*r/5), p + RelCartCoord(0, 7*r/5),
936 c).draw(surface, view, NULL);
937 }
938 }
939
drawObjects(SDL_Surface * surface,const View & view,View * boundView)940 void GameState::drawObjects(SDL_Surface* surface, const View& view,
941 View* boundView)
942 {
943 you.draw(surface, view, NULL);
944
945 for (std::vector<Shot>::iterator it = shots.begin();
946 it != shots.end();
947 it++)
948 it->draw(surface, view, boundView);
949
950 for (std::vector<Invader*>::iterator it = invaders.begin();
951 it != invaders.end();
952 it++)
953 (*it)->draw(surface, view, boundView);
954
955 for (std::vector<Node>::iterator it = nodes.begin();
956 it != nodes.end();
957 it++)
958 it->draw(surface, view, boundView);
959
960 if (mutilationWave > 0)
961 Circle(ARENA_CENTRE, mutilationWave, 0x00ffffff).draw(surface, view, NULL);
962 else if (extracted > extractPreMutCutoff && extracted < extractMax)
963 Circle(ARENA_CENTRE, ARENA_RAD,
964 0x00ffff00 + (0x4f + 0x80*(
965 ((int)extracted - extractPreMutCutoff) /
966 (extractMax - extractPreMutCutoff)) +
967 (int)(0x30 * preMutilationPhase.sinf()))
968 ).draw(surface, view, NULL);
969 }
970
971 // approxAtan2Frac: approximates atan2(y,x)*(6/PI)-1
972 // (being the linear function of atan2(y,x) which is 0 at PI/6 and 1 at PI/3)
approxAtan2Frac(int y,int x)973 float approxAtan2Frac(int y, int x)
974 {
975 static const int N = 10;
976 static const float left=0.5, right=2.0;
977 static float z0[N];
978 static float t0[N], t1[N];
979 static const float halfDist = (right-left)/(2*(N-1));
980 static bool preCalced=false;
981
982 if (!preCalced)
983 {
984 // calculate first two terms of the Taylor expansion around some
985 // values of z spaced uniformly along the interval
986 for (int i = 0; i < N; i++)
987 {
988 z0[i] = left + (right-left)*i/(N-1);
989
990 t0[i] = atan(z0[i])*(6.0/PI)-1;
991 t1[i] = (1.0/(1+z0[i]*z0[i]))*(6.0/PI);
992 }
993 preCalced = true;
994 }
995
996 float z = float(y)/x;
997
998 // use the precalculated linear approximation around the closest z value
999 for (int i = 0; i < N; i++)
1000 if ( i == N-1 || z < z0[i] + halfDist )
1001 return t0[i] + t1[i]*(z-z0[i]);
1002
1003 return 0; // won't happen
1004
1005 // cubic approximation to atan(z) around z=1:
1006 // return PI/4 + (z-1)/2 - (z-1)*(z-1)/4 + (z-1)*(z-1)*(z-1)/12;
1007 }
1008
drawIndicators(SDL_Surface * surface,const View & view)1009 void GameState::drawIndicators(SDL_Surface* surface, const View& view)
1010 {
1011 // heat, shield and extraction indicators:
1012 Uint32 colour;
1013 for (int x = screenGeom.rad/2;
1014 x <= 866*screenGeom.rad/1000 + 15;
1015 x++)
1016 {
1017 const int xsq = x*x;
1018 int rsq;
1019 for (int y = int(sqrt(screenGeom.indicatorRsqLim1 - xsq));
1020 (rsq = xsq + y*y) <= screenGeom.indicatorRsqLim4;
1021 y++)
1022 {
1023 if (rsq < screenGeom.indicatorRsqLim1)
1024 continue;
1025 const float frac = approxAtan2Frac(y,x);
1026 if (frac >= 0 && frac <= 1)
1027 {
1028 if (rsq <= screenGeom.indicatorRsqLim2)
1029 {
1030 // decay towards the edges, for prettiness
1031 const int decay = std::min(255,
1032 std::min(rsq - screenGeom.indicatorRsqLim1,
1033 screenGeom.indicatorRsqLim2 - rsq)/2);
1034 int intensity;
1035
1036 // heat
1037 intensity =
1038 you.shootHeat > you.shootMaxHeat*frac ? 55+int(200*frac) :
1039 (frac >= 0.98 ? int(5000*(frac-0.98)) : 0) + (
1040 35 );
1041 colour = 0x01000000 * intensity + decay;
1042
1043 pixelColor(surface, screenGeom.centre.x - x,
1044 screenGeom.centre.y - y, colour);
1045
1046 // extraction
1047 if (extracted <= extractPreMutCutoff)
1048 intensity =
1049 extracted > extractPreMutCutoff*frac ? 55+int(200*frac) :
1050 (frac >= 0.98 ? int(5000*(frac-0.98)) : 0) + (
1051 evilHasNode(NODEC_CYAN) ? 55 : 35 );
1052 else
1053 intensity =
1054 ((extracted - extractPreMutCutoff) >
1055 (extractMax - extractPreMutCutoff)*frac) ?
1056 85+int(170*frac) :
1057 (frac >= 0.98 ? int(5000*(frac-0.98)) : 0) + (
1058 evilHasNode(NODEC_CYAN) ? 85 : 60 );
1059 colour = 0x00010100 * intensity + decay;
1060
1061 pixelColor(surface, screenGeom.centre.x + x,
1062 screenGeom.centre.y - y, colour);
1063 }
1064 else if (rsq < screenGeom.indicatorRsqLim3)
1065 {
1066 // shade between heat and shield indicators
1067 pixelColor(surface, screenGeom.centre.x - x,
1068 screenGeom.centre.y - y, 0xa0);
1069 }
1070 else
1071 {
1072 const int decay = std::min(255,
1073 std::min(rsq - screenGeom.indicatorRsqLim3,
1074 screenGeom.indicatorRsqLim4 - rsq)/2);
1075 int intensity;
1076
1077 // shield
1078 const int i = int(frac*4);
1079 const Uint32 baseColour =
1080 (i == 0) ? 0x01000000 :
1081 (i == 1) ? 0x01010000 :
1082 (i == 2) ? 0x00010000 :
1083 0x00010100;
1084
1085 intensity =
1086 (you.shield > frac*4) ? 55+(int(4*200*frac))%200 :
1087 youHaveNode(NODEC_CYAN) ? 55 :
1088 35;
1089
1090 colour =
1091 baseColour * intensity + decay;
1092
1093 pixelColor(surface, screenGeom.centre.x - x,
1094 screenGeom.centre.y - y, colour);
1095 }
1096 }
1097 }
1098 }
1099
1100 // shot indicators
1101 static const double shotIndicatorCos = cos(PI/6-PI/120);
1102 static const double shotIndicatorSin = sin(PI/6-PI/120);
1103 for (int i = 0; i < 4; i++)
1104 {
1105 const float d = screenGeom.rad + 3 + 5*i;
1106 const float x = shotIndicatorCos * d;
1107 const float y = shotIndicatorSin * d;
1108 const View shotView(CartCoord(x,-y), 1, 0);
1109 const bool super = youHaveShotNode(i);
1110
1111
1112 if (you.shootHeat >= you.shootMaxHeat - shotHeat(i))
1113 continue;
1114
1115 if (i == 3)
1116 {
1117 // capture pod
1118 if (you.podTimer <= 0)
1119 CapturePod(NULL, RelPolarCoord(0,0), super).draw(surface, shotView, NULL);
1120 }
1121 else
1122 {
1123 const int weight = i+1;
1124 Shot( CartCoord(0,0),
1125 RelPolarCoord(-0.3,
1126 0.1+0.05*(3-weight) + super*0.04),
1127 weight, super).draw(surface, shotView, NULL);
1128 }
1129 }
1130
1131 // node possession indicators
1132 static const double nodeIndicatorCos[6] = {
1133 cos(PI/6), cos(PI/6 + PI/36), cos(PI/6 + 2*PI/36),
1134 cos(PI/6 + 3*PI/36), cos(PI/6 + 4*PI/36), cos(PI/6 + 5*PI/36) };
1135 static const double nodeIndicatorSin[6] = {
1136 sin(PI/6), sin(PI/6 + PI/36), sin(PI/6 + 2*PI/36),
1137 sin(PI/6 + 3*PI/36), sin(PI/6 + 4*PI/36), sin(PI/6 + 5*PI/36) };
1138 const float dist = screenGeom.rad + 3 + 20 + 10;
1139
1140 int youNodeCount = 0;
1141 int evilNodeCount = 0;
1142 for (std::vector<Node>::iterator it = nodes.begin();
1143 it != nodes.end();
1144 it++)
1145 {
1146 if (it->status == NODEST_YOU)
1147 {
1148 const View nodeView(CartCoord(
1149 nodeIndicatorCos[youNodeCount] * dist,
1150 - nodeIndicatorSin[youNodeCount] * dist),
1151 1, 0);
1152 Node(RelPolarCoord(0,0), 0, it->nodeColour).draw(surface, nodeView, NULL);
1153 youNodeCount++;
1154 }
1155 else if (it->status == NODEST_EVIL)
1156 {
1157 const View nodeView(CartCoord(
1158 - nodeIndicatorCos[evilNodeCount] * dist,
1159 - nodeIndicatorSin[evilNodeCount] * dist),
1160 1, 0);
1161 Node(RelPolarCoord(0,0), 0, it->nodeColour).draw(surface, nodeView, NULL);
1162 evilNodeCount++;
1163 }
1164 }
1165 }
1166
rateOfRating(int rating)1167 int GameState::rateOfRating(int rating)
1168 {
1169 static int rates[] = {
1170 4000, 3200, 2500, 2250, 2000, 1750, 1500, 1300, 1150};
1171 if (rating < 1)
1172 return -1;
1173 if (rating > 9)
1174 // Elite+, Elite++, ...
1175 return (int)(1000.0*pow(0.9, (double)(rating-9)));
1176 return rates[rating-1];
1177 }
1178
setRating()1179 void GameState::setRating()
1180 {
1181 double newRating = settings.requestedRating > 0 ?
1182 settings.requestedRating : config.rating[speed];
1183 if (newRating < 1.0)
1184 newRating = 1.0;
1185
1186 rating = newRating;
1187
1188 double effRating = std::max(1.0, rating - 0.5*speed);
1189
1190 int i = (int) effRating;
1191 int r1 = rateOfRating(i);
1192 int r2 = rateOfRating(i+1);
1193
1194 invaderRate = (r1 + (int)((effRating-i)*(r2-r1)));
1195 }
1196
ratingString(int rating)1197 const char* ratingString(int rating)
1198 {
1199 // Recognise these?
1200 switch (rating)
1201 {
1202 case 1: return "Harmless";
1203 case 2: return "Mostly Harmless";
1204 case 3: return "Poor";
1205 case 4: return "Average";
1206 case 5: return "Above Average";
1207 case 6: return "Competent";
1208 case 7: return "Dangerous";
1209 case 8: return "Deadly";
1210 case 9: return "Elite";
1211 case 10: return "Elite+";
1212 case 11: return "Elite++";
1213 case 12: return "Elite+++";
1214 default: return "Unrated";
1215 };
1216 }
1217
getHint()1218 const char* GameState::getHint()
1219 {
1220 if (rating > 5.0 || settings.invuln)
1221 return "";
1222
1223 if (end != END_WIN)
1224 {
1225 // Let's see if we can work out what they need to know...
1226 if (you.score == 0)
1227 return "Hint: use keys '1', '2', '3' to shoot.";
1228
1229 if (you.score >= 10)
1230 {
1231 if (!you.doneLaunchedPod)
1232 return "Hint: use key '4' to capture Nodes.";
1233
1234 bool destroyed = false;
1235 for (std::vector<Node>::iterator it = nodes.begin();
1236 it != nodes.end();
1237 it++)
1238 if (it->status == NODEST_DESTROYED)
1239 {
1240 destroyed = true;
1241 break;
1242 }
1243
1244 if (!destroyed && (END_EXTRACTED || you.score >= 30))
1245 return "Hint: hit filled nodes with '3'; destroy 4 to win!";
1246 }
1247 }
1248
1249 // Either they won, or they lost for no obvious reason. Maybe we should
1250 // tell them about some things they might not have thought of...
1251 switch (int(rating*10)%6)
1252 {
1253 case 0:
1254 return "Hint: Nodes help whoever has them.";
1255 case 1:
1256 return "Hint: Your shield charges if you stop shooting.";
1257 case 2:
1258 return "Hint: Use Down to turn fast, Up to glance back.";
1259 case 3:
1260 return "Hint: Pods ('4') can kill even shielded blue Blobs.";
1261 case 4:
1262 return "Hint: Green shots won't hurt blue Blobs.";
1263 case 5:
1264 return "Hint: Don't let Evil fill the cyan bar.";
1265
1266 default:
1267 return "Hint: THIS IS A BUG.";
1268 }
1269 }
1270