Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
TheLazySquid
GitHub Repository: TheLazySquid/GimkitCheat
Path: blob/main/src/scripts/2d/Freecam.svelte
4175 views
<script lang="ts">
    // storesLoaded refers to the window.stores object existing, not svelte stores
    import Group from "../../hud/Group.svelte";
    import Button from "../../hud/components/Button.svelte";
    import Slider from "../../hud/components/Slider.svelte";
    import { serializer } from "../../network/schemaDecode";
    import { storesLoaded, playerId } from "../../stores";
    import { getUnsafeWindow } from "../../utils";

    let keys = new Set();
    let select: HTMLSelectElement;

    let freecamming = false;
    function onKeydown(event: KeyboardEvent) {
        if(!event.key.startsWith("Arrow")) return;
        
        // Prevent arrow keys from moving the character
        if(freecamming) {
            event.stopImmediatePropagation();
        }
        keys.add(event.key);
    }

    function onKeyup(event: KeyboardEvent) {
        keys.delete(event.key);
    }

    let freecamPos = { x: 0, y: 0 };
    let freecamUpdateInterval: number;

    let zoomValue = 1;

    function startFreecam() {
        let scene = getUnsafeWindow().stores.phaser.scene;
        let camera = scene.cameras.cameras[0];

        scene.cameraHelper.stopFollow();
        freecamPos = { x: camera.midPoint.x, y: camera.midPoint.y };
        camera.useBounds = false;

        freecamUpdateInterval = setInterval(() => {
            let freecamSpeed = 20 / zoomValue * 1.75;
            
            if(keys.has("ArrowUp")) freecamPos.y -= freecamSpeed
            if(keys.has("ArrowDown")) freecamPos.y += freecamSpeed
            if(keys.has("ArrowLeft")) freecamPos.x -= freecamSpeed
            if(keys.has("ArrowRight")) freecamPos.x += freecamSpeed

            scene.cameraHelper.goTo(freecamPos)
        }, 1000 / 30) as any;
    }

    function stopFreecam() {
        let phaser = getUnsafeWindow().stores.phaser;
        let charObj = phaser.scene.characterManager.characters
            .get(phaser.mainCharacter.id).body

        phaser.scene.cameraHelper.startFollowingObject({ object: charObj });
        phaser.scene.cameras.cameras[0].useBounds = true;

        clearInterval(freecamUpdateInterval);
    }

    storesLoaded.subscribe(loaded => {
        if(!loaded) return;

        let getZoomInterval = setInterval(() => {
            let zoom = getUnsafeWindow()?.stores?.phaser?.scene?.cameras?.cameras[0]?.zoom;
            if(zoom) {
                zoomValue = zoom;
                clearInterval(getZoomInterval);
            }
        }, 50)
    })

    function setZoom(event: CustomEvent<number>) {
        zoomValue = event.detail;

        getUnsafeWindow().stores.phaser.scene
            .cameras.cameras[0].setZoom(event.detail);
    }

    let specCharacters = [];
    let unbinds = [];

    playerId.subscribe(id => {
        specCharacters = specCharacters.filter(char => char.id !== id);
    })

    serializer.addEventListener("load", () => {
        const reloadCharacters = () => {
            specCharacters = [];
            for(let unbind of unbinds) unbind();
            for(let [id, char] of serializer.state.characters.$items) {
                if(!char.isActive || id === $playerId) continue;
                specCharacters.push({
                    id,
                    name: char.name
                })
                let unbind = char.listen("isActive", (_: boolean, prevVal: boolean | undefined) => {
                    if(prevVal === undefined) return;
                    reloadCharacters();
                });
                unbinds.push(unbind);
            }
        }

        serializer.state.characters.onChange(reloadCharacters);
        reloadCharacters();
    })

    let spectating = false;

    function onBtnClick() {
        if(spectating) {
            spectating = false;
            stopFreecam();
            select.value = "";
        } else {
            freecamming = !freecamming;
            if(freecamming) startFreecam();
            else stopFreecam();
        }
    }

    function selectPlayer() {
        if(!select.value) return;

        spectating = true;
        freecamming = false;
        stopFreecam();

        let char = getUnsafeWindow().stores.phaser.scene.characterManager.characters.get(select.value);
        if(!char) return;

        let camHelper = getUnsafeWindow().stores.phaser.scene.cameraHelper;
        camHelper.startFollowingObject({ object: char.body });
    }
</script>

<svelte:window on:keydown={onKeydown} on:keyup={onKeyup} />

<Group name="Freecam">
    <Button disabled={!$storesLoaded} disabledMsg="Camera hasn't loaded"
    on:click={onBtnClick}>
        {#if spectating}
            Stop Spectating
        {:else}
            {freecamming ? "Stop" : "Start"} Freecam
        {/if}
    </Button>
    <Slider title="Camera Zoom" min={0.1} max={5} step={0.1} on:input={setZoom}
    disabled={!$storesLoaded} value={zoomValue} disabledMsg="Camera hasn't loaded" />
    <select disabled={specCharacters.length === 0} on:change={selectPlayer} bind:this={select}
    on:keydown|preventDefault
    title={specCharacters.length === 0 ? "No characters to spectate" : undefined}>
        <option value="" selected>Pick a player to spectate</option>
        {#each specCharacters as char}
            <option value={char.id}>{char.name}</option>
        {/each}
    </select>
</Group>

<style>
    select {
        width: calc(100% - 10px);
        padding: 5px;
        margin-left: 5px;
        margin-right: 5px;
        color: black;
    }
    select[disabled] {
        cursor: not-allowed;
    }
</style>