1 /** 2 * Game objects and logic 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <ae@cy.md> 12 */ 13 14 module ae.demo.pewpew.objects; 15 16 import std.algorithm.comparison; 17 import std.random; 18 import std.math; 19 20 import ae.utils.container.listnode; 21 import ae.utils.math; 22 import ae.utils.geometry; 23 import ae.utils.graphics.color; 24 import ae.utils.graphics.draw; 25 import ae.utils.graphics.image; 26 27 __gshared: 28 29 enum Plane 30 { 31 Logic, 32 BG0, 33 BG1, 34 BG2, 35 Ship, 36 PlasmaOrbs, 37 Enemies, 38 Particles, 39 Torpedoes, 40 Explosions, 41 Max 42 } 43 44 enum STAR_LAYERS = 3; 45 46 DListContainer!GameEntity[Plane.Max] planes; 47 bool initializing = true; 48 49 alias L16 COLOR; 50 //alias G8 COLOR; // less precise, but a bit faster 51 52 Image!COLOR canvas; 53 float cf(float x) { assert(canvas.w == canvas.h); return x*canvas.w; } 54 int ci(float x) { assert(canvas.w == canvas.h); return cast(int)(x*canvas.w); } 55 int cbound(int x) { return bound(x, 0, cast(int)canvas.w); } 56 auto BLACK = COLOR(0); 57 auto WHITE = COLOR(COLOR.ChannelType.max); 58 59 bool up, down, left, right, fire; 60 61 bool useAnalog; float analogX, analogY; 62 63 float frand () { return uniform!`[)`( 0.0f, 1.0f); } 64 float frands() { return uniform!`()`(-1.0f, 1.0f); } 65 66 T ssqr(T)(T x) { return sqr(x) * sign(x); } 67 float frands2() { return ssqr(frands()); } 68 69 // Partial copy of ae.utils.graphics.draw.FixMath 70 // TODO proper fixed-point type? 71 private mixin template FixMath(ubyte coordinateBits = 16) 72 { 73 import ae.utils.meta : SignedBitsType, UnsignedBitsType; 74 75 alias fix = SignedBitsType!(COLOR.channelBits + coordinateBits); 76 alias COLOR.ChannelType frac; 77 78 fix tofix(T:int )(T x) { return cast(fix) (x<<COLOR.channelBits); } 79 fix tofix(T:float)(T x) { return cast(fix) (x*(1<<COLOR.channelBits)); } 80 frac fixfpart(fix x) { return cast(frac)x; } 81 frac tofracBounded(T:float)(T x) { return cast(frac) bound(tofix(x), 0, frac.max); } 82 } 83 mixin FixMath; 84 85 enum Sound 86 { 87 fire, 88 warpIn, 89 torpedoHit, 90 enemyFire, 91 explosion, 92 } 93 Sound[] sounds; 94 95 int score; 96 97 // ********************************************************* 98 99 class GameEntity 100 { 101 mixin DListLink; 102 Plane plane; 103 104 abstract void step(uint deltaTicks); 105 abstract void render(); 106 107 void add(Plane plane) 108 { 109 this.plane = plane; 110 planes[plane].pushBack(this); 111 } 112 113 void remove() 114 { 115 planes[plane].remove(this); 116 } 117 } 118 119 class Game : GameEntity 120 { 121 this() 122 { 123 add(Plane.Logic); 124 spawnParticles = new SpawnParticles(Plane.Particles); 125 foreach (layer; 0..STAR_LAYERS) 126 starFields[layer] = new StarField(cast(Plane)(Plane.BG0 + layer)); 127 torpedoParticles = new TorpedoParticles(Plane.Particles); 128 } 129 130 uint spawnTimer; 131 132 override void step(uint deltaTicks) 133 { 134 foreach (i; 0..deltaTicks) 135 { 136 auto z = frand(); 137 auto star = Star( 138 frand(), 0, 139 0.0001f + (1-z) * 0.00005f, 140 COLOR(fixfpart(tofix((1-z)*0.5f)))); 141 auto layer = cast(int)((1-z)*3); 142 starFields[layer].add(star); 143 } 144 if (!initializing && ship && !ship.dead && spawnTimer--==0) 145 { 146 new Thingy(); 147 spawnTimer = uniform(1500, 2500); 148 } 149 if (!initializing && planes[Plane.Ship].empty && planes[Plane.Enemies].empty && planes[Plane.PlasmaOrbs].empty && planes[Plane.Explosions].empty) 150 new Ship(); 151 } 152 153 override void render() {} 154 } 155 156 // ********************************************************* 157 158 class ParticleManager(Particle) : GameEntity 159 { 160 Particle* particles; 161 int particleCount; 162 163 void add(Particle particle) 164 { 165 if (particleCount == Particle.MAX) 166 return; 167 particles[particleCount++] = particle; 168 } 169 170 this(Plane plane) 171 { 172 particles = (new Particle[Particle.MAX]).ptr; 173 super.add(plane); 174 } 175 176 override void step(uint deltaTicks) 177 { 178 int i = 0; 179 while (i < particleCount) 180 with (particles[i]) 181 { 182 enum REMOVE = q{ particles[i] = particles[--particleCount]; }; 183 enum NEXT = q{ i++; }; 184 mixin(Particle.STEP); 185 } 186 } 187 188 override void render() 189 { 190 import std.parallelism; 191 foreach (ref particle; taskPool.parallel(particles[0..particleCount])) 192 with (particle) 193 { 194 mixin(Particle.RENDER); 195 } 196 } 197 } 198 199 /+ 200 class ParticleManager(Particle) : GameEntity 201 { 202 Particle[] particles; 203 204 void add(Particle particle) 205 { 206 particles ~= particle; 207 } 208 209 this(Plane plane) 210 { 211 super.add(plane); 212 } 213 214 override void step(uint deltaTicks) 215 { 216 for (int i=0; i<particles.length; i++) 217 with (particles[i]) 218 { 219 enum REMOVE = q{ particles = particles[0..i] ~ particles[i+1..$]; }; 220 enum NEXT = q{ }; 221 mixin(Particle.STEP); 222 } 223 } 224 225 override void render() 226 { 227 foreach (ref particle; particles) 228 with (particle) 229 { 230 mixin(Particle.RENDER); 231 } 232 } 233 } 234 +/ 235 236 struct Star 237 { 238 float x, y, vy; 239 COLOR color; 240 241 enum MAX = 1024*32; 242 243 enum STEP = 244 q{ 245 y += deltaTicks * vy; 246 if (y > 1f) 247 { mixin(REMOVE); } 248 else 249 { mixin(NEXT); } 250 }; 251 252 enum RENDER = 253 q{ 254 canvas.aaPutPixel(cf(x), cf(y), color); 255 }; 256 } 257 258 alias ParticleManager!Star StarField; 259 StarField[STAR_LAYERS] starFields; 260 261 // ********************************************************* 262 263 class GameObject : GameEntity 264 { 265 float x, y, vx=0, vy=0; 266 Shape!float[] shapes; /// shape coordinates are relative to x,y 267 bool dead; 268 269 enum DEATHBRAKES = 0.998f; 270 271 override void step(uint deltaTicks) 272 { 273 x += vx*deltaTicks; 274 y += vy*deltaTicks; 275 if (dead) 276 vx *= DEATHBRAKES, 277 vy *= DEATHBRAKES; 278 } 279 280 final void collideWith(Plane[] planeIndices...) 281 { 282 assert(!dead); 283 284 foreach (plane; planeIndices) 285 foreach (obj; planes[plane]) 286 { 287 auto enemy = cast(GameObject) obj; 288 if (enemy && !enemy.dead) 289 foreach (shape1; shapes) 290 { 291 if (shape1.kind == ShapeKind.none) continue; 292 shape1.translate(x, y); 293 foreach (shape2; enemy.shapes) 294 { 295 if (shape2.kind == ShapeKind.none) continue; 296 shape2.translate(enemy.x, enemy.y); 297 if (intersects(shape1, shape2)) 298 { 299 die(); 300 enemy.die(); 301 return; 302 } 303 } 304 } 305 } 306 } 307 308 void die() 309 { 310 remove(); 311 } 312 } 313 314 // ********************************************************* 315 316 class Ship : GameObject 317 { 318 float death = 0f, spawn = 0f; 319 bool spawning; 320 uint t; 321 322 enum SPAWN_START = 0.3f; 323 enum SPAWN_END = 0.3f; 324 325 this() 326 { 327 x = 0.5f; 328 y = 0.85f; 329 vx = vy = 0; 330 shapes ~= shape(rect(-0.040f, -0.020f, -0.028f, +0.040f)); // left wing 331 shapes ~= shape(rect(+0.040f, -0.020f, +0.028f, +0.040f)); // right wing 332 shapes ~= shape(rect(-0.008f, -0.040f, +0.008f, +0.030f)); // center hull 333 shapes ~= shape(rect(-0.030f, +0.020f, +0.030f, +0.024f)); // bridge 334 shapes ~= shape(circle(0, +0.020f, 0.020f)); // round section 335 add(Plane.Ship); 336 ship = this; 337 dead = spawning = true; 338 sounds ~= Sound.warpIn; 339 score = 0; 340 } 341 342 override void step(uint deltaTicks) 343 { 344 if (!dead) 345 { 346 const a = 0.000_001f; 347 const maxv = 0.000_500f; 348 349 if (useAnalog) 350 vx = analogX * maxv, 351 vy = analogY * maxv; 352 else 353 { 354 if (left) 355 vx = bound(vx-a*deltaTicks, -maxv, 0); 356 else 357 if (right) 358 vx = bound(vx+a*deltaTicks, 0, maxv); 359 else 360 vx = 0; 361 362 if (up) 363 vy = bound(vy-a*deltaTicks, -maxv, 0); 364 else 365 if (down) 366 vy = bound(vy+a*deltaTicks, 0, maxv); 367 else 368 vy = 0; 369 } 370 371 t += deltaTicks; 372 } 373 super.step(deltaTicks); 374 375 if (!dead) 376 { 377 if (x<0.05f || x>0.95f) vx = 0; 378 if (y<0.05f || y>0.95f) vy = 0; 379 x = bound(x, 0.05f, 0.95f); 380 y = bound(y, 0.05f, 0.95f); 381 382 static bool wasFiring; 383 //fire = t % 250 == 0; 384 if (fire && !wasFiring) 385 { 386 new Torpedo(-0.034f, -0.020f); 387 new Torpedo(+0.034f, -0.020f); 388 sounds ~= Sound.fire; 389 } 390 wasFiring = !!fire; 391 392 collideWith(Plane.Enemies, Plane.PlasmaOrbs); 393 } 394 else 395 if (spawning) 396 { 397 spawn += 0.0005f; 398 399 if (spawn < SPAWN_START+1f) 400 foreach (n; 0..5) 401 { 402 float px = frands()*0.050f; 403 spawnParticles.add(SpawnParticle( 404 x + px*1.7f + frands ()*0.010f, 405 spawnY() + frands2()*0.050f, 406 x + px, 407 )); 408 } 409 410 if (spawn >= SPAWN_START+1f+SPAWN_END) 411 spawning = dead = false; 412 } 413 else 414 { 415 death += (1f/2475f); 416 if (death > 1f) 417 remove(); 418 } 419 } 420 421 override void die() 422 { 423 new Explosion(this, 0.150f); 424 dead = true; 425 } 426 427 override void render() 428 { 429 enum Gray25 = COLOR.ChannelType.max / 4; 430 enum Gray75 = COLOR.ChannelType.max / 4 * 3; 431 432 void drawRect(float x0, float y0, float x1, float y1, COLOR color) 433 { 434 canvas.aaFillRect(cf(x+x0), cf(y+y0), cf(x+x1), cf(y+y1), color); 435 } 436 437 void drawRect2(Rect!float r) 438 { 439 r.sort(); 440 drawRect(r.x0 , r.y0 , r.x1 , r.y1 , COLOR(Gray25)); 441 drawRect(r.x0+0.002f, r.y0+0.002f, r.x1-0.002f, r.y1-0.002f, COLOR(Gray75)); 442 } 443 444 void drawCircle(Circle!float c, COLOR color) 445 { 446 canvas.softCircle(cf(x+c.x), cf(y+c.y), cf(c.r*0.7f), cf(c.r), color); 447 } 448 449 450 void warp(float x, float y, float r) 451 { 452 auto bgx0 = cbound(ci(x-r)); 453 auto bgy0 = cbound(ci(y-r)); 454 auto bgx1 = cbound(ci(x+r)); 455 auto bgy1 = cbound(ci(y+r)); 456 auto window = canvas.crop(bgx0, bgy0, bgx1, bgy1); 457 static Image!COLOR bg; 458 window.copy(bg); 459 auto cx = ci(x)-bgx0; 460 auto cy = ci(y)-bgy0; 461 procedural!((x, y) 462 { 463 xy_t dx = x-cx; 464 xy_t dy = y-cy; 465 xy_t sx = x; 466 xy_t sy = y; 467 float f = dist(dx, dy) / cx; 468 if (f < 1f && f > 0f) 469 { 470 float f2 = (1-f)*sqrt(sqrt(f)) + f*f; 471 assert(f2 < 1f); 472 sx = cx + cast(int)(dx / f * f2); 473 sy = cy + cast(int)(dy / f * f2); 474 } 475 return bg.safeGet(sx, sy, COLOR(0)); 476 })(window.w, window.h).blitTo(window); 477 } 478 479 if (spawning) 480 { 481 enum R = 0.15f; 482 warp(x, spawnY(), R * sqrt(sin(spawn/(SPAWN_START+1f+SPAWN_END)*PI))); 483 } 484 485 static Image!COLOR bg; 486 int bgx0, bgyS; 487 if (spawning) 488 { 489 bgx0 = ci(x-0.050f); 490 bgyS = ci(spawnY()); 491 canvas.crop(bgx0, bgyS, ci(x+0.050f), ci(y+0.050f)).copy(bg); 492 } 493 494 drawRect2 (shapes[0].rect); 495 drawRect2 (shapes[1].rect); 496 drawRect2 (shapes[2].rect); 497 drawRect (shapes[3].rect.tupleof, COLOR(Gray25)); 498 drawCircle(shapes[4].circle , COLOR(Gray75)); 499 500 if (spawning) 501 bg.blitTo(canvas, bgx0, bgyS); 502 } 503 504 float spawnY() 505 { 506 return y-0.050f + (0.100f * bound(spawn-SPAWN_START, 0f, 1f)); 507 } 508 } 509 510 Ship ship; 511 512 struct SpawnParticle 513 { 514 float x0, y0, x1, t=0f; 515 516 enum MAX = 1024*32; 517 518 enum STEP = 519 q{ 520 t += 0.002f; 521 if (t > 1f) 522 { mixin(REMOVE); } 523 else 524 { mixin(NEXT); } 525 }; 526 527 enum RENDER = 528 q{ 529 float y1 = ship.spawnY(); 530 float tt0 = sqr(sqr(t)); 531 float tt1 = min(1, tt0+0.15f); 532 float lx0 = x0 + tt0*(x1-x0); 533 float ly0 = y0 + tt0*(y1-y0); 534 float lx1 = x0 + tt1*(x1-x0); 535 float ly1 = y0 + tt1*(y1-y0); 536 //canvas.aaPutPixel(cf(x), cf(y), WHITE, tofracBounded(tt)); 537 canvas.aaLine(cf(lx0), cf(ly0), cf(lx1), cf(ly1), WHITE, tofracBounded(sqr(tt0))); 538 }; 539 } 540 541 alias ParticleManager!SpawnParticle SpawnParticles; 542 SpawnParticles spawnParticles; 543 544 // ********************************************************* 545 546 class Torpedo : GameObject 547 { 548 this(float dx, float dy) 549 { 550 this.x = ship.x + dx; 551 this.y = ship.y + dy; 552 this.vx = ship.vx; 553 this.vy = ship.vy - 0.000_550f; 554 shapes ~= Shape!float(Point!float(0, 0)); 555 add(Plane.Torpedoes); 556 } 557 558 int t; 559 560 override void step(uint deltaTicks) 561 { 562 super.step(deltaTicks); 563 if (y < -0.25f || x < 0 || x > 1) 564 return remove(); 565 566 //if ((t+=deltaTicks) % 1 == 0) 567 torpedoParticles.add(TorpedoParticle( 568 x, y, 569 frands()*0.000_010f, 570 frand ()*0.000_100f + 0.000_200f)); 571 572 collideWith(Plane.Enemies, Plane.PlasmaOrbs); 573 } 574 575 override void die() 576 { 577 remove(); 578 foreach (n; 0..500) 579 { 580 auto a = uniform(0, TAU); 581 torpedoParticles.add(TorpedoParticle(x, y, 582 frand()*cos(a)*0.000_300f, 583 frand()*sin(a)*0.000_300f - 0.000_100f, 584 0.003f)); 585 } 586 sounds ~= Sound.torpedoHit; 587 score += 10; 588 } 589 590 override void render() 591 { 592 canvas.aaPutPixel(cf(x), cf(y), WHITE); 593 } 594 } 595 596 struct TorpedoParticle 597 { 598 float x, y, vx, vy, s = 0.001f, t = 0; 599 600 enum MAX = 1024*64; 601 602 enum STEP = 603 q{ 604 x += vx; 605 y += vy; 606 t += s; 607 if (t >= 1) 608 mixin(REMOVE); 609 else 610 mixin(NEXT); 611 }; 612 613 enum RENDER = 614 q{ 615 canvas.aaPutPixel(cf(x), cf(y), WHITE, tofracBounded(1-t)); 616 }; 617 } 618 619 alias ParticleManager!TorpedoParticle TorpedoParticles; 620 TorpedoParticles torpedoParticles; 621 622 // ********************************************************* 623 624 class Enemy : GameObject 625 { 626 } 627 628 class ThingyPart : Enemy 629 { 630 float death = 0f; 631 632 override void render() 633 { 634 float r1 = shapes[0].circle.r; 635 float r0 = r1*(2f/3f); 636 637 if (!dead) 638 canvas.softCircle(cf(x), cf(y), cf(r0), cf(r1), WHITE); 639 else 640 { 641 canvas.softCircle(cf(x), cf(y), cf(r0), cf(r1), COLOR(tofracBounded(1-death))); 642 canvas.softCircle(cf(x), cf(y), cf(r0*death), cf(r1*death), BLACK); 643 } 644 } 645 } 646 647 class Thingy : ThingyPart 648 { 649 float a, va; 650 ThingySatellite[] satellites; 651 int charge; // max is 2000 652 653 this() 654 { 655 this.x = uniform(0f, 1f); 656 this.y = -0.060f; 657 this.vx = frands()*0.0003f; 658 this.vy = 0.0002f+frand()*0.0003f; 659 shapes ~= shape(circle(0, 0, 0.030f)); 660 va = 0.002f * sign(frands()); 661 a = frand()*TAU; 662 charge = 0; 663 auto numSatellites = uniform!"[]"(2, 2+(ship.t / 10_000)); 664 665 //charge=int.min; x=0.25f;vx=0;vy=0.0001f; static int c=2; numSatellites=c++; 666 667 foreach (i; 0..numSatellites) 668 satellites ~= new ThingySatellite(); 669 670 add(Plane.Enemies); 671 } 672 673 override void step(uint deltaTicks) 674 { 675 for (int n=0; n<deltaTicks; n++) 676 { 677 if (x < 0.020f) 678 vx = max(vx, -vx); 679 if (x > 0.980f) 680 vx = min(vx, -vx); 681 super.step(1); 682 if (y > 1.060f) 683 { 684 foreach (s; satellites) 685 if (!s.dead) 686 s.remove(); 687 remove(); 688 return; 689 } 690 a += va; 691 692 if (!dead) 693 { 694 foreach (s; satellites) 695 if (!s.dead) 696 charge++; 697 698 while (charge >= 2000) 699 { 700 charge -= 2000; 701 if (ship && !ship.dead) 702 new PlasmaOrb(this); 703 } 704 } 705 else 706 { 707 death += 0.002f; 708 if (death > 1) 709 remove(); 710 } 711 } 712 713 uint level = 0; 714 uint lastLevelCount = 1; 715 void arrange(ThingySatellite[] satellites) 716 { 717 foreach (i, s; satellites) 718 { 719 auto sd = 0.040f + 0.025f*level + s.death*0.020f; // satellite distance 720 auto sa = a + TAU*i/satellites.length + level*(TAU/lastLevelCount/2); 721 s.x = x + sd*cos(sa); 722 s.y = y + sd*sin(sa); 723 } 724 level++; 725 lastLevelCount = cast(uint)satellites.length; 726 } 727 728 if (satellites.length <= 8) 729 arrange(satellites[ 0.. $]); 730 else 731 if (satellites.length <= 12) 732 { 733 arrange(satellites[ 0.. 6]); 734 arrange(satellites[ 6.. $]); 735 } 736 else 737 if (satellites.length <= 24) 738 { 739 arrange(satellites[ 0.. 8]); 740 arrange(satellites[ 8.. $]); 741 } 742 else 743 if (satellites.length <= 32) 744 { 745 arrange(satellites[ 0.. 8]); 746 arrange(satellites[ 8..16]); 747 arrange(satellites[16.. $]); 748 } 749 else 750 if (satellites.length <= 48) 751 { 752 arrange(satellites[ 0.. 8]); 753 arrange(satellites[ 8..24]); 754 arrange(satellites[24.. $]); 755 } 756 else 757 if (satellites.length <= 64) 758 { 759 arrange(satellites[ 0.. 8]); 760 arrange(satellites[ 8..24]); 761 arrange(satellites[24..40]); 762 arrange(satellites[40.. $]); 763 } 764 else 765 { 766 arrange(satellites[ 0.. 8]); 767 arrange(satellites[ 8..24]); 768 arrange(satellites[24..48]); 769 arrange(satellites[48.. $]); 770 } 771 } 772 773 override void die() 774 { 775 dead = true; 776 foreach (s; satellites) 777 s.dead = true; 778 new Explosion(this, 0.060f); 779 } 780 } 781 782 class ThingySatellite : ThingyPart 783 { 784 this() 785 { 786 shapes ~= shape(circle(0, 0, 0.014f)); 787 add(Plane.Enemies); 788 } 789 790 override void step(uint deltaTicks) 791 { 792 if (dead) 793 { 794 death += 0.002f; 795 if (death > 1) 796 remove(); 797 } 798 } 799 800 override void die() 801 { 802 dead = true; 803 } 804 } 805 806 class PlasmaOrb : Enemy 807 { 808 int t; 809 float death = 0f; 810 811 this(Enemy parent) 812 { 813 this.x = parent.x; 814 this.y = parent.y; 815 shapes ~= shape(circle(0, 0, 0.008f)); 816 t = 0; 817 vx = ship.x - parent.x; 818 vy = ship.y - parent.y; 819 float f = 0.0002f/dist(this.vx, this.vy); 820 vx *= f; 821 vy *= f; 822 add(Plane.PlasmaOrbs); 823 sounds ~= Sound.enemyFire; 824 } 825 826 override void step(uint deltaTicks) 827 { 828 super.step(deltaTicks); 829 if (x<0 || x>1 || y<0 || y>1) 830 return remove(); 831 if (!dead) 832 { 833 t += deltaTicks; 834 shapes[0].circle.r = 0.008f+0.002f*sin(t/100f); 835 } 836 else 837 { 838 shapes[0].circle.r += 0.000_050f; 839 death += 0.005f; 840 if (death >= 1f) 841 remove(); 842 } 843 } 844 845 override void die() 846 { 847 dead = true; 848 if (ship && !ship.dead) 849 score -= 5; // These are worth less 850 } 851 852 override void render() 853 { 854 auto r = shapes[0].circle.r; 855 auto brightness = 0.75f+0.25f*(-sin(t/100f)); 856 if (!dead) 857 canvas.softCircle(cf(x), cf(y), cf(r-0.003f), cf(r), COLOR(tofracBounded(brightness))); 858 else 859 { 860 brightness *= 1-(death/2); 861 canvas.softRing(cf(x), cf(y), cf(death*r), cf(average(r, death*r)), cf(r), COLOR(tofracBounded(brightness))); 862 } 863 } 864 } 865 866 // ********************************************************* 867 868 class Explosion : GameObject 869 { 870 float size, maxt; 871 int t; 872 873 this(GameObject source, float size) 874 { 875 this.x = source.x; 876 this.y = source.y; 877 this.vx = source.vx; 878 this.vy = source.vy; 879 this.size = size; 880 this.t = 0; 881 this.maxt = size*16500; 882 this.dead = true; 883 add(Plane.Explosions); 884 } 885 886 override void step(uint deltaTicks) 887 { 888 t += deltaTicks; 889 super.step(deltaTicks); 890 891 if (t>maxt) 892 remove; 893 else 894 if (frand() < size) 895 { 896 float tf = t/maxt; // time factor 897 float ex = x+tf*size*frands(); 898 float ey = y+tf*size*frands(); 899 float ed = dist(x-ex, y-ey); 900 if (ed>size) return; 901 float r = 902 frand() * // random factor 903 (1-ed/size); // distance factor 904 new Splode(ex, ey, size/3*r); 905 } 906 } 907 908 override void die() { assert(0); } 909 910 override void render() 911 { 912 } 913 } 914 915 class Splode : GameEntity 916 { 917 float x, y, r, cr; 918 bool growing; 919 static uint index; 920 921 this(float x, float y, float r) 922 { 923 this.x = x; 924 this.y = y; 925 this.r = r; 926 this.cr = 0; 927 this.growing = true; 928 add(Plane.Explosions); 929 if (index++ % 32 == 0) 930 sounds ~= Sound.explosion; 931 } 932 933 override void step(uint deltaTicks) 934 { 935 const ra = 0.000_020f; 936 if (growing) 937 { 938 cr += ra; 939 if (cr>=r) 940 growing = false; 941 } 942 else 943 { 944 cr -= ra; 945 if (cr<=0) 946 { 947 remove(); 948 return; 949 } 950 } 951 } 952 953 override void render() 954 { 955 //std.stdio.writeln([x, y, cr]); 956 canvas.softCircle(cf(x), cf(y), max(0f, cf(cr)-1.5f), cf(cr), COLOR(tofracBounded(cr/r))); 957 } 958 }