"use strict";

// packet handling
function mapKeys(obj, mapping) {
    return Object.keys(obj).reduce((r, k) => {
        const type = mapping[k];
        const val = obj[k];
        if (type instanceof Array) {
            if (type[1] == Array) {
                r[type[0]] = val.map(el => mapKeys(el, type[2]));
            }
            else {
                r[type[0]] = mapKeys(val, type[1]);
            }
        }
        else {
            r[type] = val;
        }
        return r;
    }, {});
}
const HighscoreGrow = {
    0: "nickname",
    1: "score",
};
const HighscoresGrow = {
    0: ["topScores", Array, HighscoreGrow],
};
const ConnectResponse = "CR";
const ConnectResponseGrow = {
    0: ["highScores", HighscoresGrow],
    1: "playerCount",
};
const RegisterPlayer = "RP";
const RegisterPlayerResponseGrow = {
    0: "uuid",
    1: "nickname",
    2: ["highScores", HighscoresGrow],
};
const MapResponse = "MR";
const MapResponseGrow = {
    0: "data",
    1: "useFullscreen",
};
const PositionGrow = {
    0: "x",
    1: "y",
};
const JoinGame = "JG";
const JoinGameResponseGrow = {
    0: "id",
    1: ["position", PositionGrow],
    2: "baseDirection",
    3: "towerDirection",
};
const OtherPlayerGrow = {
    0: "id",
    1: "score",
    2: "movement",
    3: ["position", PositionGrow],
    4: "baseDirection",
    5: "towerDirection",
    6: ["bullets", Array, PositionGrow],
};
const KilledPlayerGrow = {
    0: "nickname",
    1: ["position", PositionGrow],
};
const GameUpdate = "G";
const GameUpdateGrow = {
    0: ["players", Array, OtherPlayerGrow],
    1: ["killedPlayers", Array, KilledPlayerGrow],
};
const PlayerDeath = "PD";
const PlayerDeathGrow = {
    0: "killerId",
    1: "killerNickname",
    2: "score",
    3: ["highScores", HighscoresGrow],
};
function shrinkKeys(obj, mapping) {
    return Object.keys(mapping).map(k => {
        const type = mapping[k];
        if (type instanceof Array) {
            if (type[1] == Array) {
                return obj[type[0]].map(el => shrinkKeys(el, type[2]));
            }
            else {
                return shrinkKeys(obj[type[0]], type[1]);
            }
        }
        else {
            return obj[type];
        }
    });
}
// const RegisterPlayer = "RP"; // note: already defined for grow
const RegisterPlayerShrink = {
    0: "uuid",
    1: "nickname",
};
const PlayerBulletShrink = {
    0: "id",
    1: "x",
    2: "y",
};
const PositionShrink = {
    0: "x",
    1: "y",
};
const PlayerMove = "M";
const PlayerMoveShrink = {
    0: "movement",
    1: ["position", PositionShrink],
    2: "baseDirection",
    3: "towerDirection",
    4: ["bullets", Array, PlayerBulletShrink],
};

const playlist = [
    "loudly_dreamers.mp3",
    "loudly_deep_float.mp3",
    "loudly_junior_conquerer.mp3",
    "loudly_dub_wave.mp3",
    "loudly_no_paradise.mp3",
];

// some DOM helpers
const showId = (id) => document.getElementById(id).hidden = false;
const hideId = (id) => document.getElementById(id).hidden = true;
const enableId = (id) => document.getElementById(id).disabled = false;
const disableId = (id) => document.getElementById(id).disabled = true;
const textOnId = (id, text) => document.getElementById(id).textContent = text;
const htmlOnId = (id, html) => document.getElementById(id).innerHTML = html;
const valueOnId = (id, text) => document.getElementById(id).value = text;
const valueFromId = (id) => document.getElementById(id).value;
const setClass = (id, clazz, set) => {
    const elem = document.getElementById(id);
    elem.className = [elem.className.replace(new RegExp(`(?:^|\\s+)muted($|\\s+)`), ' '), set ? clazz : null].join(' ');
};

// ["xo ", "   "] => [[0,1,-1], [-1, -1, -1]]
const buildTilemapData = (textMap) =>
    textMap.map((line) =>
        line.split('').map((chr) =>
            chr === 'x' ? 0 :
                chr === 'o' ? 1 : -1
        )
    );

// SignalR handling
var connection = new signalR.HubConnectionBuilder()
    .withUrl("/game")
    .withAutomaticReconnect([0, 1000, 5000, null])
    //.configureLogging(signalR.LogLevel.Trace)
    .configureLogging(signalR.LogLevel.Information)
    //.withHubProtocol(new JsonHubProtocol())
    .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
    .build();

connection.on(ConnectResponse, (r) => {
    const response = mapKeys(r, ConnectResponseGrow);
    if (response !== '') {
        if (response.highScores.topScores !== '') {
            updateHighscores(response.highScores.topScores);
        }
        updateOpponentCount(response.playerCount);
    }
});

connection.on(RegisterPlayer, (p) => {
    const player = mapKeys(p, RegisterPlayerResponseGrow);
    if (player.nickname !== '') {
        valueOnId("playerName", player.nickname);
        setCookie('mass-netztank', player.uuid, 7);
        updateHighscores(player.highScores.topScores);

        connection.invoke(JoinGame).catch(function (err) {
            return console.error(err.toString());
        });
    }
});

var gameMap;
var mapUseFullscreen = false;
connection.on(MapResponse, m => {
    const map = mapKeys(m, MapResponseGrow);
    gameMap = buildTilemapData(map.data);
    mapUseFullscreen = map.useFullscreen;
});

var playerId;
var playerBaseDirection;
var playerTowerDirection;
var playerStartPosition;
connection.on(JoinGame, r => {
    const response = mapKeys(r, JoinGameResponseGrow);
    playerId = response.id
    playerBaseDirection = 24; // response.baseDirection;
    playerTowerDirection = response.towerDirection;
    playerStartPosition = response.position;

    startGame();
});

var updateCount = 0;
var updatedPlayers;
var killedPlayers;
connection.on(GameUpdate, u => {
    updateCount++;
    const update = mapKeys(u, GameUpdateGrow);
    if (!player) return;
    updatedPlayers = update.players;
    killedPlayers = update.killedPlayers;

    updateOpponentCount(updatedPlayers.length - 1);

    sendPlayerMove();
});

connection.on(PlayerDeath, d => {
    const death = mapKeys(d, PlayerDeathGrow);
    if (!player) return;
    updateHighscores(death.highScores.topScores);

    stopGame();
    showDeath(death.killerId, death.killerNickname, death.score);
});

const Disconnect = "D";
connection.on(Disconnect, function () {
    connection.stop();
});

connection.onclose(() => {
    stopGame();
    showDisconnect();
});

function connect() {
    disableId("connectButton");

    connection.start().then(() => {
        hideId("connectButton");
        showId("joinButton");
        enableId("joinButton");

        let uuid = getCookie('mass-netztank');

        if (uuid) {
            connection.invoke(RegisterPlayer, shrinkKeys({ uuid: uuid, nickname: "" }, RegisterPlayerShrink)).catch(function (err) {
                return console.error(err.toString());
            });
        }
    }).catch(function (err) {
        showDisconnect();
        return console.error(err.toString());
    });
}

const toBullet = ([id, b]) => ({ id: id, ...toPosition(b) });
function sendPlayerMove() {
    if (!player) return;

    let move = {
        movement: playerMovement,
        position: toPosition(player.base),
        baseDirection: Math.round(player.base.rotation * RAD_TO_STEPS * 10) / 10,
        towerDirection: Math.round(player.turret.rotation * RAD_TO_STEPS * 10) / 10,
        bullets: bullets.map(toBullet).concat(deadBullets)
    };

    deadBullets.splice(0);
    connection.invoke(PlayerMove, shrinkKeys(move, PlayerMoveShrink)).catch(function (err) {
        return console.error(err.toString());
    });
}

// GUI
function showDeath(killerId, killerNick, score) {
    if (killerId !== playerId)
        textOnId("formHead", `You Whimp! - Our fighter was rubished by ${killerNick}`);
    else
        textOnId("formHead", `Are you crazy? - You destroyed yourself!`);

    showId("formText");
    if (score >= 1000)
        textOnId("formText", `You scored ${score} kills. I suspect we have a cheater!`);
    else if (score >= 100)
        textOnId("formText", `You scored ${score} kills. Respect!`);
    else if (score >= 10)
        textOnId("formText", `You scored ${score} kills. Not too shabby!`);
    else if (score !== 0)
        textOnId("formText", `You scored ${score} kills. Please try harder next time!`);
    else
        textOnId("formText", `You scored nothing! Go and learn how to shoot!`);

    showId("registerScene");
    //document.getElementById("playerName").focus();
}
function showDisconnect() {
    textOnId("formHead", 'Oh no! - We lost our service. Please try again later!');
    hideId("formText");

    hideId("joinButton");
    showId("connectButton");
    enableId("connectButton");

    showId("registerScene");
}

function updateHighscores(scores) {
    let list = scores.map(e => `<div class="row"><span class="nick">${e.nickname}</span><span class="score">${e.score}</span></div>`);
    htmlOnId("highScores", list.join('\n'));
}

function updateOpponentCount(count) {
    if (opponentCountText) opponentCountText.setText(count ? ` hostile: ${`   ${count}`.slice(-3)} ` : ' warmup ');
}
function updateScore(score) {
    if (scoreText) scoreText.setText('score: ' + score);
}

function registerPlayer(event) {
    event.preventDefault();

    let nickname = valueFromId("playerName");
    if (nickname === '') return;

    let uuid = getCookie('mass-netztank');
    if (uuid === '') uuid = "_";

    connection.invoke(RegisterPlayer, shrinkKeys({ uuid: uuid, nickname: nickname }, RegisterPlayerShrink))
        .catch(function (err) {
            return console.error(err.toString());
        });
}

var player, bullets = [], deadBullets = [];
var playerMovement = 0;
var enemies = [];
var enemybullets = [];

const toPosition = (o) => ({ x: Math.round(100 * o.x / 32.) / 100., y: Math.round(100 * o.y / 32.) / 100. });
const moveToPosition = (o, p) => o.setPosition(p.x * 32, p.y * 32);
const rotateToVelocity = (o, v) => o.setVelocity(Math.sin(o.rotation) * v, -Math.cos(o.rotation) * v);
const offsetFrom = (o, d) => ({ x: o.x + Math.sin(o.rotation) * d, y: o.y - Math.cos(o.rotation) * d });

const TAU = Math.PI * 2;
const DEGREE_TO_RAD = TAU / 360.;
const ROTATION_STEPS = 32;
const RAD_TO_STEPS = ROTATION_STEPS / TAU;
const snapRadToStep = (r) => Math.round(r * RAD_TO_STEPS) / RAD_TO_STEPS;

class IntroScene extends Phaser.Scene {
    static get MAP() {
        return [
            "oooooooooooooooooooooooooooooooooooooooo",
            "o                                      o",
            "o xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx o",
            "o                                      o",
            "o         xxxxxxxx          xxxxxxx    o",
            "o x    x      x      x      x          o",
            "o xx   x xxxx x xxxx x x  x x      xx  o",
            "o x x  x x    x    x x x  x xxxx  x  x o",
            "o x  x x xxx  x   x  x x  x x     x  x o",
            "o x   xx x    x  x   x x  x x     x  x o",
            "o x    x xxxx x xxxx x  xx  x      xx  o",
            "o                                      o",
            "o xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx o",
            "o                                      o",
            "o                                      o",
            "o                                      o",
            "o                                      o",
            "o                                      o",
            "o                                      o",
            "o                                      o",
            "o                                      o",
            "o                                      o",
            "oooooooooooooooooooooooooooooooooooooooo"];
    }

    constructor() { super({ key: "intro" }); }

    preload() {
        this.load.image('intro_tiles', 'images/tiles.png');
        this.load.image('ufo', 'images/ufo_break.png');
    }
    create() {
        let gameMap = buildTilemapData(IntroScene.MAP);
        let mapWidth = gameMap[0].length;
        let mapHeight = gameMap.length;

        let map = this.make.tilemap({
            width: mapWidth,
            height: mapHeight,
            data: gameMap,
            insertNull: true
        });
        let tiles = map.addTilesetImage('intro_tiles');
        let layer = map.createLayer(0, tiles, 0, 0);
        let ufo = this.add.sprite(933, 236, 'ufo');
        ufo.scale = 0.685;
    }
    update() { }
}

var scoreText;
var opponentCountText;
var urlText;
var infoText;
var music = null;
function toggleMusic(event) {
    event.preventDefault();

    if (!music) return;
    const current = music.list[music.position];
    if (current.isPaused) {
        current.resume();
        setClass('musicButton', 'muted', false);
    }
    else if (current.isPlaying) {
        current.pause();
        setClass('musicButton', 'muted', true);
    }
}

class OverlayScene extends Phaser.Scene {
    constructor() {
        super({ key: "overlay", active: true });
    }

    preload() {
        this.load.audio(playlist.map((m, i) => ({ key: `music${i}`, url: [`music/${m}`] })));
    }

    create() {
        music = new Phaser.Structs.List();
        music.add(playlist.map((_, i) => this.sound.add(`music${i}`, { volume: 0.2 })));
        music.each(sound => sound.on("complete", () => (music.next || music.first).play()));

        const style = {
            backgroundColor: '#000', fontSize: '24px', fill: '#FFF'
        };
        opponentCountText = this.add.text(1060, 4, '', style).setDepth(200);
        scoreText = this.add.text(16, 4, '', style).setDepth(200);
        urlText = this.add.text(16, 710, 'Join in http://ufo.staxion.de', style).setDepth(200);
        let revisionText = this.add.text(860, 710, 'By HicknHack @ Revision 2024', style).setDepth(100);
        infoText = this.add.text(700, 4, '00', style).setDepth(200);

        updateOpponentCount(0);
        updateScore(0);

        if (!this.sound.locked) {
            (music.current || music.next).play();
        }
        else {
            disableId('musicButton');
            this.sound.once(Phaser.Sound.Events.UNLOCKED, () => {
                enableId('musicButton');
                (music.current || music.next).play();
            });
        }
        window.setInterval(() => {
            infoText.setText(updateCount);
            updateCount = 0;
        }, 1000);
    }
}

const BASE_SHAPE = { type: "rectangle", x: 1, y: 6, width: 26, height: 18 };
const TURRET_SHAPE = { type: "rectangle", x: 12, y: 0, width: 4, height: 12 };
const ANGLE_SPEED = 4.8 * DEGREE_TO_RAD; // rotation speed of tank & turret [?/s]
const TANK_SPEED = 3; // movement speed of tank [?/s]
const BULLET_OFFSET = 20; // where the bullet starts [px]
const BULLET_SPEED = 6; // movement speed of bullet [?/s]

const EXPLOSIONS = 3; // amount animations for explosions

var fireSounds = [];
var explosionSounds = [];

var delta;
var explodeAnimations = [];
function pickExplosion() {
    return explodeAnimations[Math.floor(Math.random() * explodeAnimations.length)];
}
function playExplosionAt(scene, pos) {
    if (Math.abs(player.base.x - pos.x) > 21 || Math.abs(player.base.y - pos.y) > 15) return;

    const explodeAnimation = pickExplosion();
    if (!scene.sound.locked) {
        let sound = explosionSounds.find(s => !s.isPlaying);
        if (!sound && explosionSounds.length < 5) {
            sound = scene.sound.add("explosion", { loop: false });
            explosionSounds.push(sound);
        }
        sound.play({ ...pos });
    }
    explodeAnimation.setPosition(pos.x, pos.y).setVisible(true).play(explodeAnimation.texture.key).setScale(.75);
}

var killAnimations = [];
var keys;
var wrap;
class GameScene extends Phaser.Scene {
    constructor() {
        super({
            key: "game",
            physics: {
                arcade: {
                    gravity: { y: 0 }
                },
                matter: {
                    //debug: true,
                    gravity: { y: 0 },
                    plugins: { wrap: true },
                    getDelta: () => delta
                }
            }
        });
        this.bulletCount = 0;
    }
    preload() {
        this.load.image('tiles', 'images/tiles-ufo.png');

        this.load.image('player', 'images/player.png');
        this.load.spritesheet('enemy', 'images/enemy.png', { frameWidth: 30, frameHeight: 30 });

        this.load.image('turret', 'images/turret.png');
        this.load.image('bullet', 'images/bullet2.png');

        //this.load.spritesheet('wall02', 'images/wall02.png', { frameWidth: 32, frameHeight: 32 });

        this.load.spritesheet('explode01', 'images/explode01.png', { frameWidth: 128, frameHeight: 128 });
        this.load.spritesheet('explode02', 'images/explode02.png', { frameWidth: 128, frameHeight: 128 });
        this.load.spritesheet('explode03', 'images/explode03.png', { frameWidth: 128, frameHeight: 128 });

        this.load.audio("fire", ["sound/fire.mp3"]);
        this.load.audio("explosion", ["sound/explosion.mp3"]);
    }

    _createTank(image, options) {
        let Composite = Phaser.Physics.Matter.Matter.Composite;
        let Constraint = Phaser.Physics.Matter.Matter.Constraint;
        let tank = Composite.create(options);

        let base = this.matter.add.sprite(0, 0, image, null, {
            restitution: 0, friction: 1, inertia: Infinity, shape: BASE_SHAPE,
            collisionFilter: { category: 2, mask: 1 }
        });

        let turret = this.matter.add.image(0, 0, 'turret', null, {
            restitution: 0, friction: 1, inertia: Infinity, shape: TURRET_SHAPE,
            collisionFilter: { category: 2, mask: 1 }
        });

        let constraint = Constraint.create({
            bodyA: base.body,
            bodyB: turret.body,
            stiffness: 1,
            length: 0
        });

        Composite.add(tank, [base.body, turret.body, constraint]);
        this.matter.world.remove(base);
        this.matter.world.remove(turret);

        this.matter.world.add(tank);

        tank.base = base;
        tank.turret = turret;
        if (image === 'enemy') base.play('enemy');
        return tank;
    }
    _destroyTank(tank) {
        //spectator cant die
        if (tank.nickname !== 'spectator') {
            tank.base.destroy();
            tank.turret.destroy();
            this.matter.world.remove(tank);
        }
    }

    create() {

        this.scale.displaySize.setAspectRatio(16.0 / 9.0);
        this.scale.refresh();

        fireSounds.push(this.sound.add("fire", { loop: false }));
        explosionSounds.push(this.sound.add("explosion", { loop: false }));

        const mapWidth = gameMap[0].length;
        const mapHeight = gameMap.length;
        if (!mapWidth || !mapHeight) return;

        this.cameras.main.setBounds(0, 0, 32 * mapWidth, 32 * mapHeight);
        this.physics.world.setBounds(0, 0, 32 * mapWidth, 32 * mapHeight);
        //this.matter.world.setBounds(0, 0, 32 * mapWidth, 32 * mapHeight);
        wrap = {
            wrap: {
                min: { x: 16, y: 16 },
                max: { x: 32 * mapWidth - 16, y: 32 * mapHeight - 16 }
            }
        };

        const KeyCodes = Phaser.Input.Keyboard.KeyCodes;
        keys = this.input.keyboard.addKeys({
            up: KeyCodes.UP,
            down: KeyCodes.DOWN,
            left: KeyCodes.LEFT,
            right: KeyCodes.RIGHT,
            space: KeyCodes.SPACE,
            dot: KeyCodes.PERIOD,
            comma: KeyCodes.COMMA,
            w: KeyCodes.W,
            s: KeyCodes.S,
            a: KeyCodes.A,
            d: KeyCodes.D,
            q: KeyCodes.Q,
            e: KeyCodes.E
        });

        const map = this.make.tilemap({
            width: mapWidth,
            height: mapHeight,
            data: gameMap,
            insertNull: true
        });

        let tiles = map.addTilesetImage('tiles');
        //let walls = map.addTilesetImage('wall02');

        let layer = map.createLayer(0, tiles, 0, 0).
            setCollisionBetween(0, 100);

        this.matter.world.convertTilemapLayer(layer, {
            restitution: 0, friction: 1,
            collisionFilter: { category: 1, mask: 0xFF }
        });

        this.anims.create({ key: 'enemy', frames: this.anims.generateFrameNumbers('enemy', { start: 0, end: 15 }), frameRate: 2, repeat: -1 });
        player = this._createTank('player', { label: 'player', plugin: wrap });

        moveToPosition(player.base, playerStartPosition).
            setRotation(playerBaseDirection / RAD_TO_STEPS);
        moveToPosition(player.turret, playerStartPosition).
            setRotation(playerTowerDirection / RAD_TO_STEPS);


        for (let i = 1; i <= EXPLOSIONS; i++) {
            const randstr = `00${i}`.slice(-2); //leading zero for single digit numbers

            explodeAnimations.push(this.add.sprite(0, 0, 'explode' + randstr).setDepth(100).setVisible(false));
            this.anims.create({
                key: 'explode' + randstr,
                frames: this.anims.generateFrameNumbers('explode' + randstr, { start: 0, end: 15 }),
                frameRate: 15,
                repeat: 0,
                hideOnComplete: true
            });
        }

        this.cameras.main.startFollow(player.base, true, 0.05, 0.05);
    }
    update(t, d) {

        delta = d;

        if ((keys.right.isDown || keys.d.isDown) && !(keys.left.isDown || keys.a.isDown)) {
            player.base.setAngularVelocity(ANGLE_SPEED);
        }
        else if ((keys.left.isDown || keys.a.isDown) && !(keys.right.isDown || keys.d.isDown)) {
            player.base.setAngularVelocity(-ANGLE_SPEED);
        }
        else {
            player.base.setAngularVelocity(0).
                setRotation(snapRadToStep(player.base.rotation));
        }

        if ((keys.up.isDown || keys.w.isDown) && !(keys.down.isDown || keys.s.isDown)) {
            playerMovement = +1;
            rotateToVelocity(player.base, +TANK_SPEED);
        }
        else if ((keys.down.isDown || keys.s.isDown) && !(keys.up.isDown || keys.w.isDown)) {
            playerMovement = -1;
            rotateToVelocity(player.base, -TANK_SPEED);
        }
        else {
            playerMovement = 0;
            player.base.setVelocity(0);
        }

        if ((keys.dot.isDown || keys.e.isDown) && !(keys.comma.isDown || keys.q.isDown)) {
            player.turret.setAngularVelocity(player.base.body.angularVelocity + ANGLE_SPEED);
        }
        else if (!(keys.dot.isDown || keys.e.isDown) && (keys.comma.isDown || keys.q.isDown)) {
            player.turret.setAngularVelocity(player.base.body.angularVelocity - ANGLE_SPEED);
        }
        else {
            player.turret.setAngularVelocity(player.base.body.angularVelocity);
            if (player.turret.body.angularVelocity === 0)
                player.turret.setRotation(snapRadToStep(player.turret.rotation));
        }

        // update player bullets
        if (player.nickname !== 'spectator' && this.input.keyboard.checkDown(keys.space, 500) && bullets.length < 10) {
            let o = offsetFrom(player.turret, BULLET_OFFSET);
            let b = this.matter.add.image(o.x, o.y, "bullet", null, {
                restitution: 0, frictionAir: 0, friction: 1, inertia: Infinity,
                collisionFilter: { category: 4, mask: 1 },
                plugin: wrap
            });
            if (!this.sound.locked) {
                let sound = fireSounds.find(s => !s.isPlaying);
                if (!sound && fireSounds.length < 10) {
                    sound = this.sound.add("fire", { loop: false });
                    fireSounds.push(sound);
                }
                sound.play();
            }
            let x = [this.bulletCount++, b];
            bullets.push(x);
            b.setOnCollide(
                ({ bodyA, bodyB }) => {
                    if (b && b.active && explodeAnimations) {
                        playExplosionAt(this, b);

                        const other = bodyA.gameObject === b ? bodyB.gameObject : bodyA.gameObject;
                        if (!other?.tile) return;
                        deadBullets.push(toBullet(x));
                        bullets.splice(bullets.indexOf(x), 1);
                        b.destroy();
                    }
                });
        }

        bullets.forEach(([_, b]) => {
            b.setRotation(snapRadToStep(player.turret.rotation));
            rotateToVelocity(b, BULLET_SPEED);
        });

        //killed players
        if (killedPlayers) {
            let spare = killAnimations.filter(([a, t, v]) => v);
            killedPlayers.forEach((kp, i) => {
                let arr;
                if (i < spare.length) {
                    arr = spare[i];
                }
                else {
                    let t = this.add.text(0, 0, '', {
                        fontSize: '20px', fill: '#f88', bold: true
                    }).setDepth(110);
                    arr = [t, false];
                    killAnimations.push(arr);
                }
                let [t, b] = arr;
                moveToPosition(t, kp.position).setText(kp.nickname);
                this.tweens.add({
                    targets: t, duration: 2000, ease: 'Linear', repeat: 0,
                    x: '+=28', y: '-=32', fontSize: '32px',
                    alpha: {
                        getStart: () => 1,
                        getEnd: () => 0
                    },
                    onComplete: () => arr[2] = true
                });
                playExplosionAt(this, kp.position);
            });
            killedPlayers = null;
        }

        //update enemies
        if (updatedPlayers) {
            let i = 0, bi = 0;
            updatedPlayers.forEach(other => {
                if (other.id === playerId) {
                    updateScore(other.score);
                    return;
                }
                let e;
                if (i < enemies.length) {
                    e = enemies[i];
                }
                else {
                    e = this._createTank('enemy', { label: 'Enemy' });
                    enemies.push(e);
                }
                i++;

                moveToPosition(e.base, other.position).
                    setRotation(other.baseDirection / RAD_TO_STEPS);
                rotateToVelocity(e.base, other.movement * TANK_SPEED);

                moveToPosition(e.turret, other.position).
                    setRotation(other.towerDirection / RAD_TO_STEPS);


                //create bullets from enemies 
                other.bullets.forEach((ob) => {
                    let b;
                    if (bi < enemybullets.length) {
                        b = enemybullets[bi];
                    }
                    else {
                        b = this.matter.add.image(0, 0, "bullet", null, {
                            restitution: 0, friction: 1,
                            collisionFilter: { category: 4, mask: 1 }
                        });
                        enemybullets.push(b);
                        b.setOnCollide(() => {
                            if (b && b.active && explodeAnimations) {
                                playExplosionAt(this, b);
                            }
                            b.setVisible(false);
                        });
                    }
                    if (b) {
                        moveToPosition(b, ob).
                            setRotation(e.turret.rotation).
                            setVisible(true);
                        rotateToVelocity(b, BULLET_SPEED);
                    }
                    bi++;
                });
            });
            for (let i = updatedPlayers.length - 1; i < enemies.length;) {
                let e = enemies[i];
                this._destroyTank(e);
                enemies.splice(i, 1);
            }
            while (bi < enemybullets.length) {
                let b = enemybullets[bi];
                b.destroy();
                enemybullets.splice(bi, 1);
            }
            updatedPlayers = null;
        }
    }
}

var config = {
    type: Phaser.AUTO,
    parent: 'game',
    width: 1280,
    height: 736,
    "transparent": true,
    scale: {
        mode: Phaser.Scale.Fill,
        autoCenter: Phaser.Scale.CENTER_BOTH
    },
    fps: {
        target: 30,
        forceSetTimeOut: true,
    },
    scene: [IntroScene, GameScene, OverlayScene],
};

var game = new Phaser.Game(config);

var scale = Math.min(1 / game.scale.displayScale.x, 1 / game.scale.displayScale.y);
document.getElementById("registerScale").style.transform = `scale(${scale}, ${scale})`;
game.scale.on("resize", () => {
    let scale = Math.min(1 / game.scale.displayScale.x, 1 / game.scale.displayScale.y);
    document.getElementById("registerScale").style.transform = `scale(${scale}, ${scale})`;
});
function startGame() {
    if (mapUseFullscreen) {
        const mapWidth = gameMap[0].length;
        const mapHeight = gameMap.length;

        game.destroy(true);
        config.width = mapWidth * 32;
        config.height = mapHeight * 32;
        config.scene = [GameScene, OverlayScene];
        game = new Phaser.Game(config);
    }
    else {
        game.scene.stop("intro");
        game.input.keyboard.enabled = true;
        game.scene.start("game");
    }
    hideId("registerScene");
}

function stopGame() {
    if (mapUseFullscreen) {
        game.destroy(true);
        config.width = 1280;
        config.height = 736;
        config.scene = [IntroScene, GameScene, OverlayScene];
        game = new Phaser.Game(config);
    }
    else {
        game.scene.stop("game");
        game.input.keyboard.enabled = false;
        game.scene.start("intro");
        document.getElementById("joinButton").focus({ focusVisible: true });
    }

    player = null;
    bullets = [];
    enemies = [];
    enemybullets = [];
    keys = null;
    explodeAnimations = [];
    killAnimations = [];

    updatedPlayers = null;
}

//cookie handling
const timeFromNow = (ms) => {
    let date = new Date();
    date.setTime(date.getTime() + ms);
    return date;
};
function setCookie(name, value, days) {
    let expires = days ? `;expires=${timeFromNow(days * 24 * 60 * 60 * 1000).toUTCString()}` : '';
    document.cookie = `${name}=${value || ""}${expires}; path=/`;
}
function getCookie(name) {
    let v = document.cookie.split(/;\s*/).find((c) => c.startsWith(name) && c.charAt(name.length) === '=');
    if (v) return v.substring(name.length + 1);
    return null;
}
function eraseCookie(name) {
    document.cookie = `${name}=; Max-Age=-99999999;`;
    document.cookie = `${name}=;expires=${(new Date()).toUTCString()};path=/`;
}

document.getElementById("connectButton").onclick = connect;
document.getElementById("joinButton").onclick = registerPlayer;
document.getElementById("musicButton").onclick = toggleMusic;
connect();
