import { Entity, EntityInventoryComponent, GameMode, ItemStack, Player, system, TimeOfDay, WeatherType, world } from "@minecraft/server";
import { namespace } from "./namespace";
import { Vector3 } from "./vector/vector3";
import { items } from "./items/hackerTable";
import { cameraOrbitPosition } from "./camera/camera";
import { colours } from "./util/colour";

/**
 * @type {Entity[]}
 */
let runInts = []
/**
 * @type {string[]} list of entities ids in the tutorial
 */
let tutorialEntities = []
export let tutorialRunning = false

export function setupTutorial() {
    const dim = world.getDimension("overworld")
    dim.getPlayers().forEach(player => {
        player.teleport(new Vector3(-445.5, 20, -276.5), { rotation: { x: 0, y: 270 } })
        if (player.getGameMode() != GameMode.survival) player.setGameMode(GameMode.survival)
        player.runCommand(`xp -999999L @s`)
        player.setDynamicProperty(`${namespace}:in_tutorial`, true)
    })

    replenishHealthAndHunger()

    clearRuns()
    clearItems()
    clearInvs()

    tutorialEntities = []

    hackerTableRoom()
}

function replenishHealthAndHunger() {
    world.getAllPlayers().forEach(player => {
        system.run(() => {
            try {
                player.getComponent('health').resetToMaxValue();
                player.addEffect("saturation", 1, { amplifier: 255, showParticles: false })
            }
            catch (e) {
                // console.error("Failed to replenish health and hunger", e)
            }
        })
    })
}

/**
 * removes all items from all players
 */
function clearInvs() {
    const dim = world.getDimension("overworld")

    system.run(() =>
        dim.runCommand(`clear @a`)
    )
}

/**
 * removes all runs
 */
function clearRuns() {
    runInts.forEach(runInt => system.clearRun(runInt))
    runInts = []
}

/**
 * removes all items from the world
 */
function clearItems() {
    const dim = world.getDimension("overworld")
    system.run(() => dim.getEntities().filter(entity => entity.typeId == "minecraft:item").forEach(entity => entity.remove()))
}

/**
 * sets input permissions to false for player
 * @param {Player} player
 */
function disableMovement(player) {
    player.inputPermissions.cameraEnabled = false
    player.inputPermissions.movementEnabled = false
}

/**
 * sets input permissions to true for player
 * @param {Player} player
 */
function enableMovement(player) {
    player.inputPermissions.cameraEnabled = true
    player.inputPermissions.movementEnabled = true
}

/**
 * removes all entities except the tutorial entities
 */
function clearEntities() {
    const dim = world.getDimension("overworld")
    system.run(() => {
        system.runTimeout(() => {
            dim.getEntities().filter(entity => !tutorialEntities.includes(entity.id) && entity.typeId != "minecraft:player" && entity.typeId != "minecraft:item" && entity.typeId != "minecraft:armor_stand").forEach(entity => {
                entity.remove()
            })
            // dim.getEntitiesAtBlockLocation(new Vector3(-246.5, 20.00, -281.5)).filter(entity => entity.typeId == `minecraft:armor_stand`).splice(1).forEach(entity => entity.remove())
            // dim.getEntitiesAtBlockLocation(new Vector3(-241.5, 21, -287.5)).filter(entity => entity.typeId == `minecraft:armor_stand`).splice(1).forEach(entity => entity.remove())
            // dim.getEntitiesAtBlockLocation(new Vector3(-246.5, 21, -288.5)).filter(entity => entity.typeId == `minecraft:armor_stand`).splice(1).forEach(entity => entity.remove())
            // dim.getEntitiesAtBlockLocation(new Vector3(-251.5, 21, -287.5)).filter(entity => entity.typeId == `minecraft:armor_stand`).splice(1).forEach(entity => entity.remove())
            if (tutorialEntities.length < 12) spawnEntities1()
        }, 15)
    })
}

function hackerTableRoom() {
    const dim = world.getDimension("overworld")

    world.getAllPlayers().forEach(player => {
        //TODO: add back in after development
        player.teleport(new Vector3(-445.5, 20, -276.5), { rotation: { x: 0, y: 270 } })
    })

    /**
     * @type {Entity}
    */
    let table
    try {
        // First, try to remove any existing hacker_desk entities at this location
        dim.getEntitiesAtBlockLocation(new Vector3(-438.5, 20, -276.5))
            .filter(entity => entity.typeId == `${namespace}:hacker_desk`)
            .forEach(entity => entity.remove())

        // Then spawn the new entity
        table = dim.spawnEntity(`${namespace}:hacker_desk`, new Vector3(-438.5, 20, -276.5))
    }
    catch (e) {
        // if setup fails, try again with a longer timeout
        // console.error("Failed to spawn table, trying again", e)
        system.runTimeout(() => { hackerTableRoom() }, 20)
        return
    }


    try {
        if (table) table.setProperty(`${namespace}:table_on`, true)
    }
    catch (e) {
        // if property setting fails, try again with a longer timeout
        // console.error("Failed to set table property, trying again", e)
        system.runTimeout(() => { hackerTableRoom() }, 20)
        return
    }

    if (tutorialRunning) {
        return
    }
    tutorialRunning = true

    spawnEntities1()
    placeDoors1()
    tutorialEntities.push(table.id)

    // console.error(table.isValid(), table.typeId, table.id)
    if (dim.getEntitiesAtBlockLocation(new Vector3(-438.5, 20, -276.5)).filter(entity => entity.typeId == `${namespace}:hacker_desk`).length < 1) {
        // if the table is not present, try again
        // console.error("Table not present, trying again 1")
        system.runTimeout(() => { hackerTableRoom() }, 20)
        return
    }

    runInts.push(system.runInterval(() => {
        // console.error(table.isValid(), table.typeId, table.id, "after interval")
        if (!table.isValid()) {
            tutorialRunning = false
            setupTutorial()
            return
        }

        world.getAllPlayers().forEach(player => {
            player.onScreenDisplay.setActionBar("           Welcome to Hacker Items\nClick the button to move to the next room")
            replenishHealthAndHunger()
        })
    }, 40))

    const buttonEvent = world.afterEvents.playerInteractWithEntity.subscribe(event => {
        const target = event.target
        if (target.getProperty(`${namespace}:button_pressed`)) return

        if (new Vector3(-432.5, 20, -273.5).equals(target.location) && target.typeId == `${namespace}:button`) {
            // ensure only one button exists
            dim.getEntitiesAtBlockLocation(new Vector3(-432.5, 20, -273.5)).filter(entity => entity.typeId == `${namespace}:button` && entity.id != target.id).forEach(entity => entity.remove())
            target.setProperty(`${namespace}:button_pressed`, true)

            // find blocks to outline
            /**
             * @type {Set<Vector3>}
             */
            const outlinePositions = new Set()
            // -412 17 -279 to -408 23 -275
            for (let x = -412; x <= -408; x++) {
                for (let y = 17; y <= 23; y++) {
                    for (let z = -279; z <= -275; z++) {
                        if (dim.getBlock(new Vector3(x, y, z)).typeId != "minecraft:air") outlinePositions.add(new Vector3(x, y, z))
                    }
                }
            }
            // spawn outlines
            const runInt = system.runInterval(() => {
                outlinePositions.forEach(pos => {
                    dim.spawnEntity(`${namespace}:outline`, pos.add(new Vector3(0.5, 0.5, 0.5)))
                })
            }, 1)

            // do camera animation
            world.getAllPlayers().forEach(player => {
                disableMovement(player)
                player.addEffect("invisibility", 30 * 20, { showParticles: false })
                player.runCommand(`camera @s set minecraft:free pos ~ ~1.62 ~ rot ~ ~`)
                player.runCommand(`camera @s set minecraft:free ease 1.5 in_out_cubic pos -434.5 23.5 -276.5 rot 0 270`)
                system.runTimeout(() => {
                    player.teleport(new Vector3(-432.5, 20, -276.5), { rotation: { x: 0, y: 270 } })
                    cameraOrbitPosition(player, new Vector3(-409.5, 19, -276.5), 11, 6, 270, 360)
                }, 30)

                system.runTimeout(() => {
                    system.clearRun(runInt)
                    enableMovement(player)
                    player.removeEffect("invisibility")
                }, 1.5 * 20 + 6 * 20 + 3.5 * 20)
            })

            system.runTimeout(() => {
                system.run(() => openDoor(new Vector3(-428.5, 20, -276.5)))
                clearRuns()
                clearItems()
                system.run(() => treecapitatorRoom())
                system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
                system.run(() => world.afterEvents.entityHitEntity.unsubscribe(buttonHitEvent))
                clearInvs()
                target.setProperty(`${namespace}:button_pressed`, false)
            }, 20)
        }
    })

    const buttonHitEvent = world.afterEvents.entityHitEntity.subscribe(event => {
        const target = event.hitEntity
        if (target.getProperty(`${namespace}:button_pressed`)) return

        if (new Vector3(-432.5, 20, -273.5).equals(target.location) && target.typeId == `${namespace}:button`) {
            // ensure only one button exists
            dim.getEntitiesAtBlockLocation(new Vector3(-432.5, 20, -273.5)).filter(entity => entity.typeId == `${namespace}:button` && entity.id != target.id).forEach(entity => entity.remove())
            target.setProperty(`${namespace}:button_pressed`, true)

            // find blocks to outline
            const outlinePositions = new Set()
            // -412 17 -279 to -408 23 -275
            for (let x = -412; x <= -408; x++) {
                for (let y = 17; y <= 23; y++) {
                    for (let z = -279; z <= -275; z++) {
                        if (dim.getBlock(new Vector3(x, y, z)).typeId != "minecraft:air") outlinePositions.add(new Vector3(x, y, z))
                    }
                }
            }
            // spawn outlines
            const runInt = system.runInterval(() => {
                outlinePositions.forEach(pos => {
                    dim.spawnEntity(`${namespace}:outline`, pos.add(new Vector3(0.5, 0.5, 0.5)))
                })
            }, 1)

            // do camera animation
            world.getAllPlayers().forEach(player => {
                disableMovement(player)
                player.addEffect("invisibility", 30 * 20, { showParticles: false })
                player.runCommand(`camera @s set minecraft:free pos ~ ~1.62 ~ rot ~ ~`)
                player.runCommand(`camera @s set minecraft:free ease 1.5 in_out_cubic pos -434.5 23.5 -276.5 rot 0 270`)
                system.runTimeout(() => {
                    player.teleport(new Vector3(-432.5, 20, -276.5), { rotation: { x: 0, y: 270 } })
                    cameraOrbitPosition(player, new Vector3(-409.5, 19, -276.5), 11, 6, 270, 360)
                }, 30)

                system.runTimeout(() => {
                    system.clearRun(runInt)
                    enableMovement(player)
                    player.removeEffect("invisibility")
                }, 1.5 * 20 + 6 * 20 + 3.5 * 20)
            })

            system.runTimeout(() => {
                system.run(() => openDoor(new Vector3(-428.5, 20, -276.5)))
                clearRuns()
                clearItems()
                system.run(() => treecapitatorRoom())
                system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
                system.run(() => world.afterEvents.entityHitEntity.unsubscribe(buttonHitEvent))
                clearInvs()
                target.setProperty(`${namespace}:button_pressed`, false)
            }, 20)
        }
    })

    // console.error(table.isValid(), table.typeId, table.id)
    clearEntities()
    // console.error(table.isValid(), table.typeId, table.id)
    dim.runCommand(`structure load block_ha:tutorial_tree -412 17 -279`)
    world.setDynamicProperty(`${namespace}:tutorial_room_number`, 0)
}

function treecapitatorRoom() {
    const dim = world.getDimension("overworld")

    runInts.push(system.runInterval(() => {
        world.getAllPlayers().forEach(player => {
            player.onScreenDisplay.setActionBar("Use the treecapitator to break the entire tree")
            replenishHealthAndHunger()
        })
    }, 40))

    const treecapitator = new ItemStack(`${namespace}:treecapitator`)
    treecapitator.setLore(findLore(`${namespace}:treecapitator`))

    for (let i = 0; i < world.getAllPlayers().length; i++) {
        dim.spawnItem(treecapitator, new Vector3(-423.5, 20.9, -276.5))
    }


    const breakBlocksCallback = world.beforeEvents.playerBreakBlock.subscribe(event => {
        const block = event.block
        const item = event.itemStack
        const player = event.player

        if (!item || item.typeId != `${namespace}:treecapitator` || block.typeId != "minecraft:oak_log") {
            system.run(() => player.onScreenDisplay.setActionBar("Use the treecapitator on an oak log!"))
            event.cancel = true
            return
        }

        if (item.typeId == `${namespace}:treecapitator` && block.typeId == "minecraft:oak_log") {
            system.run(() => openDoor(new Vector3(-391, 20, -277)))
            system.run(() => world.beforeEvents.playerBreakBlock.unsubscribe(breakBlocksCallback))
            system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
            system.run(() => world.afterEvents.entityHitEntity.unsubscribe(buttonHitEvent))
            clearItems()
            system.run(() => miningRoom())
            clearRuns()
            clearInvs()
        }
        else {
            event.cancel = true
        }
    })

    const buttonEvent = world.afterEvents.playerInteractWithEntity.subscribe(event => {
        const target = event.target
        if (target.getProperty(`${namespace}:button_pressed`)) return

        if (target.typeId == `${namespace}:button` && new Vector3(-394.5, 20, -273.5).equals(target.location)) {
            dim.getEntitiesAtBlockLocation(new Vector3(-394.5, 20, -273.5)).filter(entity => entity.typeId == `${namespace}:button` && entity.id != target.id).forEach(entity => entity.remove())

            target.setProperty(`${namespace}:button_pressed`, true)
            system.runTimeout(() => {
                system.run(() => openDoor(new Vector3(-390.5, 20, -276.5)))
                clearRuns()
                clearItems()
                system.run(() => miningRoom())
                system.run(() => world.beforeEvents.playerBreakBlock.unsubscribe(breakBlocksCallback))
                system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
                system.run(() => world.afterEvents.entityHitEntity.unsubscribe(buttonHitEvent))
                clearInvs()
                target.setProperty(`${namespace}:button_pressed`, false)
            }, 5)
        }
    })

    const buttonHitEvent = world.afterEvents.entityHitEntity.subscribe(event => {
        const target = event.hitEntity
        if (target.getProperty(`${namespace}:button_pressed`)) return

        if (target.typeId == `${namespace}:button` && new Vector3(-394.5, 20, -273.5).equals(target.location)) {
            dim.getEntitiesAtBlockLocation(new Vector3(-394.5, 20, -273.5)).filter(entity => entity.typeId == `${namespace}:button` && entity.id != target.id).forEach(entity => entity.remove())

            target.setProperty(`${namespace}:button_pressed`, true)
            system.runTimeout(() => {
                system.run(() => openDoor(new Vector3(-390.5, 20, -276.5)))
                clearRuns()
                clearItems()
                system.run(() => miningRoom())
                system.run(() => world.beforeEvents.playerBreakBlock.unsubscribe(breakBlocksCallback))
                system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
                system.run(() => world.afterEvents.entityHitEntity.unsubscribe(buttonHitEvent))
                clearInvs()
                target.setProperty(`${namespace}:button_pressed`, false)
            }, 5)
        }
    })

    clearEntities()
    dim.runCommand(`fill -378 24 -275 -364 15 -284 stone replace air`)
    dim.runCommand(`fill -363 15 -279 -359 19 -270 dirt replace air`)
    dim.runCommand(`fill -358 15 -275 -354 19 -284 stone replace air`)
    dim.runCommand(`structure load block_ha:tutorial_ores -353 15 -279`)
    world.setDynamicProperty(`${namespace}:tutorial_room_number`, 1)
}

function miningRoom() {
    const dim = world.getDimension("overworld")

    const megaPickaxe = new ItemStack(`${namespace}:mega_pickaxe`)
    megaPickaxe.setLore(findLore(`${namespace}:mega_pickaxe`))
    const megaShovel = new ItemStack(`${namespace}:mega_shovel`)
    megaShovel.setLore(findLore(`${namespace}:mega_shovel`))
    const veinPickaxe = new ItemStack(`${namespace}:vein_pickaxe`)
    veinPickaxe.setLore(findLore(`${namespace}:vein_pickaxe`))

    // spawn items
    for (let i = 0; i < world.getAllPlayers().length; i++) {
        dim.spawnItem(megaPickaxe, new Vector3(-383.5, 20.9, -276.5))
        dim.spawnItem(megaShovel, new Vector3(-370.5, 15.9, -286.5))
        dim.spawnItem(veinPickaxe, new Vector3(-360.5, 15.9, -266.5))
    }

    // spawn waypoints
    const waypoint1 = dim.spawnEntity(`${namespace}:waypoint`, new Vector3(-370.5, 18, -286.5))
    waypoint1.setProperty(`${namespace}:texture_index`, 0)
    tutorialEntities.push(waypoint1.id)
    const waypoint2 = dim.spawnEntity(`${namespace}:waypoint`, new Vector3(-360.5, 18, -266.5))
    waypoint2.setProperty(`${namespace}:texture_index`, 1)
    tutorialEntities.push(waypoint2.id)
    const waypoint3 = dim.spawnEntity(`${namespace}:waypoint`, new Vector3(-383.5, 23, -276.5))
    waypoint3.setProperty(`${namespace}:texture_index`, 3)
    tutorialEntities.push(waypoint3.id)

    runInts.push(system.runInterval(() => {
        world.getAllPlayers().forEach(player => {
            player.onScreenDisplay.setActionBar("Mine through using the tools provided")
            replenishHealthAndHunger()
            player.runCommand(`clear @s cobblestone`)
            player.runCommand(`clear @s dirt`)
        })
    }, 5))

    runInts.push(system.runInterval(() => {
        world.getAllPlayers().forEach(player => {
            // check if player within bounds of -343 15 -274 -336 25 -280
            const pos = player.location

            if (pos.x > -343 && pos.x < -336 && pos.y > 15 && pos.y < 25 && pos.z > -280 && pos.z < -274) {
                clearRuns()
                clearItems()
                clearInvs()
                world.getAllPlayers().forEach(player => {
                    player.runCommand(`xp -999999L @s`)
                })
                system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
                system.run(() => farmingRoom())
            }
        })
    }, 5))

    const buttonEvent = world.afterEvents.playerInteractWithEntity.subscribe(event => {
        const target = event.target
        if (target.getProperty(`${namespace}:button_pressed`)) return

        if (new Vector3(-383.5, 20, -272.5).equals(target.location) && target.typeId == `${namespace}:button`) {
            dim.getEntitiesAtBlockLocation(new Vector3(-383.5, 20, -272.5)).filter(entity => entity.typeId == `${namespace}:button` && entity.id != target.id).forEach(entity => entity.remove())

            target.setProperty(`${namespace}:button_pressed`, true)
            system.runTimeout(() => {
                clearInvs()
                clearRuns()
                clearItems()
                world.getAllPlayers().forEach(player => {
                    player.runCommand(`xp -999999L @s`)
                    player.teleport(new Vector3(-337.5, 20, -276.5), { rotation: { x: 0, y: 270 } })
                })
                system.run(() => farmingRoom())
                system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
                system.run(() => world.afterEvents.entityHitEntity.unsubscribe(buttonHitEvent))
                target.setProperty(`${namespace}:button_pressed`, false)
            }, 10)
        }
    })

    const buttonHitEvent = world.afterEvents.entityHitEntity.subscribe(event => {
        const target = event.hitEntity
        if (target.getProperty(`${namespace}:button_pressed`)) return

        if (new Vector3(-383.5, 20, -272.5).equals(target.location) && target.typeId == `${namespace}:button`) {
            dim.getEntitiesAtBlockLocation(new Vector3(-383.5, 20, -272.5)).filter(entity => entity.typeId == `${namespace}:button` && entity.id != target.id).forEach(entity => entity.remove())

            target.setProperty(`${namespace}:button_pressed`, true)
            system.runTimeout(() => {
                clearInvs()
                clearRuns()
                clearItems()
                world.getAllPlayers().forEach(player => {
                    player.runCommand(`xp -999999L @s`)
                    player.teleport(new Vector3(-337.5, 20, -276.5), { rotation: { x: 0, y: 270 } })
                })
                system.run(() => farmingRoom())
                system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
                system.run(() => world.afterEvents.entityHitEntity.unsubscribe(buttonHitEvent))
                target.setProperty(`${namespace}:button_pressed`, false)
            }, 10)
        }
    })

    clearEntities()
    dim.runCommand(`structure load block_ha:tutorial_farming -332 19 -281`)
    world.setDynamicProperty(`${namespace}:tutorial_room_number`, 2)
}

function farmingRoom() {
    const dim = world.getDimension("overworld")

    world.getAllPlayers().forEach(player => {
        disableMovement(player)
        player.addEffect("invisibility", 30 * 20, { showParticles: false })
        const startPos1 = new Vector3(-331.5, 20.5, -280.5)
        const startPos2 = new Vector3(-331.5, 20.5, -274.5)
        const runInt = system.runInterval(() => {
            for (let i = 0; i <= 1; i++) {
                for (let x = 0; x <= 6; x++) {
                    for (let z = 0; z <= 2; z++) {
                        // water part
                        if (x == 1 && z == 3) continue
                        if (i == 0) dim.spawnEntity(`${namespace}:outline`, startPos1.add(new Vector3(x, 0, z)))
                        else dim.spawnEntity(`${namespace}:outline`, startPos2.add(new Vector3(x, 0, z)))
                    }
                }
            }
        })
        player.runCommand(`camera @s set minecraft:free pos ~ ~1.62 ~ rot ~ ~`)
        player.runCommand(`camera @s set minecraft:free ease 1.5 in_out_cubic pos ~ 24 -276.5 rot 30 270`)
        system.runTimeout(() => {
            player.teleport(new Vector3(-337.5, 20, -276.5), { rotation: { x: 0, y: 270 } })
            player.runCommand(`camera @s set minecraft:free ease 4 in_out_cubic pos -307.5 23 -276.5 rot 10 270`)
        }, 1.5 * 20)
        system.runTimeout(() => {
            player.runCommand(`camera @s set minecraft:free ease 1.5 in_out_cubic pos ~ ~1.62 ~ rot ~ ~`)
        }, 1.5 * 20 + 4 * 20)
        system.runTimeout(() => {
            player.runCommand(`camera @s clear`)
            system.clearRun(runInt)
            enableMovement(player)
            player.removeEffect("invisibility")
            /**
             * @type {EntityInventoryComponent}
             */
            const inv = player.getComponent('inventory')
            inv.container.clearAll()
            inv.container.addItem(new ItemStack(`${namespace}:replenish_hoe`))
        }, 1.5 * 20 + 4 * 20 + 1.5 * 20)
    })

    const infiniteWater = new ItemStack(`${namespace}:infinite_water_bucket`)
    infiniteWater.setLore(findLore(`${namespace}:infinite_water_bucket`))
    for (let i = 0; i < world.getAllPlayers().length; i++) {
        dim.spawnItem(infiniteWater, new Vector3(-334.5, 20.9, -276.5))
    }

    const runInt = system.runInterval(() => {
        world.getAllPlayers().forEach(player => {
            player.onScreenDisplay.setActionBar(`Farm 30 wheat and 30 carrots\n ${carrotCount >= 30 ? colours.green : colours.red}${carrotCount.toString().padStart(2, "0")}/30${colours.reset} Carrots | ${wheatCount >= 30 ? colours.green : colours.red}${wheatCount.toString().padStart(2, "0")}/30${colours.reset} Wheat`)
            replenishHealthAndHunger()
        })
    }, 1)

    const boneMeal = new ItemStack("minecraft:bone_meal")
    boneMeal.setLore(["Use this to grow crops faster"])
    runInts.push(system.runInterval(() => {
        dim.spawnItem(boneMeal, new Vector3(-324.5, 20.0, -276.5))
        dim.spawnItem(boneMeal, new Vector3(-316.5, 20.0, -276.5))
    }, 20 * 3))

    let wheatCount = 0
    let carrotCount = 0

    const buttonEvent = world.afterEvents.playerInteractWithEntity.subscribe(event => {
        const entity = event.target
        if (entity.getProperty(`${namespace}:button_pressed`)) return

        if (new Vector3(-306.5, 20, -273.5).equals(entity.location) && entity.typeId == `${namespace}:button`) {
            dim.getEntitiesAtBlockLocation(new Vector3(-306.5, 20, -273.5)).filter(entity => entity.typeId == `${namespace}:button` && entity.id != entity.id).forEach(entity => entity.remove())

            system.runTimeout(() => {
                system.run(() => openDoor(new Vector3(-300, 20, -277)))
                clearRuns()
                system.runTimeout(() => {system.clearRun(runInt)}, 2)
                system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
                system.run(() => world.afterEvents.entityHitEntity.unsubscribe(buttonHitEvent))
                clearItems()
                system.run(() => duperRoom())
                clearInvs()
            }, 5)
        }
    })

    const buttonHitEvent = world.afterEvents.entityHitEntity.subscribe(event => {
        const entity = event.hitEntity
        if (entity.getProperty(`${namespace}:button_pressed`)) return

        if (new Vector3(-306.5, 20, -273.5).equals(entity.location) && entity.typeId == `${namespace}:button`) {
            dim.getEntitiesAtBlockLocation(new Vector3(-306.5, 20, -273.5)).filter(entity => entity.typeId == `${namespace}:button` && entity.id != entity.id).forEach(entity => entity.remove())

            system.runTimeout(() => {
                system.run(() => openDoor(new Vector3(-300, 20, -277)))
                clearRuns()
                system.runTimeout(() => {system.clearRun(runInt)}, 2)
                system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
                system.run(() => world.afterEvents.entityHitEntity.unsubscribe(buttonHitEvent))
                clearItems()
                system.run(() => duperRoom())
                clearInvs()
            }, 5)
        }
    })

    /**
     * @type {Vector3}
     */
    let lastBlockLoc = new Vector3(0, 0, 0)
    let lastBreakTick = -1
    let beforeBreakBlock = world.beforeEvents.playerBreakBlock.subscribe(event => {
        const block = event.block
        const growth = block.permutation.getState("growth")

        if (lastBlockLoc.equals(block.location) && system.currentTick - lastBreakTick < 40) return
        lastBlockLoc = new Vector3().toVector(block.location)
        lastBreakTick = system.currentTick

        // console.error("unique block", block.typeId)

        if (!growth) return
        if (growth < 7) return

        if (block.typeId == "minecraft:wheat") {
            wheatCount++
        }
        else if (block.typeId == "minecraft:carrots") {
            carrotCount++
        }

        if (wheatCount >= 30 && carrotCount >= 30) {
            system.run(() => openDoor(new Vector3(-300, 20, -277)))
            system.run(() => world.beforeEvents.playerBreakBlock.unsubscribe(beforeBreakBlock))
            system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
            system.run(() => world.afterEvents.entityHitEntity.unsubscribe(buttonHitEvent))
            clearRuns()
            system.runTimeout(() => {system.clearRun(runInt)}, 2)
            clearItems()
            system.run(() => duperRoom())
            clearInvs()
        }
    })

    spawnEntities2()
    clearEntities()
    dim.runCommand(`structure load block_ha:tutorial_dupe -294 20 -290`)
    world.setDynamicProperty(`${namespace}:tutorial_room_number`, 3)
}

function duperRoom() {
    const dim = world.getDimension("overworld")

    const runInt = system.runInterval(() => {
        // spawn outline on each duper
        dim.getEntities().filter(entity => entity.typeId == `${namespace}:item_duper`).forEach(entity => {
            dim.spawnEntity(`${namespace}:outline`, new Vector3().toVector(entity.location).add(new Vector3(0, 0.5, 0)))
        })
    })


    world.getAllPlayers().forEach(player => {
        disableMovement(player)
        player.addEffect("invisibility", 30 * 20, { showParticles: false })
        player.runCommand(`camera @s set minecraft:free ease 1.5 in_out_cubic pos -305.5 23.5 -276.5 rot 0 270`)
        system.runTimeout(() => {
            player.teleport(new Vector3(-298.5, 20, -276.5), { rotation: { x: 0, y: 270 } })
            player.runCommand(`camera @s set minecraft:free ease 3 in_out_cubic pos -280.5 29 -276.5 rot 40 270`)
        }, 1.5 * 20)
        system.runTimeout(() => {
            player.runCommand(`camera @s set minecraft:free ease 3 in_sine pos -280.5 29 -276.5 rot 40 89`)
        }, 1.5 * 20 + 3 * 20)
        system.runTimeout(() => {
            player.runCommand(`camera @s set minecraft:free ease 3 out_sine pos -280.5 29 -276.5 rot 40 268`)
        }, 1.5 * 20 + 3 * 20 + 3 * 20)
        system.runTimeout(() => {
            player.runCommand(`camera @s set minecraft:free ease 1.5 in_out_cubic pos ~ ~1.62 ~ rot ~ ~`)
        }, 1.5 * 20 + 3 * 20 + 3 * 20 + 3 * 20)
        system.runTimeout(() => {
            player.runCommand(`camera @s clear`)
            system.clearRun(runInt)
            enableMovement(player)
            player.removeEffect("invisibility")
        }, 1.5 * 20 + 3 * 20 + 3 * 20 + 3 * 20 + 1.5 * 20)
    })

    placeDoors2()

    runInts.push(system.runInterval(() => {
        world.getAllPlayers().forEach(player => {
            player.onScreenDisplay.setActionBar("       Interact with the Duper to duplicate the items\nFill the box with netherite blocks to move to the next room")
            replenishHealthAndHunger()
        })

        dim.getEntities().filter(entity => entity.typeId == `${namespace}:item_duper`).forEach(entity => {
            dim.spawnEntity(`${namespace}:outline_long`, new Vector3().toVector(entity.location).add(new Vector3(0, 0.5, 0)))
        })
    }, 40))

    const netheriteIngot = new ItemStack("minecraft:netherite_ingot", 1)
    dim.spawnItem(netheriteIngot, new Vector3(-296.5, 20.9, -276.5))
    // respawn every 5 minutes to prevent softlock
    runInts.push(system.runInterval(() => {
        dim.spawnItem(netheriteIngot, new Vector3(-296.5, 20.9, -276.5))
    }, 20 * 60 * 5))

    const blockEvent = world.afterEvents.playerPlaceBlock.subscribe(event => {
        const block = event.block
        const player = event.player

        // console.error(block.typeId, block.location.x, block.location.y, block.location.z)

        if (block.typeId == "minecraft:netherite_block") {
            const loc = block.location

            // check if not placed in -282.0 23.0 -278.0 and -279.0 26.00 -275.0
            if (loc.x < -282 || loc.x > -280 || loc.y < 23 || loc.y > 25 || loc.z < -278 || loc.z > -276) {
                player.onScreenDisplay.setActionBar("Place the netherite block in the box")
                event.dimension.runCommand(`fill ${loc.x} ${loc.y} ${loc.z} ${loc.x} ${loc.y} ${loc.z} minecraft:air destroy`)
                return
            }

            let netheriteCount = 0
            for (let x = -282; x <= -279; x++) {
                for (let y = 23; y <= 26; y++) {
                    for (let z = -278; z <= -275; z++) {
                        // dont check the center block: -281 24 -277
                        if (x == -281 && y == 24 && z == -277) {
                            netheriteCount++
                            continue
                        }
                        if (event.dimension.getBlock(new Vector3(x, y, z)).typeId == "minecraft:netherite_block") {
                            netheriteCount++
                        }
                    }
                }
            }

            if (netheriteCount >= 3 * 3 * 3) {
                let outline = dim.getEntitiesAtBlockLocation(new Vector3(-280.5, 23 + 5, -276.5)).filter(entity => entity.typeId == `${namespace}:block_outline`)


                for (let i = 0; i < outline.length - 1; i++) {
                    outline[i].remove()
                }

                if (outline.length < 1) {
                    outline.push(dim.spawnEntity(`${namespace}:block_outline`, new Vector3(-280.5, 23 + 5, -276.5)))
                    // console.error(outline[0].isValid(), outline[0].typeId, outline[0].id)
                }
                try {
                    outline[0].setProperty(`${namespace}:red`, 0)
                    outline[0].setProperty(`${namespace}:blue`, 0)
                }
                catch (e) { }

                system.run(() => openDoor(new Vector3(-261.5, 20, -276.5)))
                clearRuns()
                system.run(() => world.afterEvents.playerPlaceBlock.unsubscribe(blockEvent))
                dim.getEntities().filter(entity => entity.typeId == `${namespace}:item_duper`).forEach(entity => {
                    dim.runCommand(`fill ${entity.location.x} ${entity.location.y} ${entity.location.z} ${entity.location.x} ${entity.location.y} ${entity.location.z} minecraft:air destroy`)
                    entity.remove()
                })
                clearItems()
                clearInvs()
                system.run(() => armorRoom())
            }
        }
        else {
            event.dimension.runCommand(`fill ${block.location.x} ${block.location.y} ${block.location.z} ${block.location.x} ${block.location.y} ${block.location.z} minecraft:air destroy`)
        }
    })

    const buttonEvent = world.afterEvents.playerInteractWithEntity.subscribe(event => {
        const target = event.target
        if (target.getProperty(`${namespace}:button_pressed`)) return

        if (new Vector3(-267.5, 20, -273.5).equals(target.location) && target.typeId == `${namespace}:button`) {
            dim.getEntitiesAtBlockLocation(new Vector3(-267.5, 20, -273.5)).filter(entity => entity.typeId == `${namespace}:button` && entity.id != target.id).forEach(entity => entity.remove())

            target.setProperty(`${namespace}:button_pressed`, true)
            system.runTimeout(() => {
                system.run(() => openDoor(new Vector3(-261.5, 20, -276.5)))
                clearRuns()
                system.run(() => world.afterEvents.playerPlaceBlock.unsubscribe(blockEvent))
                dim.getEntities().filter(entity => entity.typeId == `${namespace}:item_duper`).forEach(entity => {
                    dim.runCommand(`fill ${entity.location.x} ${entity.location.y} ${entity.location.z} ${entity.location.x} ${entity.location.y} ${entity.location.z} minecraft:air destroy`)
                    entity.remove()
                })
                clearItems()
                clearInvs()
                system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
                system.run(() => world.afterEvents.entityHitEntity.unsubscribe(buttonHitEvent))
                system.run(() => armorRoom())
                target.setProperty(`${namespace}:button_pressed`, false)
            }, 5)
        }
    })

    const buttonHitEvent = world.afterEvents.entityHitEntity.subscribe(event => {
        const target = event.hitEntity
        if (target.getProperty(`${namespace}:button_pressed`)) return

        if (new Vector3(-267.5, 20, -273.5).equals(target.location) && target.typeId == `${namespace}:button`) {
            dim.getEntitiesAtBlockLocation(new Vector3(-267.5, 20, -273.5)).filter(entity => entity.typeId == `${namespace}:button` && entity.id != target.id).forEach(entity => entity.remove())

            target.setProperty(`${namespace}:button_pressed`, true)
            system.runTimeout(() => {
                system.run(() => openDoor(new Vector3(-261.5, 20, -276.5)))
                clearRuns()
                system.run(() => world.afterEvents.playerPlaceBlock.unsubscribe(blockEvent))
                dim.getEntities().filter(entity => entity.typeId == `${namespace}:item_duper`).forEach(entity => {
                    dim.runCommand(`fill ${entity.location.x} ${entity.location.y} ${entity.location.z} ${entity.location.x} ${entity.location.y} ${entity.location.z} minecraft:air destroy`)
                    entity.remove()
                })
                clearItems()
                clearInvs()
                system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
                system.run(() => world.afterEvents.entityHitEntity.unsubscribe(buttonHitEvent))
                system.run(() => armorRoom())
                target.setProperty(`${namespace}:button_pressed`, false)
            }, 5)
        }
    })

    clearEntities()
    world.setDynamicProperty(`${namespace}:tutorial_room_number`, 4)
}

function armorRoom() {
    const dim = world.getDimension("overworld")
    dim.getEntities({ type: `minecraft:armor_stand` }).forEach(entity => entity.remove())
    dim.runCommand(`structure load block_ha:tutorial_armour -252 20 -289`)
    tutorialEntities.push(dim.getEntitiesAtBlockLocation(new Vector3(-246.5, 20, -281.5))[0].id)
    tutorialEntities.push(dim.getEntitiesAtBlockLocation(new Vector3(-241.5, 21, -287.5))[0].id)
    tutorialEntities.push(dim.getEntitiesAtBlockLocation(new Vector3(-246.5, 21, -288.5))[0].id)
    tutorialEntities.push(dim.getEntitiesAtBlockLocation(new Vector3(-251.5, 21, -287.5))[0].id)


    world.getAllPlayers().forEach(player => {
        disableMovement(player)
        player.addEffect("invisibility", 30 * 20, { showParticles: false })
        player.runCommand(`camera @s set minecraft:free ease 3 in_out_cubic pos -266.5 24 -276.5 rot 0 270`)
        system.runTimeout(() => {
            player.teleport(new Vector3(-258.5, 20, -276.5), { rotation: { x: 0, y: 270 } })
            cameraOrbitPosition(player, new Vector3(-246.5, 18, -288.5), 13, 5, 135, 90)
        }, 3 * 20)
        system.runTimeout(() => {
            enableMovement(player)
            player.removeEffect("invisibility")
        }, 3 * 20 + 5 * 20 + 3.5 * 20)
    })

    runInts.push(system.runInterval(() => {
        world.getAllPlayers().forEach(player => {
            player.onScreenDisplay.setActionBar("Use the armor to pass the course")
            replenishHealthAndHunger()
        })
    }))

    runInts.push(system.runInterval(() => {
        world.getAllPlayers().forEach(player => {
            // check if players in bounds of -247 21 -214 to -251 25 -211
            const pos = player.location
            if (pos.x > -251 && pos.x < -247 && pos.y > 21 && pos.y < 25 && pos.z > -214 && pos.z < -211) {
                clearRuns()
                clearItems()
                clearInvs()
                system.run(() => bowRoom())
            }
        })
    }, 5))

    const buttonEvent = world.afterEvents.playerInteractWithEntity.subscribe(event => {
        const target = event.target
        if (target.getProperty(`${namespace}:button_pressed`)) return

        if (new Vector3(-248.5, 20, -264.5).equals(target.location) && target.typeId == `${namespace}:button`) {
            dim.getEntitiesAtBlockLocation(new Vector3(-248.5, 20, -264.5)).filter(entity => entity.typeId == `${namespace}:button` && entity.id != target.id).forEach(entity => entity.remove())

            target.setProperty(`${namespace}:button_pressed`, true)
            system.runTimeout(() => {
                clearRuns()
                clearInvs()
                clearItems()
                world.getAllPlayers().forEach(player => {
                    player.teleport(new Vector3(-248.5, 22, -212.5), { rotation: { x: 0, y: 0 } })
                })
                system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
                system.run(() => world.afterEvents.entityHitEntity.unsubscribe(buttonHitEvent))
                system.run(() => bowRoom())
                target.setProperty(`${namespace}:button_pressed`, false)
            }, 5)
        }
    })

    const buttonHitEvent = world.afterEvents.entityHitEntity.subscribe(event => {
        const target = event.hitEntity
        if (target.getProperty(`${namespace}:button_pressed`)) return

        if (new Vector3(-248.5, 20, -264.5).equals(target.location) && target.typeId == `${namespace}:button`) {
            dim.getEntitiesAtBlockLocation(new Vector3(-248.5, 20, -264.5)).filter(entity => entity.typeId == `${namespace}:button` && entity.id != target.id).forEach(entity => entity.remove())

            target.setProperty(`${namespace}:button_pressed`, true)
            system.runTimeout(() => {
                clearRuns()
                clearInvs()
                clearItems()
                world.getAllPlayers().forEach(player => {
                    player.teleport(new Vector3(-248.5, 22, -212.5), { rotation: { x: 0, y: 0 } })
                })
                system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
                system.run(() => world.afterEvents.entityHitEntity.unsubscribe(buttonHitEvent))
                system.run(() => bowRoom())
                target.setProperty(`${namespace}:button_pressed`, false)
            }, 5)
        }
    })


    clearEntities()
    world.setDynamicProperty(`${namespace}:tutorial_room_number`, 5)
}

function bowRoom() {
    const dim = world.getDimension("overworld")

    world.getAllPlayers().forEach(player => {
        player.teleport(new Vector3(-248.5, 22, -212.5), { rotation: { x: 0, y: 0 } })
    })


    const machineBow = new ItemStack(`${namespace}:machine_bow`)
    machineBow.setLore(findLore(`${namespace}:machine_bow`))
    for (let i = 0; i < world.getAllPlayers().length; i++) {
        dim.spawnItem(machineBow, new Vector3(-251.5, 22.9, -195.5))
    }

    runInts.push(system.runInterval(() => {
        world.getAllPlayers().forEach(player => {
            player.onScreenDisplay.setActionBar("Use the Machine Bow to shoot the targets")
            replenishHealthAndHunger()
        })
    }, 40))

    clearEntities()

    let targetCount = 0
    const hitEvent = world.afterEvents.projectileHitEntity.subscribe(event => {
        const entity = event.getEntityHit().entity

        // First check if this is a target block and if it's already been hit
        dim.getEntitiesAtBlockLocation(entity.location).filter(e => e.typeId == `${namespace}:target_block` && e.id != entity.id).forEach(e => e.remove())
        if (entity.typeId == `${namespace}:target_block` && !entity.getProperty(`${namespace}:on`)) {
            // Mark it as hit before doing anything else to prevent double counting
            entity.setProperty(`${namespace}:on`, true)
            targetCount++
            // console.error("incremented targetcount ", targetCount)
        }

        if (targetCount >= 3) {
            system.run(() => world.afterEvents.projectileHitEntity.unsubscribe(hitEvent))
            system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
            system.run(() => world.afterEvents.entityHitEntity.unsubscribe(buttonHitEvent))
            system.run(() => openDoor(new Vector3(-242.5, 22, -195.5)))
            clearItems()
            clearInvs()
            clearRuns()

            const waypoint1 = dim.spawnEntity(`${namespace}:waypoint`, new Vector3(-228.5, 23, -195.5))
            waypoint1.setProperty(`${namespace}:texture_index`, 4)
            tutorialEntities.push(waypoint1.id)
            const waypoint2 = dim.spawnEntity(`${namespace}:waypoint`, new Vector3(-213.5, 27, -195.5))
            waypoint2.setProperty(`${namespace}:texture_index`, 4)
            tutorialEntities.push(waypoint2.id)

            runInts.push(system.runInterval(() => {
                world.getAllPlayers().forEach(player => {
                    player.onScreenDisplay.setActionBar("Follow the waypoint to the bubble elevator to leave the tutorial")
                    // check if player in bounds of -210 110 -198 to -214 118 -194
                    const pos = player.location
                    if (pos.x > -214 && pos.x < -210 && pos.y > 110 && pos.y < 118 && pos.z > -198 && pos.z < -194) {
                        clearRuns()
                        world.getAllPlayers().forEach(player => {
                            player.setDynamicProperty(`${namespace}:in_tutorial`, false)
                            player.runCommand(`xp -999999L @s`)
                            player.getComponent('inventory').container.addItem(new ItemStack(`${namespace}:guide_book`))
                        })
                        world.setDynamicProperty(`${namespace}:tutorial_done`, true)
                        tutorialRunning = false
                        world.setDefaultSpawnLocation(new Vector3(-211, 115, -200))
                        world.gameRules.spawnRadius = 5
                        world.gameRules.doMobSpawning = true
                        world.setTimeOfDay(TimeOfDay.Day)
                        system.run(() => dim.runCommand("weather clear"))
                    }
                })
            }, 5))
        }
    })

    const buttonEvent = world.afterEvents.playerInteractWithEntity.subscribe(event => {
        const target = event.target
        if (target.getProperty(`${namespace}:button_pressed`)) return

        if (new Vector3(-245.5, 22, -192.5).equals(target.location) && target.typeId == `${namespace}:button`) {
            dim.getEntitiesAtBlockLocation(new Vector3(-245.5, 22, -192.5)).filter(entity => entity.typeId == `${namespace}:button` && entity.id != target.id).forEach(entity => entity.remove())

            target.setProperty(`${namespace}:button_pressed`, true)
            system.runTimeout(() => {
                clearRuns()
                clearItems()
                clearInvs()
                world.getAllPlayers().forEach(player => {
                    player.teleport(new Vector3(-211.5, 114.94, -198.5), { rotation: { x: 0, y: 180 } })
                    player.setDynamicProperty(`${namespace}:in_tutorial`, false)
                    player.runCommand(`xp -999999L @s`)
                    player.getComponent('inventory').container.addItem(new ItemStack(`${namespace}:guide_book`))
                })
                world.setDynamicProperty(`${namespace}:tutorial_done`, true)
                tutorialRunning = false
                world.setDefaultSpawnLocation(new Vector3(-211, 115, -200))
                world.gameRules.spawnRadius = 5
                world.gameRules.doMobSpawning = true
                world.setTimeOfDay(TimeOfDay.Day)
                system.run(() => dim.runCommand("weather clear"))
                system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
                system.run(() => world.afterEvents.entityHitEntity.unsubscribe(buttonHitEvent))
                system.run(() => world.afterEvents.projectileHitEntity.unsubscribe(hitEvent))
                target.setProperty(`${namespace}:button_pressed`, false)
            }, 5)
        }
    })

    const buttonHitEvent = world.afterEvents.entityHitEntity.subscribe(event => {
        const target = event.hitEntity
        if (target.getProperty(`${namespace}:button_pressed`)) return

        if (new Vector3(-245.5, 22, -192.5).equals(target.location) && target.typeId == `${namespace}:button`) {
            dim.getEntitiesAtBlockLocation(new Vector3(-245.5, 22, -192.5)).filter(entity => entity.typeId == `${namespace}:button` && entity.id != target.id).forEach(entity => entity.remove())

            target.setProperty(`${namespace}:button_pressed`, true)
            system.runTimeout(() => {
                clearRuns()
                clearItems()
                clearInvs()
                world.getAllPlayers().forEach(player => {
                    player.teleport(new Vector3(-211.5, 114.94, -198.5), { rotation: { x: 0, y: 180 } })
                    player.setDynamicProperty(`${namespace}:in_tutorial`, false)
                    player.runCommand(`xp -999999L @s`)
                    player.getComponent('inventory').container.addItem(new ItemStack(`${namespace}:guide_book`))
                })
                world.setDynamicProperty(`${namespace}:tutorial_done`, true)
                tutorialRunning = false
                world.setDefaultSpawnLocation(new Vector3(-211, 115, -200))
                world.gameRules.spawnRadius = 5
                world.gameRules.doMobSpawning = true
                world.setTimeOfDay(TimeOfDay.Day)
                system.run(() => dim.runCommand("weather clear"))
                system.run(() => world.afterEvents.playerInteractWithEntity.unsubscribe(buttonEvent))
                system.run(() => world.afterEvents.entityHitEntity.unsubscribe(buttonHitEvent))
                system.run(() => world.afterEvents.projectileHitEntity.unsubscribe(hitEvent))
                target.setProperty(`${namespace}:button_pressed`, false)
            }, 5)
        }
    })

    clearEntities()
    world.setDynamicProperty(`${namespace}:tutorial_room_number`, 6)
}

/**
 * 
 * @param {string} itemId 
 * @returns {string[]}
 */
function findLore(itemId) {
    const itemList = items.legendary.concat(items.rare).concat(items.common)
    for (const item of itemList) {
        if (item.id == itemId) {
            return item.lore
        }
    }
}

/**
 * animates a door opening
 * @param {Vector3} location the bottom middle of the door
 */
function openDoor(location) {
    const dim = world.getDimension("overworld")

    dim.getEntitiesAtBlockLocation(location).filter(entity => entity.typeId == `${namespace}:door`).forEach(entity => {
        entity.setProperty(`${namespace}:open`, true)
        dim.runCommand(`fill ${location.x} ${location.y - 20} ${location.z - 3} ${location.x} ${location.y - 20} ${location.z + 3} minecraft:air`)
    })
}

function spawnEntities1() {
    tutorialEntities = []
    const dim = world.getDimension("overworld")
    const startLen = tutorialEntities.length

    try {
        // start room
        let button = dim.spawnEntity(`${namespace}:button`, new Vector3(-432.5, 20, -273.5))
        button.setProperty(`${namespace}:show_outline`, true)
        tutorialEntities.push(button.id)
        tutorialEntities.push(dim.spawnEntity(`${namespace}:sign`, new Vector3(-438.5, 20, -282.5)).id)

        // treecapitator room
        tutorialEntities.push(dim.spawnEntity(`${namespace}:button`, new Vector3(-394.5, 20, -273.5)).id)
        tutorialEntities.push(dim.spawnEntity(`${namespace}:skip_text`, new Vector3(-394.3, 22, -273.1)).id)

        // mining room
        button = dim.spawnEntity(`${namespace}:button`, new Vector3(-383.5, 20, -272.5))
        button.setProperty(`${namespace}:rotation`, 2)
        tutorialEntities.push(button.id)
        let skipText = dim.spawnEntity(`${namespace}:skip_text`, new Vector3(-383.5, 22, -272.3))
        skipText.setProperty(`${namespace}:rotation`, 2)
        tutorialEntities.push(skipText.id)

        // farming room
        // button = dim.spawnEntity(`${namespace}:button`, new Vector3(-302.5, 20, -274.5))
        // button.setProperty(`${namespace}:rotation`, 2)
        // button.setProperty(`${namespace}:show_outline`, true)
        // tutorialEntities.push(button.id)
        // button = dim.spawnEntity(`${namespace}:button`, new Vector3(-302.5, 20, -278.5))
        // button.setProperty(`${namespace}:rotation`, 0)
        // button.setProperty(`${namespace}:show_outline`, true)
        // tutorialEntities.push(button.id)
        tutorialEntities.push(dim.spawnEntity(`${namespace}:button`, new Vector3(-306.5, 20, -273.50)).id)
        tutorialEntities.push(dim.spawnEntity(`${namespace}:skip_text`, new Vector3(-306.125, 22, -273.1)).id)
    }
    catch (e) {
        tutorialEntities = tutorialEntities.splice(0, startLen)
        system.runTimeout(() => {
            spawnEntities1()
        }, 10)
    }
}

function spawnEntities2() {
    const dim = world.getDimension("overworld")

    // duper room
    tutorialEntities.push(dim.spawnEntity(`${namespace}:block_outline`, new Vector3(-280.5, 23 + 5, -276.5)).id)
    tutorialEntities.push(dim.spawnEntity(`${namespace}:button`, new Vector3(-267.5, 20, -273.5)).id)
    tutorialEntities.push(dim.spawnEntity(`${namespace}:skip_text`, new Vector3(-267.125, 22, -273.1)).id)
    tutorialEntities.push(dim.spawnEntity(`${namespace}:item_duper`, new Vector3(-293.5, 20, -269.5)).id)
    tutorialEntities.push(dim.spawnEntity(`${namespace}:item_duper`, new Vector3(-287.5, 20, -263.5)).id)
    tutorialEntities.push(dim.spawnEntity(`${namespace}:item_duper`, new Vector3(-273.5, 20, -263.5)).id)
    tutorialEntities.push(dim.spawnEntity(`${namespace}:item_duper`, new Vector3(-267.5, 20, -269.5)).id)
    tutorialEntities.push(dim.spawnEntity(`${namespace}:item_duper`, new Vector3(-267.5, 20, -283.5)).id)
    tutorialEntities.push(dim.spawnEntity(`${namespace}:item_duper`, new Vector3(-273.5, 20, -289.5)).id)
    tutorialEntities.push(dim.spawnEntity(`${namespace}:item_duper`, new Vector3(-287.5, 20, -289.5)).id)
    tutorialEntities.push(dim.spawnEntity(`${namespace}:item_duper`, new Vector3(-293.5, 20, -283.5)).id)

    // armor room
    let button = dim.spawnEntity(`${namespace}:button`, new Vector3(-248.5, 20, -264.5))
    button.setProperty(`${namespace}:rotation`, 3)
    tutorialEntities.push(button.id)
    let skipText = dim.spawnEntity(`${namespace}:skip_text`, new Vector3(-248.9, 22, -264.5))
    skipText.setProperty(`${namespace}:rotation`, 3)
    tutorialEntities.push(skipText.id)

    tutorialEntities.push(dim.spawnEntity(`${namespace}:button`, new Vector3(-245.5, 22, -192.5)).id)
    tutorialEntities.push(dim.spawnEntity(`${namespace}:skip_text`, new Vector3(-245.1, 24, -192.1)).id)

    dim.getEntities({ type: `${namespace}:target_block` }).forEach(entity => entity.remove())
    tutorialEntities.push(dim.spawnEntity(`${namespace}:target_block`, new Vector3(-268.5, 23, -198.5)).id)
    tutorialEntities.push(dim.spawnEntity(`${namespace}:target_block`, new Vector3(-270.5, 26, -193.5)).id)
    tutorialEntities.push(dim.spawnEntity(`${namespace}:target_block`, new Vector3(-262.5, 23, -191.5)).id)
}

function placeDoors1() {
    const dim = world.getDimension("overworld")
    const locations = [
        new Vector3(-428.5, 20, -276.5),
        new Vector3(-390.5, 20, -276.5),
        new Vector3(-299.5, 20, -276.5)
    ]

    /**
     * @type {Entity[]}
     */
    let doors = []
    try {
        for (const location of locations) {
            doors.push(dim.spawnEntity(`${namespace}:door`, location).id)
            dim.runCommand(`fill ${location.x} ${location.y - 20} ${location.z - 3} ${location.x} ${location.y - 20} ${location.z + 3} minecraft:border_block`)
        }
    }
    catch (e) {
        dim.runCommand(`kill @e[type=${namespace}:door]`)
        system.runTimeout(() => {
            placeDoors1()
        }, 10)
        return
    }

    tutorialEntities.push(...doors)
}

function placeDoors2() {
    const dim = world.getDimension("overworld")
    const locations = [
        new Vector3(-261.5, 20, -276.5),
        new Vector3(-242.5, 22, -195.5)
    ]

    /**
     * @type {Entity[]}
     */
    let doors = []
    try {
        for (const location of locations) {
            doors.push(dim.spawnEntity(`${namespace}:door`, location).id)
            dim.runCommand(`fill ${location.x} ${location.y - 20} ${location.z - 3} ${location.x} ${location.y - 20} ${location.z + 3} minecraft:border_block`)
        }
    }
    catch (e) {
        dim.runCommand(`kill @e[type=${namespace}:door]`)
        system.runTimeout(() => {
            placeDoors2()
        }, 10)
        return
    }

    tutorialEntities.push(...doors)
}

/**
 * @param {Player} player 
 */
export function teleportToCurrentRoom(player) {
    const roomNumber = world.getDynamicProperty(`${namespace}:tutorial_room_number`)
    const dim = world.getDimension("overworld")

    switch (roomNumber) {
        case 0:
            player.teleport(new Vector3(-445.5, 20, -276.5), { rotation: { x: 0, y: 270 }, dimension: dim })
            break
        case 1:
            player.teleport(new Vector3(-427.5, 20, -276.5), { rotation: { x: 0, y: 270 }, dimension: dim })
            break
        case 2:
            player.teleport(new Vector3(-388.5, 20, -276.5), { rotation: { x: 0, y: 270 }, dimension: dim })
            break
        case 3:
            player.teleport(new Vector3(-337.5, 20, -276.5), { rotation: { x: 0, y: 270 }, dimension: dim })
            break
        case 4:
            player.teleport(new Vector3(-298.5, 20, -276.5), { rotation: { x: 0, y: 270 }, dimension: dim })
            break
        case 5:
            player.teleport(new Vector3(-258.5, 20, -276.5), { rotation: { x: 0, y: 270 }, dimension: dim })
            break
        case 6:
            player.teleport(new Vector3(-248.5, 22, -212.5), { rotation: { x: 0, y: 0 }, dimension: dim })
            break
        default:
            player.teleport(new Vector3(-445.5, 20, -276.5), { rotation: { x: 0, y: 270 }, dimension: dim })
            break
    }
}