1 module game.game;
2 
3 import game.renderer;
4 import game.level;
5 import game.terminal;
6 import spasm.types;
7 import spasm.event;
8 import spasm.dom;
9 import spasm.rt.memory;
10 import game.audio;
11 import canvas;
12 
13 nothrow:
14 @safe:
15 
16 extern(C) void load_image(string name, void delegate (Handle) cb);
17 extern(C) void load_level(uint id, ubyte[] level, uint ctx, uint fun);
18 extern(C) void scheduleFrame(uint ctx, uint fun);
19 
20 struct Game {
21   import spasm.bindings.webgl;
22   nothrow:
23   Renderer renderer;
24   Level level;
25   uint time_last = 0;
26   Input input = Input.None;
27   Canvas* canvas;
28   WebGLRenderingContext context;
29   int width, height;
30   int mouseX, mouseY;
31   bool running;
32   bool finished;
33   void init(int w, int h) @trusted {
34     import std.algorithm : move;
35     this.context = canvas.node.getContext("webgl").front.trustedGet!WebGLRenderingContext.move;
36     glSetContext(*context.handle.ptr);
37     renderer.renderer_init(w,h);
38     width = w;
39     height = h;
40     load_image("q2", &this.textureLoaded);
41     // TODO: simplify this manual call into addEventListenerTyped(&onKeydown)
42     document.addEventListener("keydown",(event)=>processKey(event.as!KeyboardEvent.key.getKey, true));
43     document.addEventListener("keyup",(event)=>processKey(event.as!KeyboardEvent.key.getKey, false));
44     canvas.node.addEventListener("mousemove",(event)=>onMousemove(event.as!MouseEvent));
45     document.addEventListener("mousedown",(event){input |= Input.Shoot;});
46     document.addEventListener("mouseup",(event){input &= ~Input.Shoot;});
47   }
48   void loadNextLevel() @trusted {
49     if (current_level == 3) {
50       (*gTerminal).terminal_run_outro();
51       finished = true;
52       return;
53     }
54     current_level++;
55     loadLevel();
56   }
57   void loadLevel() @trusted {
58     running = false;
59     if (level.data.length == 0)
60       level.data = allocator.make!(ubyte[64*64]);
61     load_level(current_level, level.data, toTuple(&this.generateLevel).expand);
62   }
63   void processKey(Input key, bool pressed) {
64     if (pressed)
65       input |= key;
66     else if (key != Input.None)
67       input &= ~key;
68   }
69   void onMousemove(scope MouseEvent* event) {
70     int clientWidth = canvas.node.clientWidth;
71     int clientHeight = canvas.node.clientHeight;
72     mouseX = cast(int)(width * (cast(double)event.offsetX)/clientWidth);
73     mouseY = cast(int)(height * (cast(double)event.offsetY)/clientHeight);
74   }
75   extern(C) void textureLoaded(Handle image) @trusted {
76     gTerminal.hide();
77     renderer.renderer_bind_image(image);
78     loadLevel();
79   }
80   void generateLevel(uint) {
81     level.decodeLevel(renderer);
82     scheduleFrame(toTuple(&this.tick).expand);
83     running = true;
84   }
85   void tick(uint time_now) {
86     auto time_elapsed = cast(float)(time_now - time_last)/1000;
87     time_last = time_now;
88 
89     renderer.renderer_prepare_frame();
90 
91     level.updateEntities(time_elapsed, input, mouseX, mouseY);
92     level.render(renderer, time_elapsed);
93     renderer.update_camera(level.player);
94 
95     renderer.renderer_end_frame();
96 
97     if (running)
98       scheduleFrame(toTuple(&this.tick).expand);
99   }
100 }