Skip to content
Snippets Groups Projects
Select Git revision
  • f99ed77c06ecc54f1b56b682de35dd4a1c93181f
  • master default protected
  • circleci-project-setup
  • actions
4 results

game.js

Blame
  • game.js 28.03 KiB
    /* 
        For js13k 2022
        Theme: death
    */
    
    // Functions for my little game engine thing
    const log = (logType, msg) => {
        console.log(`[${logType}] ${msg}`);
    }
    
    
    // CONFIG
    const fontStack = '"Comic Sans MS"';
    var id = 0;
    var pi = Math.PI;
    gPar = (key) => {
    
        // Address of the current window
        let address = window.location.search
    
        // Returns a URLSearchParams object instance
        let parameterList = new URLSearchParams(address)
    
        // Returning the respected value associated
        // with the provided key
        return parameterList.get(key)
    }
    
    var customLv = gPar("lv");
    
    class Canvas {
        constructor(id) {
            this.c = document.getElementById(id);
            this.ctx = this.c.getContext('2d');
            this.w = this.c.width;
            this.h = this.c.height;
            // get the width and height of the canvas from CSS
            this.tW = this.c.offsetWidth;
            this.tH = this.c.offsetHeight;
            this.scale = this.tW / this.w;
            this.camera = {x: 0, y: 0};
    
            this.mousePos = {x: 0, y: 0};
            this.realMousePos = {x: 0, y: 0};
    
        }
    
        fill(color) {
            this.ctx.fillStyle = color;
            this.ctx.fillRect(0, 0, this.w, this.h);
        }
    
        // Mouse position crap
        getMousePos(evt) {
            var rect = this.c.getBoundingClientRect(), // abs. size of element
              scaleX = this.c.width / rect.width,    // relationship bitmap vs. element for x
              scaleY = this.c.height / rect.height;  // relationship bitmap vs. element for y
    
            this.mousePos.x = ((evt.clientX - rect.left) * scaleX) + this.camera.x;
            this.mousePos.y = ((evt.clientY - rect.top) * scaleY) + this.camera.y;
          
            return {
              x: (evt.clientX - rect.left) * scaleX,   // scale mouse coordinates after they have
              y: (evt.clientY - rect.top) * scaleY     // been adjusted to be relative to element
            }
          }
    
        translate(x, y) {
            this.ctx.translate(x, y);
        }
    
        rotate(angle) {
            this.ctx.rotate(angle);
        }
    
        // Drawing
        drawImg(img, x,y,w,h, direction=0, originx=x+w/2, originy=y+h/2) {
            this.ctx.save();
            this.ctx.translate(originx-this.camera.x, originy-this.camera.y);
            this.ctx.rotate(direction * pi/180);
            this.ctx.drawImage(img, (-w/2), -h/2, w, h);
            this.ctx.restore();
        }
        sliceImage(img, x, y, w, h, cropX, cropY, cropW, cropH, direction=0) {
            this.ctx.save();
            this.ctx.translate((x+w/2)-this.camera.x, (y+h/2)-this.camera.y);
            this.ctx.rotate(direction * pi/180);
            this.ctx.drawImage(img, cropX, cropY, cropW, cropH, -w/2, -h/2, w, h);
            this.ctx.restore();
            // console.log(`${x}, ${y}, ${w}, ${h}, ${cropX}, ${cropY}, ${cropW}, ${cropH}`);
        }
        
        drawImage(img, x, y, w, h, direction=0) {
            // alias for drawImg
            this.drawImg(img, x, y, w, h, direction);
        }
    
        drawRect(x, y, w, h, color="white") {
            this.ctx.fillStyle = color;
            this.ctx.fillRect(x-this.camera.x, y-this.camera.y, w, h);
        }
    
        strokeRect(x, y, w, h, color) {
            this.ctx.strokeStyle = color;
            this.ctx.strokeRect(x-this.camera.x, y-this.camera.y, w, h);
        }
    
        drawFont(string, x, y, color, align="start") {
            this.ctx.fillStyle = color;
            this.ctx.textAlign = align;
            this.ctx.fillText(string, x-this.camera.x, y-this.camera.y);
        }
    
        dT(string, x, y, scaley, scalex, color, align="start", vAliign="top", ops={}) {
    
            // console.log(ops);
    
            // console.log(string)
    
            string = string.toUpperCase();
            let chars = string.split("");
            // console.log(chars);
            
            let charWidth = 7
            let strLength = (chars.length * charWidth - 1) * scalex; 
    
            let charHeight = 7
            let strHeight = (charHeight * scaley);
    
            switch(align) {
                case "start":
                case "left":
                    x = x;
                    break;
                case "center":
                case "middle":
                    x = x - strLength/2;
                    break;
                case "end":
                case "right":
                    x = x - strLength;
                    break;
            }
            switch(vAliign) {
                case "top":
                    y = y;
                    break;
                case "middle":
                case "center":
                    y = y - strHeight/2;
                    break;
                case "bottom":
                    y = y - strHeight;
                    break;
            }
    
            
            let charI = 0;
            let nextOffset = (7 * scalex);
            let lastWasFull = false;
            
            for (let char of chars) {
                
                this.ctx.fillStyle = color;
                let row = 0;
                let col = 0;
                // offset is the amount of pixels to offset the character by. you can calculate this by multiplying the current character (they're all the same size) by the scalex and scaley
                let offset = nextOffset;
    
                if (lastWasFull) {
                    offset -= (0.5 * scalex);
                    lastWasFull = false;
                }
                
                if(ops.shortFullStop) {
                    if(char == ".") {
                        lastWasFull = true;
                    }
                }
    
                char = fI[char];
                if (char == undefined) {
                    // leave a blank space
                } else {
                    for (let cRow in char) {
                        col = 0;
                        // for each pixel in the row
                        for (let c of char[cRow]) {
                            if (c == 1) {
                                this.ctx.fillRect((x + (col * scalex) + (charI * offset)) - this.camera.x,( y + (row * scaley)) - this.camera.y, scalex, scaley);
    
                            }
                            col++;
                        }
                        row++;
                    }
                }
    
                charI++;
                // console.log(charI)
    
                nextOffset = (7 * scalex);
    
            }
    
            return {
                "w": strLength
            }
    
        }
    
        setFont(fontStack, size="10") {
            this.ctx.font = `${size}px ${fontStack}`
        }
    
        drawLine(x1, y1, x2, y2, color) {
            this.ctx.strokeStyle = color;
            this.ctx.beginPath();
            this.ctx.moveTo(x1-this.camera.x, y1-this.camera.y);
            this.ctx.lineTo(x2, y2);
            this.ctx.stroke();
        }
    
        // Camera stuff
        mvCamera(x, y) {
            this.camera.x += x;
            this.camera.y += y;
        }
    
        setCamera(x, y, s=1) {
            this.camera.x = x;
            this.camera.y = y;
        }
    
    }
    
    // Entity classes
    
    class Entity {
        constructor(name, x, y, sprite=undefined) {
            this.name = name,
            this.x = x,
            this.y = y,
            this.sprite = sprite
        }
        step() {
            console.log(`${this.name} is stepping`);
            console.log(`${this.name} is at ${this.x}, ${this.y}`);
        }
        draw () {
    
        }
        intersects(other) {
            return (this.x < other.x + other.w && this.x + this.w > other.x && this.y < other.y + other.h && this.y + this.h > other.y)
        }
    }
    
    class Room {
        constructor(name) {
            this.id = id;
            id += 1;
            this.name = name;
            this.objects = [];
            this.hitboxes = [];
            this.background = [];
            this.w = c.w;
            this.h = c.h;
        }
    
        spawn(entity) {
            this.objects.push(entity)
        }
    
        step() {
            // step all objects in the room
            for (let obj of this.objects) {
                obj.step();
            }
        }
    
        draw() {
            // draw all objects in the room
            for (let obj of this.objects) {
                obj.draw();
            }
        }
    
        drawGUI() {
    
        }
    
        keyDown(key) {
            // console.log(key);
        }
        keyHeld(key) {
            // console.log(key);
        }
        click(x, y) {
            // console.log(x, y);
        }
        mHeld(x,y){
    
        }
    
        start() {
    
        }
    
    }
    
    // INIT CANVAS
    var fI = fntINDEX; // in letters.js
    var c = new Canvas('gameCanvas');
    // check if the canvas is supported
    if(!c.ctx) {
        alert("Your browser does not support the canvas element");
    }
    gameCtx = c.ctx;
    c.fill("#1c1c1c");
    c.setFont(fontStack);
    gameCtx.imageSmoothingEnabled = false;
    var gameStart = false;
    
    c.dT("Death By Hamster", c.w / 2, c.h / 2 - 40, 2, 2, "white", "middle");
    
    // Load images
    var images = {
        "mouse": {
            "ingame": "./assets/aimerthing.png",
            "cursor": "./cursor.png"
        },
        "level": {
            "tileset": "./t.png"
        },
        "player": {
            "debugarrow": "./assets/arrow.png",
            "car": "./hamster.png",
            "gun": "./gun.png",
        },
        "ui": {
            "a": "./arw.png" // arrow
        }
    };
    
    var loader = new Room("loader");
    var loadingText = "Loading...";
    var loadingError = 0;
    loader.drawGUI = () => {
        c.dT(loadingText, c.w / 2, c.h / 2, 2, 2, "white", "middle");
        if (loadingError) {
            c.dT(loadingErrorText, c.w / 2, c.h / 2 + 20, 1, 1, "red", "middle");
        }
    }
    var rooms = [];
    
    var loadedImages = 0;
    var totalImages = 0;
    
    // count the total number of images to load
    for (let key in images) {
        for (let subkey in images[key]) {
            totalImages++;
        }
    }
    
    loadingText = `Loading...`;
    
    loadingText = `Loading images (${loadedImages} / ${totalImages})`
    
    
    // after all images are loaded, and no errors occured, start the game
    for (var key in images) {
        for (var subkey in images[key]) {
            
            // attempt to load the image
            var IMG = new Image();
            IMG.addEventListener('load', () => {
                loadedImages++;
                loadingText = `Loading images (${loadedImages} / ${totalImages})`
                if (loadedImages == totalImages) {
                    loader.step = () => {
                        cRoom = rooms[1];
                    }
                    loadingText = "Loaded! Please wait...";
                }
            });
            IMG.addEventListener('error', (e) => {
                loadingError = 1;
                loadingErrorText = `Error loading image ${e.target.src}`;
            } );
            IMG.src = images[key][subkey];
    
            // add the image to the images object
            images[key][subkey] = IMG;
            
            // draw the loading text by drawing a rectangle over the previous text, and drawing the new text
            loadingText = `Loading images (${loadedImages} / ${totalImages})`
            
        }
    }
    
    var levels = [
        {
            "name": "Tutorial",
            "data": "[[1,1,2],[1,1,3],[5,1,4],[1,1,1],[2,0,3],[2,0,2],[2,0,1],[4,0,0],[5,1,0],[5,2,0],[5,3,0],[5,4,0],[5,6,0],[5,5,0],[5,7,0],[6,8,0],[0,9,0],[0,10,0],[2,8,1],[2,8,2],[2,8,3],[8,8,4],[5,7,4],[5,5,4],[5,6,4],[5,4,4],[5,2,4],[5,3,4],[7,0,4],[1,2,3],[9,2,2],[1,2,1],[1,3,1],[1,3,2],[1,3,3],[1,4,3],[1,4,2],[1,4,1],[1,5,1],[1,5,2],[1,5,3],[1,6,3],[10,6,2],[1,6,1],[1,7,1],[1,7,2],[1,7,3]]"
        },
        {
            "name": "First Floor"
        },
    ]
    
    hamsterRef = {
        "file": images.player.car,
        "nl": {
            "x": 1,
            "y": 1,
            "w": 32,
            "h": 16,
        },
        "b": {
            "x": 35,
            "y": 1,
            "w": 32,
            "h": 16,
        },
        "br": {
            "x": 1,
            "y": 20,
            "w": 32,
            "h": 16,
        },
        "r": {
            "x": 35,
            "y": 20,
            "w": 32,
            "h": 16,
        }
    }
    
    var levelRef = {
        "file": images.level.tileset,
        "default": {
            "x": 0,
            "y": 0,
            "w": 32,
            "h": 32,
            "type": "blank"
        },
        "tiles": [
            {
            },
            {
                "x": 32,
                "type": "floor"
            },
            {
                "x": 64,
                "type": "wall"
            },
            {
                "x": 96,
                "type": "wall"
    
            },
            {
                "x": 128,
                "type": "wall"
            },
            {
                "x": 160,
                "type": "wall"
            },
            {
                "x": 192,
                "type": "wall"
            },
            {
                "x": 224,
                "type": "wall"
            },
            {
                "x": 256,
                "type": "wall"
            },
            { // player
                "x": 32,
                "type": "floor"
            },
            { // human
                "x": 32,
                "type": "floor"
            }
        ]
    }
    
    for (let tile of levelRef.tiles) {
        // if the tile is missing properties from the default, add them
        for (let key in levelRef.default) {
            if (!tile[key]) {
                tile[key] = levelRef.default[key];
            }
        }
    }
    
    console.debug(images)
    var targFPS = 60;
    var frame = 0;
    
    
    var menu = new Room("menu");
    
    menu.s = 0
    menu.o = [
        {
            "t": "Play",
            "a": _=>{ setRoom(4) } // go to game room
        },
        {
            "t": "Editor",
            "a": _=>{ setRoom(3) } // go to level editor
        }
    ]
    
    
    menu.drawGUI = () => {
        c.dT("Death by Hamster", c.w/2, c.h/2-25, 4, 4, "white", "middle", "middle");
        c.dT("W/Up or S/Down to select", c.w/2, c.h/2, 1,1,"gray","middle","middle");
        c.dT("Space or ENTER to activate", c.w/2, c.h/2+8, 1,1,"gray","middle","middle")
        for (let o in menu.o) {
            let txt = c.dT(`${menu.o[o].t}`, c.w/2, (c.h/2+50)+(o*20), 2,2,"#fff","middle","top");
            if (menu.s == o) {
                let a = images.ui.a;
                let ap = ((c.w/2)-(txt.w/2))-a.width-4;
                let ap2 = ((c.w/2)+(txt.w/2))+a.width-4;
                c.drawImg(a, ap, (c.h/2+50)+(o*20), a.width*2, a.height*2)
                c.drawImg(a, ap2, (c.h/2+50)+(o*20), a.width*2, a.height*2, 180)
            }
        }
    }
    const nextRoom = () => {
        // move to the next room
        roomI++;
        if (roomI >= rooms.length) {
            roomI = 0;
        }
        cRoom = rooms[roomI];
        cRoom.start();
    }
    
    const prevRoom = () => {
        // move to the previous room
        roomI--;
        if (roomI < 0) {
            roomI = rooms.length - 1;
        }
        cRoom = rooms[roomI];
        cRoom.start();
    }
    
    const setRoom = (roomI) => {
        // set the current room to the given room
        cRoom = rooms[roomI];
        cRoom.start();
    }
    menu.keyDown = (key) => {
        if (key == "ArrowUp" || key == "KeyW") {
            menu.s -= 1
            if (menu.s < 0) {
                menu.s = menu.o.length-1
            }
        }
        if (key == "ArrowDown" || key == "KeyS") {
            menu.s += 1
            if (menu.s > menu.o.length-1) {
                menu.s = 0
            }
        }
        if (key == "Space" || key == "Enter") {
            menu.o[menu.s].a();
        }
    }
    
    var gameRoom = new Room("Game");
    gameRoom.level = levels[0];
    var player   = new Entity("Player", 0,0);
    player.speed = 0;
    player.maxSpeed = 5;
    player.direction = 0;
    player.accel = 1;
    player.sprite = images.player.car;
    console.debug(player.sprite);
    player.crop = hamsterRef.nl;
    player.x = 0;
    player.y = 0;
    player.w = player.crop.w*2;
    player.h = player.crop.h*2;
    
    player.step = () => {
        // move in this.direction, which is an angle in degrees
        player.x += player.speed * Math.cos(player.direction * pi / 180);
        player.y += player.speed * Math.sin(player.direction * pi / 180);
    
        player.speed *= 0.009;
    
        // check that the player won't go into a wall on the next step, and if so, stop.
        for (let tile of gameRoom.level) {
            if (player.x/64 > tile.x && player.x/64 < tile.x + tile.w && player.y/64 > tile.y && player.y/64 < tile.y + tile.h) {
                player.speed = 0;
            }
        }
    
        // keep the camera centered on the player
        c.setCamera(player.x - c.w/2, player.y - c.h/2);
    
    }
    
    console.log(player);
    
    player.draw = () => {
        // draw this.sprite at this.x, this.y
        c.sliceImage(player.sprite, player.x, player.y, player.w, player.h, player.crop.x, player.crop.y, player.crop.w, player.crop.h, player.direction);
        // canvas.strokeRect(player.x, player.y, player.w, player.h, "white");
    
        let gun = images.player.gun;
        let gunOx = 13;
        let gunOy = 0;
    
        let carCx = player.x + player.w/2;
        let carCy = player.y + player.h/2;
        
        // get gunx and guny by moving backwards (gunOx and gunOy) from the center of the car in this.direction
        let gunx = carCx - gunOx * Math.cos(player.direction * pi / 180) - gunOy * Math.sin(player.direction * pi / 180);
        let guny = carCy - gunOx * Math.sin(player.direction * pi / 180) + gunOy * Math.cos(player.direction * pi / 180);
        player.gx = gunx
        player.gy = guny
    
        // get the angle between the gun and the mouse
        player.aim = Math.atan2(c.mousePos.y - guny, c.mousePos.x - gunx) * 180 / pi;
    
        // canvas.drawText(`Width${gun.width} Height${gun.height}`, gunx, guny-15, 1, 1, "green", "middle", "middle");
        c.drawImg(gun, gunx, guny, gun.width*2, gun.height*2, player.aim, gunx, guny); // these two vars at the end are where the gun's center is placed
        // canvas.drawRect(gunx, guny, 1,1, "red");
    
    }   
    
    player.shoot = () => {
        // shoot a bullet
        let bullet = new Entity("Bullet", player.gx, player.gy);
        bullet.speed = 20;
        bullet.direction = player.aim;
        
        bullet.step = () => {
            // for each step, check if it's path intersects with any other entity
            for (let i = 0; i < cRoom.objects.length; i++) {
                let ent = cRoom.objects[i];
                if (ent != bullet && ent.intersects(bullet)) {
                    // if it does, remove the bullet and the entity unless it's the player
                    if (ent != player) {
                        cRoom.objects.splice(i, 1);
                        cRoom.objects.splice(cRoom.objects.indexOf(bullet), 1);
                    }
                    return;
                }
            }
            // if it doesn't, move the bullet
            bullet.x += bullet.speed * Math.cos(bullet.direction * pi / 180);
            bullet.y += bullet.speed * Math.sin(bullet.direction * pi / 180);
        }
        bullet.draw = () => {
            c.drawRect(bullet.x, bullet.y, 2,2, "white");
        }
        cRoom.spawn(bullet);
    }
    
    
    gameRoom.spawn(player);
    
    gameRoom.keyDown = (key) => {
        console.log(key);
    
        if (key == "ArrowUp" || key == "KeyW") {
            player.speed += player.accel*2;
            if (player.speed > player.maxSpeed) {
                player.speed = player.maxSpeed;
            }
        }
        if (key == "ArrowDown" || key == "KeyS") {
            player.speed -= player.accel*1.5;
            if (player.speed < -player.maxSpeed) {
                player.speed = -player.maxSpeed;
            }
        }
        if (key == "ArrowLeft" || key == "KeyA") {
            player.direction -= 2.5;
            if (player.direction < 0) {
                player.direction = 360;
            }
        }
        if (key == "ArrowRight" || key == "KeyD") {
            player.direction += 2.5;
            if (player.direction > 360) {
                player.direction = 0;
            }
        }
        if (key == "Space") {
            player.shoot();
        }
    }
    gameRoom.keyHeld = (key) => {
        if (key == "ArrowUp" || key == "KeyW") {
            player.speed += player.accel*1.9;
            if (player.speed > player.maxSpeed) {
                player.speed = player.maxSpeed;
            }
        }
        if (key == "ArrowDown" || key == "KeyS") {
            player.speed -= player.accel*1.53;
            if (player.speed < -player.maxSpeed) {
                player.speed = -player.maxSpeed;
            }
        }
        if (key == "ArrowLeft" || key == "KeyA") {
            player.direction -= 2.5;
            if (player.direction < 0) {
                player.direction = 360;
            }
        }
        if (key == "ArrowRight" || key == "KeyD") {
            player.direction += 2.5;
            if (player.direction > 360) {
                player.direction = 0;
            }
        }
    }
    gameRoom.click = (e) => {
        player.shoot();
    }
    
    gameRoom.start = () =>{
        if (customLv) {
            gameRoom.level = customLv
        }
        gameRoom.level = JSON.parse(gameRoom.level);
    
        for (let tile of gameRoom.level) {
            if (tile[0] === 9) {
                player.x = (tile[1]*64)+32
                player.y = (tile[2]*64)+32
            }
            if(tile[0]===10){
                let checkwall = (tx,ty) => {
                    for (let tile of gameRoom.level) {
                        if (levelRef.tiles[tile[0]].type == "wall" && tile[1] == tx && tile[2] == ty) {
                            return true;
                        }
                    }
                    return false;
                }
    
                let pooman = new Entity("Human", (tile[1]*64),(tile[2]*64), images.mouse.cursor)
                pooman.step = _=>{
                    if (pooman.timer<=0){
                        let director = Math.floor(Math.random()*4)
                        pooman.direction = director*90;
                        let tX = Math.floor(pooman.x / 64)
                        let tY = Math.floor(pooman.y / 64)
                        if (director === 0){
                            if (!checkwall(tX,tY-1)){
                                pooman.y -= 64;
                            }
                        }
                        if (director === 1){
                            if (!checkwall(tX+1,tY)){
                                pooman.x += 64;
                            }
                        }
                        if (director === 2){
                            if (!checkwall(tX,tY+1)){
                                pooman.y += 64;
                            }
                        }
                        if (director === 3){
                            if (!checkwall(tX-1,tY)){
                                pooman.x -= 64;
                            }
                        }
                        pooman.timer = 90;
                    }
                    pooman.timer--;
                }
                pooman.draw = _=>{
                    c.drawImage(images.mouse.cursor, pooman.x, pooman.y, 64, 64, pooman.direction);
                    c.dT(`${pooman.timer} :: ${pooman.direction}`, pooman.x, pooman.y, 1, 1, "white", "middle", "middle");
                }
                pooman.timer = 90;
                gameRoom.spawn(pooman);
            }
        }
    }
    
    gameRoom.draw = () => {
        for (let tile of gameRoom.level) {
            // [index, x, y]
            c.sliceImage(levelRef.file, (tile[1]*32)*2, (tile[2]*32)*2, 32*2,32*2, levelRef.tiles[tile[0]].x, 0, 32, 32);
        }
        
        for (let i = 0; i < cRoom.objects.length; i++) {
            cRoom.objects[i].draw();
        }
    }
    
    let editor = new Room("Editor");
    editor.i = 0;
    editor.t = levelRef;
    editor.l = []
    editor.n = "LV1"
    editor.saving = false
    editor.sa = 0
    
    editor.start = _=>{
        editor.dPos = [15,65]
    }
    editor.draw = _=>{
        for (let tile of editor.l) {
            // [index, x, y]
            c.sliceImage(levelRef.file, (tile[1]*32)+editor.dPos[0], tile[2]*32+editor.dPos[1], 32,32, tile[0]*32, 0, 32, 32);
            c.drawRect(editor.dPos[0], editor.dPos[1], 1,1, "red")
        }
    }
    editor.step = _=>{
        if (editor.i < 0) {
            editor.i = levelRef.tiles.length-1;
        }
        if (editor.i > levelRef.tiles.length-1) {
            editor.i = 0;
        }
    }
    editor.generate = _=>{
        editor.saving=1
        let encodedLevel = encodeURIComponent(JSON.stringify(editor.l))
        if (encodedLevel != editor.data){
            encodedLevel = "?lvl=" + encodedLevel;
            document.getElementById("leveltext").innerText = encodedLevel
        }
        editor.data = encodedLevel;
        editor.saving=0;
        editor.sa = 1;
    }
    editor.click = (x,y)=>{
        if (y < 50) {
            if (x> 516 && y < 50) {
                if (!editor.saving){
                    editor.generate()
                }
                editor.saveclick = true
            }
        }
         else {
            x = Math.floor((x-editor.dPos[0])/32)
            y = Math.floor((y-editor.dPos[1])/32)
            // console.debug(x,y)
            for (let t in editor.l) {
                if (editor.l[t][1] == x && editor.l[t][2] == y) {
                    editor.l[t] = [editor.i, x, y];
                    return;
                }
            }
            editor.l.push([editor.i,x,y])
            editor.sa = 0
        }
    }
    editor.keyHeld = (key) => {
        switch (key) {
            case "KeyW":
            case "ArrowUp":
                editor.dPos[1] += 4
                break;
            case "KeyS":
            case "ArrowDown":
                editor.dPos[1] -= 4
                break;
            case "KeyA":
            case "ArrowLeft":
                editor.dPos[0] += 4
                break;
            case "KeyD":
            case "ArrowRight":
                editor.dPos[0] -= 4
                break;
        }
    }
    
    editor.drawGUI = _=>{
        c.drawRect(0,0,c.w,50,"gray")
        c.dT(`DBH Editor::${editor.n}`, 15,25,2,2,"#fff","start","middle");
        let s = c.dT(`Save`, c.w-15,25,2,2,"#fff","end","middle");
        if (c.mousePos.x > (c.w-30)-s.w && c.mousePos.y < 50) {
            // console.debug((c.w-30)-s.w)
            c.dT(`Save`, c.w-15,25,2,2,"#e5e5e5","end","middle");
        }
        if (editor.sa) {
            c.dT(`Save`, c.w-15,25,2,2,"#1fdc2f","end","middle");
        } if (editor.saving) {
            c.dT(`Save`, c.w-15,25,2,2,"#1fccdc","end","middle");
        }
    
        c.sliceImage(editor.t.file, c.mousePos.x+16,c.mousePos.y+16,32,32,32*editor.i,0,32,32);
    }
    
    let lvlS = new Room("Level Select")
    lvlS.s = 0
    lvlS.o = levels
    
    lvlS.drawGUI = () => {
        c.dT("Death by Hamster", c.w/2, 25, 2, 2, "white", "middle", "top");
        c.dT("Level Select", c.w/2, 44, 1,1,"gray","middle","middle");
        for (let o in lvlS.o) {
            let n = parseInt(o)+1
            c.dT(`${n}`, (20)+(32*n), 70, 2, 2, "#fff", "middle", "middle")
            if (o == lvlS.s) {
                c.strokeRect((20-14)+(32*n), 70-16, 32, 32, "#fff")
            }
        }
    }
    
    lvlS.keyDown = (key) => {
        if (key == "ArrowUp"||key=="ArrowRight"||key == "KeyW"||key=="KeyD") {
            lvlS.s -= 1
            if (lvlS.s < 0) {
                lvlS.s = lvlS.o.length-1
            }
        }
        if (key == "ArrowDown" ||key=="ArrowLeft"||key == "KeyW"|| key == "KeyA") {
            lvlS.s += 1
            if (lvlS.s > lvlS.o.length-1) {
                lvlS.s = 0
            }
        }
        if (key == "Space" || key == "Enter") {
            gameRoom.level = lvlS.o[lvlS.s].data;
            setRoom(2)
        }
    }
    
    rooms.push(loader);
    rooms.push(menu);
    rooms.push(gameRoom);
    rooms.push(editor);
    rooms.push(lvlS);
    var roomI = !gPar("goto") ? 0 : gPar("goto");
    
    var cRoom = rooms[roomI];
    
    
    var keysPressed = {};
    var keysLastPressed = {};
    
    document.addEventListener('keydown', (e) => {
        keysPressed[e.code] = true;
    });
    document.addEventListener('keyup', (e) => {
        keysPressed[e.code] = false;
        keysLastPressed[e.code] = false;
    } );
    
    var lastTime = 0;
    
    var mse = {x: 0, y: 0};
    var lastClick = {x: 0, y: 0};
    var startclicked = false;
    var endclicked = false
    
    c.c.addEventListener('mousemove', (e) => {
        mse = c.getMousePos(e);
    } );
    
    c.c.addEventListener("mousedown", (e) => {
        // console.log(e);
        lastClick = c.getMousePos(e);
        mse = c.getMousePos(e);
        startclicked = true;
    });
    c.c.addEventListener("mouseup", (e)=>{
        // console.log(e);
        lastClick = c.getMousePos(e);
        mse = c.getMousePos(e);
        endclicked = true;
    })
    
    window.onwheel = (e)=>{
        if (e.deltaY > 0) {
            editor.i += 1;
        }
        if (e.deltaY < 0) {
            editor.i -= 1;
        }
    }
    
    cRoom.start();
    
    var gameLoop = setInterval(() => {
        c.tW = c.c.offsetWidth;
        c.tH = c.c.offsetHeight;
        c.scale = c.tW / c.w;
        frame++;
        c.fill("#151f1f");
    
        for (let key in keysPressed) {
            if (keysPressed[key]) {
                if (!keysLastPressed[key]) {
                    cRoom.keyDown(key);
                    keysLastPressed[key] = true;
                } else if (keysLastPressed[key]) {
                    cRoom.keyHeld(key);
                }
            }
        }
        if (startclicked) {
            cRoom.click(lastClick.x, lastClick.y);
            startclicked = false;
        }
    
        cRoom.step();
        cRoom.draw();
        cRoom.drawGUI(); 
    
        /* BEDUG INFO */
        c.dT(`FPS:${Math.round(1000 / (Date.now() - lastTime))}`, 0+c.camera.x, 0+c.camera.y, 1, 1, "#fafafa", "left", "top");
    
        switch (cRoom.name) {
            case "menu":
            case "Editor":
                c.ctx.drawImage(images.mouse.cursor, Math.round(mse.x), Math.round(mse.y), images.mouse.cursor.width*2, images.mouse.cursor.height*2);
                break;
            case "Game":
                c.ctx.drawImage(images.mouse.ingame, Math.round(mse.x)-16, Math.round(mse.y)-16, 32, 32);
                break;
        }
        lastTime = Date.now();
    
    } , 1000/targFPS); // 60 fps