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 }