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 }