1 module game.level; 2 3 import game.renderer; 4 import game.entity; 5 import game.math; 6 import game.terminal; 7 import game.game; 8 import spasm.rt.array; 9 import spasm.array; 10 import std.range : only; 11 import spasm.rt.memory; 12 import game.chain; 13 14 nothrow: 15 @safe: 16 17 enum Input { 18 None = 0, 19 Left = 1, 20 Right = 2, 21 Up = 4, 22 Down = 8, 23 Shoot = 16 24 } 25 26 Input getKey(string key) { 27 if (key == "ArrowLeft" || key == "a") 28 return Input.Left; 29 if (key == "ArrowRight" || key == "d") 30 return Input.Right; 31 if (key == "ArrowUp" || key == "w") 32 return Input.Up; 33 if (key == "ArrowDown" || key =="s") 34 return Input.Down; 35 return Input.None; 36 } 37 38 __gshared Terminal* gTerminal; 39 __gshared Game* gGame; 40 __gshared int current_level = 1; 41 42 struct Level { 43 nothrow: 44 enum Block {empty=0, floor=1, wall=2, player=3, cpu=4, sentry=5} 45 Player player; 46 ubyte[] data; 47 int cpus_rebooted = 0; 48 PointerArray!(Cpu*) cpus; 49 PointerArray!(PlayerPlasma*) pPlasma; 50 PointerArray!(Spider*) spiders; 51 PointerArray!(Health*) healths; 52 PointerArray!(Sentry*) sentries; 53 PointerArray!(SentryPlasma*) sPlasma; 54 PointerArray!(Particle*) particles; 55 PointerArray!(Explosion*) explosions; 56 void updateEntities(float tick, Input input, int mouseX, int mouseY) @trusted { 57 if (player.entity.dead || gGame.finished) 58 return; 59 player.update(this,tick,input,mouseX, mouseY); 60 collision(); 61 foreach(ref cpu; cpus[]) cpu.update(this, tick); 62 foreach(ref pp; pPlasma[]) pp.update(this, tick); 63 foreach(ref s; spiders[]) s.update(this, tick); 64 foreach(ref s; sentries[]) s.update(this, tick); 65 foreach(ref s; sPlasma[]) s.update(this, tick); 66 foreach(ref p; particles[]) p.update(this, tick); 67 foreach(ref e; explosions[]) e.update(this, tick); 68 } 69 void collision() { 70 static bool collides(ref Entity a, ref Entity b) { 71 return !(a.x >= b.x + 9 || 72 a.x + 9 <= b.x || 73 a.z >= b.z + 9 || 74 a.z + 9 <= b.z); 75 } 76 static void checkPlayerCollision(T)(T[] ts, ref Level level, ref Player player) { 77 foreach(ref t; ts) { 78 if (collides(player.entity,t.entity)) { 79 t.check(level, player); 80 break; 81 } 82 } 83 } 84 // check player against cpus, spiders, healths, sentryPlasmas 85 checkPlayerCollision(cpus[],this,player); 86 checkPlayerCollision(spiders[],this,player); 87 checkPlayerCollision(healths[],this,player); 88 checkPlayerCollision(sPlasma[],this,player); 89 90 // check plasma against spiders, sentries 91 outer: foreach(ref pp; pPlasma[]) { 92 foreach(ref s; spiders[]) { 93 if (collides(pp.entity,s.entity)) { 94 pp.check(this, s); 95 break outer; 96 } 97 } 98 foreach(ref s; sentries[]) { 99 if (collides(pp.entity,s.entity)) { 100 pp.check(this, s); 101 break outer; 102 } 103 } 104 } 105 } 106 void render(ref Renderer renderer, float tick) @trusted { 107 if (!gGame.finished && !player.entity.dead) 108 player.render(renderer); 109 foreach(i; 0..player.entity.health) 110 renderer.push_sprite(-renderer.camera_x - 50 + i * 4, 29-renderer.camera_y, -renderer.camera_z-30, 26); 111 foreach(ref cpu; cpus[]) cpu.render(renderer); 112 foreach(ref pp; pPlasma[]) pp.render(renderer); 113 foreach(ref s; spiders[]) s.render(renderer); 114 foreach(ref h; healths[]) h.render(renderer); 115 foreach(ref s; sentries[]) s.render(renderer); 116 foreach(ref sp; sPlasma[]) sp.render(renderer); 117 foreach(ref p; particles[]) p.render(renderer); 118 foreach(ref e; explosions[]) e.render(renderer); 119 } 120 void put(Explosion* e) { 121 explosions.put(e); 122 } 123 void put(Particle* p) { 124 particles.put(p); 125 } 126 void put(PlayerPlasma* p) { 127 pPlasma.put(p); 128 } 129 void put(Sentry* s) { 130 sentries.put(s); 131 } 132 void put(SentryPlasma* p) { 133 sPlasma.put(p); 134 } 135 void remove(Explosion* e) { 136 explosions.removeItem(e); 137 } 138 void remove(Particle* p) { 139 particles.removeItem(p); 140 } 141 void remove(Health* h) { 142 healths.removeItem(h); 143 } 144 void remove(Spider* s) { 145 spiders.removeItem(s); 146 } 147 void remove(PlayerPlasma* p) { 148 pPlasma.removeItem(p); 149 } 150 void remove(Sentry* s) { 151 sentries.removeItem(s); 152 } 153 void remove(SentryPlasma* s) { 154 sPlasma.removeItem(s); 155 } 156 void rebootCpu() @trusted { 157 cpus_rebooted++; 158 159 enum reboot_message = only( 160 l(3,"REBOOTING..."), 161 l(13,"SUCCESS") 162 ); 163 enum stillOffline = " SYSTEM(S) STILL OFFLINE"; 164 165 if (cpus.length-cpus_rebooted > 0) { 166 (*gTerminal).terminal_show_notice( 167 chain(reboot_message,only(l(14,text(cpus.length-cpus_rebooted,stillOffline)))) 168 ); 169 } 170 else { 171 if (current_level != 3) { 172 (*gTerminal).terminal_show_notice(chain( 173 reboot_message, 174 only(l(14,"ALL SYSTEMS ONLINE"), 175 l(15,"TRIANGULATING POSITION FOR NEXT HOP..."), 176 l(45,"TARGET ACQUIRED"), 177 l(46,"JUMPING...")) 178 ), 179 &this.nextLevel 180 ); 181 } 182 else { 183 (*gTerminal).terminal_show_notice(chain( 184 reboot_message, 185 only(l(14,"ALL SYSTEMS ONLINE")) 186 ),&this.nextLevel); 187 } 188 } 189 // TODO 190 // audio_play(audio_sfx_beep); 191 } 192 void clearEntities() { 193 cpus.shrinkTo(0); 194 pPlasma.shrinkTo(0); 195 spiders.shrinkTo(0); 196 healths.shrinkTo(0); 197 sentries.shrinkTo(0); 198 sPlasma.shrinkTo(0); 199 particles.shrinkTo(0); 200 explosions.shrinkTo(0); 201 } 202 void nextLevel() @trusted { 203 gGame.loadNextLevel(); 204 } 205 void decodeLevel(ref Renderer renderer, int id = 0) @trusted { 206 clearEntities(); 207 cpus_rebooted = 0; 208 auto randomWallTile() { 209 if (random() < 0.8f) 210 return 7; 211 return cast(int)(random()*8f+8.5f); 212 } 213 auto randomFloorTile() { 214 auto i = cast(int)(random()*16f+0.5f); 215 if (i < 5) return 0; 216 if (i < 6) return 1; 217 if (i < 8) return 2; 218 if (i < 14) return 4; 219 if (i < 15) return 5; 220 return 6; 221 } 222 renderer.begin_build_level(); 223 for(int idx = 0; idx < 64*64; idx++) { 224 if (data[idx] == Block.empty) 225 continue; 226 auto y = cast(int)(idx / 64); 227 auto x = idx % 64; 228 if (data[idx] != Block.wall) { 229 renderer.push_floor(x*8,y*8,randomFloorTile()); 230 } 231 with (Block) { 232 switch(data[idx]) { 233 case floor: break; 234 case wall: renderer.push_block(x*8,y*8,4,randomWallTile()); 235 break; 236 case player: 237 this.player.entity = Entity(x*8,0,y*8,5,18); 238 break; 239 case cpu: 240 cpus.put(allocator.make!(Cpu)(Entity(x*8, 0, y*8, 0, 18))); 241 break; 242 case sentry: 243 sentries.put(allocator.make!Sentry(Entity(x*8, 0, y*8, 5, 32))); 244 break; 245 default: assert(0); 246 } 247 } 248 } 249 // generate spiders/health 250 float spiderProb = 1f / (16-(id*2)); 251 enum healthProb = 1f / 100; 252 for(int idx = 0; idx < 64*64; idx++) { 253 if (data[idx] != Block.floor) 254 continue; 255 auto y = cast(int)(idx / 64); 256 auto x = idx % 64; 257 bool toCloseForSpiders = abs(player.entity.x - x*8) < 64 || abs(player.entity.y - y*8) < 64; 258 if (!toCloseForSpiders && random() <= spiderProb) 259 spiders.put(allocator.make!(Spider)(Entity(x*8, 0, y*8, 5, 27))); 260 if (random() <= healthProb) 261 healths.put(allocator.make!(Health)(Entity(x*8, 0, y*8, 5, 31))); 262 } 263 renderer.end_build_level(-player.entity.x,-300,-player.entity.z-100); 264 } 265 bool collides(int x, int z) { 266 enum w = 64; 267 return data[(x >> 3) + (z >> 3) * w] == Block.wall || // top left 268 data[(x >> 3) + (z >> 3) * w] == Block.cpu || // top left 269 data[((x + 6) >> 3) + (z >> 3) * w] == Block.wall || // top right 270 data[((x + 6) >> 3) + (z >> 3) * w] == Block.cpu || // top right 271 data[((x + 6) >> 3) + ((z+4) >> 3) * w] == Block.wall || // bottom right 272 data[((x + 6) >> 3) + ((z+4) >> 3) * w] == Block.cpu || // bottom right 273 data[(x >> 3) + ((z+4) >> 3) * w] == Block.wall || // bottom left 274 data[(x >> 3) + ((z+4) >> 3) * w] == Block.cpu; // bottom left 275 } 276 }