import { BlockPermutation, EntityInventoryComponent, EquipmentSlot, GameMode, ItemStack, MolangVariableMap, Player, system, world } from '@minecraft/server';
import { BedrockMod } from './BedrockMod';
import { CoroUtil, debug, err, log } from './util';
//import { Vector3 } from './threejs/Vector3';
import { MODID, biome_themes_from_biome, biome_themes_to_waystone, blocks_allowed_to_gen_over, elevators, solid_ground_blocks_to_gen_on, waystones } from './SharedConstants';
import { ActionFormData, ModalFormData } from '@minecraft/server-ui';
import { ticksSinceModInit } from './main';
//import { Vector3 } from './threejs/Vector3';
//NOT SERIALIZED
export class PlayerData {
    constructor(){
        this.wasSneaking = false;
        this.wasOnGround = true;
        //only used to notify of the v1_1 changes
        this.nearMasterWaystoneForOldGui = false;
    }
}
export class ExplorersWayStones extends BedrockMod {
    getPlayerData(mob) {
        let data = this.playerToData.get(mob.id);
        if (data == null) {
            data = new PlayerData();
            this.playerToData.set(mob.id, data);
        }
        return data;
    }
    getRegionFromPos(pos) {
        return {
            x: Math.floor(pos.x / this.waystoneRegionSize),
            y: pos.y,
            z: Math.floor(pos.z / this.waystoneRegionSize)
        };
    }
    processConfigSetting(event, config) {
        if (event.id == config) {
            try {
                if (event.message == "get") {
                    CoroUtil.trySendMessage(event.sourceEntity, config + "is currently set to " + world.getDynamicProperty(config));
                } else {
                    let value = JSON.parse(event.message);
                    if (typeof value === 'boolean' || typeof value === 'number' || typeof value === 'string' || CoroUtil.isVector3(value)) {
                        world.setDynamicProperty(config, value);
                        CoroUtil.trySendMessage(event.sourceEntity, config + " set to " + world.getDynamicProperty(config));
                    } else {
                        CoroUtil.trySendMessage(event.sourceEntity, "error setting " + config + " to " + world.getDynamicProperty(config));
                    }
                }
            } catch (e) {
                err("error: " + e.message);
                CoroUtil.trySendMessage(event.sourceEntity, "§4error setting " + config + " to " + event.message);
            }
        }
    }
    setPropSafeAndUpdateRuntimeConfig(target, prop, value) {
        CoroUtil.setPropSafe(target, prop, value);
        this.updateRuntimeVarsFromConfigOrSetDefaults();
    }
    updateRuntimeVarsFromConfigOrSetDefaults() {
        CoroUtil.outputDebug = CoroUtil.getPropBoolOrUndefined(world, this.propConfigLoggingDebug) ?? false;
        CoroUtil.outputLog = CoroUtil.getPropBoolOrUndefined(world, this.propConfigLoggingLog) ?? false;
        CoroUtil.outputError = CoroUtil.getPropBoolOrUndefined(world, this.propConfigLoggingError) ?? false;
        this.configElevatorSearchDist = CoroUtil.getPropNumberOrUndefined(world, this.propConfigElevatorSearchDist) ?? 50;
        this.configTimesPerIterationToFindGoodXZPosSurface = CoroUtil.getPropNumberOrUndefined(world, this.propConfigTimesPerIterationToFindGoodXZPosSurface) ?? 20;
        this.configTimesPerIterationToFindGoodXZPosCave = CoroUtil.getPropNumberOrUndefined(world, this.propConfigTimesPerIterationToFindGoodXZPosCave) ?? 20;
        this.configMaxScanDownDistanceFromPosCave = CoroUtil.getPropNumberOrUndefined(world, this.propConfigMaxScanDownDistanceFromPosCave) ?? 30;
        this.configMasterWaystonePistonNoise = CoroUtil.getPropBoolOrUndefined(world, this.propConfigMasterWaystonePistonNoise) ?? true;
        this.configMasterWaystoneGUIv1_1 = CoroUtil.getPropBoolOrUndefined(world, this.propConfigMasterWaystoneGUIv1_1) ?? true;
        this.configGenWaystones = CoroUtil.getPropBoolOrUndefined(world, this.propConfigGenWaystones) ?? true;
        this.configEnableMasterPlayerWayfinder = CoroUtil.getPropBoolOrUndefined(world, this.propConfigEnableMasterPlayerWayfinder) ?? true;
    }
    initBootstrap() {
        /**
         * /scriptevent razz_ew:config_logging_debug true
         * /scriptevent razz_ew:config_logging_log true
         * /scriptevent razz_ew:config_logging_error true
         */ //TODO post release: refactor this code to be a common util thing so its easily copied to other mods
        //TODO: TEMP for testing new world gen
        CoroUtil.outputDebug = true;
        CoroUtil.outputLog = true;
        CoroUtil.outputError = true;
        /* let players = world.getPlayers();
        if (players.length > 0) {
            if (ticksSinceModInit == 0) {
                players[0].runCommandAsync("/script profiler start")
            } else if (ticksSinceModInit == 60) {
                players[0].runCommandAsync("/script profiler stop")
            }
        } */ this.updateRuntimeVarsFromConfigOrSetDefaults();
        log(MODID + " mod init");
        system.afterEvents.scriptEventReceive.subscribe((event)=>{
            /* log("s " + event.id);
			log("s " + event.message);
			log("s " + event.sourceEntity?.typeId) */ try {
                if (event.id.startsWith(MODID)) {
                    //process configs
                    this.processConfigSetting(event, this.propConfigLoggingDebug);
                    this.processConfigSetting(event, this.propConfigLoggingLog);
                    this.processConfigSetting(event, this.propConfigLoggingError);
                    this.processConfigSetting(event, this.propConfigElevatorSearchDist);
                    this.processConfigSetting(event, this.propConfigMasterWaystonePistonNoise);
                    this.updateRuntimeVarsFromConfigOrSetDefaults();
                    if (event.id == MODID + ":test") {
                        if (event.sourceEntity instanceof Player) {
                            let player = event.sourceEntity;
                            const form = new ActionFormData();
                            form.title("Minigames");
                            form.body("Choose the games");
                            form.button("Spleef", "textures/items/diamond_shovel");
                            form.button("Murder Mystery", "textures/items/iron_sword");
                            form.button("Bedwars", "textures/minigames/bedwars.png");
                            form.show(player).then((response)=>{
                                if (response.canceled) {
                                    player.sendMessage("Canceled due to " + response.cancelationReason);
                                }
                                if (response.selection == 0) {
                                    player.sendMessage("You have selected Spleef");
                                }
                                if (response.selection == 1) {
                                    player.sendMessage("You have selected Murder Mystery");
                                }
                                if (response.selection == 2) {
                                    player.sendMessage("You have selected Bedwars");
                                }
                                return;
                            }); // show player the form
                        }
                    }
                    if (event.id == MODID + ":get_biome" && event.sourceEntity !== undefined) {
                        console.warn("!!!");
                        let tryPos = {
                            x: event.sourceEntity.location.x,
                            y: event.sourceEntity.location.y,
                            z: event.sourceEntity.location.z
                        };
                        var ent = event.sourceEntity.dimension.spawnEntity(MODID + ":biome_getter", {
                            x: tryPos.x,
                            y: tryPos.y,
                            z: tryPos.z
                        });
                        log("biome here: " + this.getBiomeFromBiomeGetter(ent));
                        CoroUtil.despawnEntity(ent);
                    }
                    if (event.id == MODID + ":reset_all") {
                        log("clearDynamicProperties");
                        world.clearDynamicProperties();
                    }
                    if (event.id == MODID + ":init") {
                        log("init");
                        this.init();
                    }
                /* if (event.id == MODID + ":test2") {
                        log("init");
                        this.testGui(event.sourceEntity);
                    } */ }
            } catch (e) {
                err(e.message);
            }
        });
        world.beforeEvents.worldInitialize.subscribe((event)=>{
            event.blockComponentRegistry.registerCustomComponent("razz_ew" + ":interact_top", {
                onPlayerInteract: (e)=>{
                    //console.warn("wat " + e.block.x);
                    if (e.player && this.configMasterWaystoneGUIv1_1) {
                        const equippable = e.player.getComponent("equippable");
                        let itemStack = equippable.getEquipment(EquipmentSlot.Mainhand);
                        if (itemStack !== undefined && (itemStack.typeId == this.itemNameWayfinderSimple || itemStack.typeId == this.itemNameWayfinderClairvoyant || itemStack.typeId == this.itemNameWayfinderEcho || itemStack.typeId == this.itemNameWayfinderMaster)) {} else {
                            if (this.isMasterWaystoneEntityHere(e.dimension, CoroUtil.add(e.block, 0, -1, 0))) {
                                this.openMasterWaystoneGUIv1_1(e.dimension, e.player, CoroUtil.add(e.block, 0, -1, 0));
                            }
                        }
                    }
                }
            });
            event.blockComponentRegistry.registerCustomComponent("razz_ew" + ":interact_base", {
                onPlayerInteract: (e)=>{
                    //console.warn("wat " + e.block.x);
                    if (e.player && this.configMasterWaystoneGUIv1_1) {
                        const equippable = e.player.getComponent("equippable");
                        let itemStack = equippable.getEquipment(EquipmentSlot.Mainhand);
                        if (itemStack !== undefined && (itemStack.typeId == this.itemNameWayfinderSimple || itemStack.typeId == this.itemNameWayfinderClairvoyant || itemStack.typeId == this.itemNameWayfinderEcho || itemStack.typeId == this.itemNameWayfinderMaster)) {} else {
                            if (this.isMasterWaystoneEntityHere(e.dimension, e.block)) {
                                this.openMasterWaystoneGUIv1_1(e.dimension, e.player, e.block);
                            }
                        }
                    }
                }
            });
        });
    }
    init() {
        super.init();
        if (this.modActive) {
            return;
        }
        this.modActive = true;
        world.afterEvents.dataDrivenEntityTrigger.subscribe((event)=>{
            /* log("dataDrivenEntityTrigger "+event.entity.typeId)
            log("dataDrivenEntityTrigger "+event.eventId) */ //log(""+event.getModifiers)
            if (event.eventId == "event:on_interact") {
                if (event.entity.typeId == this.entityNameWaystoneMasterMenuDestination) {
                    const run = system.run(()=>{
                        this.interactMasterWaystoneMenuDestination(event.entity);
                    });
                } else if (event.entity.typeId == this.entityNameWayfinder) {
                    this.turnWayfinderEntityIntoItem(event.entity);
                }
            }
        });
        world.afterEvents.entityHitEntity.subscribe((event)=>{
            //log("entityHitEntity: " + CoroUtil.vector3ToString(event.hitEntity.location))
            if (event.hitEntity.typeId == this.entityNameWaystoneMasterMenuDestination) {
                //CoroUtil.printVector3(event.hitEntity.location);
                if (event.damagingEntity instanceof Player) {
                    //log("id: " + event.hitEntity.id)
                    this.hitMasterWaystoneMenuDestination(event.hitEntity, event.damagingEntity);
                }
            }
        });
        world.afterEvents.entityHitBlock.subscribe((event)=>{
            if (event.damagingEntity instanceof Player) {
            //log(CoroUtil.toStr(event.hitBlock.location));
            /* if (event.hitBlock.permutation.matches(this.blockNameWaystoneMasterBase)) {
					const run = system.run(() => {
						this.openMasterWaystoneGUI(event.damagingEntity.dimension, event.damagingEntity as Player, event.hitBlock);
					});
				} else if (event.hitBlock.permutation.matches(this.blockNameWaystoneMasterTop)) {
					const run = system.run(() => {
						this.openMasterWaystoneGUI(event.damagingEntity.dimension, event.damagingEntity as Player, CoroUtil.up(event.hitBlock, -1));
					});
				} */ }
        });
        world.beforeEvents.playerBreakBlock.subscribe((event)=>{
            //log("hook break: " + event.block.isAir);
            if (event.itemStack !== undefined) {
                event.itemStack.getComponents();
            }
            if (this.isElevator(event.block)) {
                const run = system.run(()=>{
                    this.updateElevatorsActivatedState(event.player.dimension, event.block);
                });
            } /* else if (event.block.permutation.matches(this.blockNameWaystoneMasterBase)) {
				const run = system.run(() => {
					if (CoroUtil.getBlock(event.dimension, CoroUtil.up(event.block, 1))?.permutation.matches(this.blockNameWaystoneMasterTop)) {
						CoroUtil.setBlockAt(event.dimension, { x: event.block.x, 
							y: event.block.y + 1, 
							z: event.block.z}, "minecraft:air");
					}
					if (CoroUtil.getGameMode(event.player) != GameMode.creative) {
						event.player.dimension.spawnItem(new ItemStack(this.blockNameWaystoneMasterFull), event.block);
					}
					this.removeMasterWaystoneEntity(event.dimension, event.block);
				});
			} else if (event.block.permutation.matches(this.blockNameWaystoneMasterTop)) {
				const run = system.run(() => {
					if (CoroUtil.getBlock(event.dimension, CoroUtil.up(event.block, -1))?.permutation.matches(this.blockNameWaystoneMasterBase)) {
						CoroUtil.setBlockAt(event.dimension, { x: event.block.x, 
							y: event.block.y - 1, 
							z: event.block.z}, "minecraft:air");
					}
					if (CoroUtil.getGameMode(event.player) != GameMode.creative) {
						event.player.dimension.spawnItem(new ItemStack(this.blockNameWaystoneMasterFull), event.block);
					}
					this.removeMasterWaystoneEntity(event.dimension, CoroUtil.up(event.block, -1));
				});
			} */ 
            if (this.isWaystone(event.block)) {
                this.removeWaystoneLocation(event.dimension, event.block);
            }
        });
        world.afterEvents.playerPlaceBlock.subscribe((event)=>{
            if (this.isElevator(event.block)) {
                //log("hook place")
                this.updateElevatorsDeactivatedState(event.player.dimension, event.block);
            } else if (event.block.permutation.matches(this.blockNameWaystoneMasterFull)) {
                const run = system.run(()=>{});
                this.tryReplaceMasterWaystoneMultiblockFromFullBlock(event.player, event.block);
            }
            if (this.isWaystone(event.block)) {
                this.addWaystoneLocation(event.dimension, event.block);
            }
        });
        world.beforeEvents.itemUseOn.subscribe((event)=>{
            log("itemUseOn");
            const source = event.source;
            if (event.itemStack !== undefined) {
                let itemStack = event.itemStack;
                if (itemStack.typeId == this.itemNameWayfinderSimple || itemStack.typeId == this.itemNameWayfinderClairvoyant || itemStack.typeId == this.itemNameWayfinderEcho || itemStack.typeId == this.itemNameWayfinderMaster) {
                    let tryBindWayfinderToWaystone = false;
                    let wayfinderAlreadyBound = false;
                    let bindFail = false;
                    let posToWarpTo = event.block;
                    let posBound = this.getWayfinderPosFromLore(itemStack, false);
                    let dimBound = this.getWayfinderDimFromLore(itemStack, false);
                    if (posBound !== undefined) {
                        wayfinderAlreadyBound = true;
                    }
                    //log("wayfinderAlreadyBound " + wayfinderAlreadyBound)
                    //TODO: master wayfinder logic to flag wayfinderAlreadyBound correctly
                    if (itemStack.typeId == this.itemNameWayfinderMaster) {
                        let pos = event.block;
                        if (event.block.permutation.matches(this.blockNameWaystoneMasterTop)) {
                            pos = CoroUtil.up(pos, -1);
                        }
                        if (/* !event.source.isSneaking &&  */ this.doesMasterWayfinderContainDestination(event.source, event.source.dimension, pos, itemStack, event.source.isSneaking)) {
                            log("bound already");
                            wayfinderAlreadyBound = true;
                        }
                        if (event.source.isSneaking) {
                            wayfinderAlreadyBound = true;
                        }
                    }
                    //var block: Block | undefined = CoroUtil.getBlock(source.dimension, data.block);
                    let posMasterBase = undefined;
                    if (event.block.permutation.matches(this.blockNameWaystoneMasterBase) && CoroUtil.getBlock(event.source.dimension, CoroUtil.up(event.block, 1))?.permutation.matches(this.blockNameWaystoneMasterTop)) {
                        posMasterBase = event.block;
                    } else if (event.block.permutation.matches(this.blockNameWaystoneMasterTop) && CoroUtil.getBlock(event.source.dimension, CoroUtil.up(event.block, -1))?.permutation.matches(this.blockNameWaystoneMasterBase)) {
                        posMasterBase = CoroUtil.up(event.block, -1);
                        posToWarpTo = CoroUtil.up(event.block, -1);
                    }
                    let tryToBindToMasterWaystone = false;
                    if (itemStack.typeId !== this.itemNameWayfinderMaster) {
                        if (posMasterBase !== undefined) {
                            if (!wayfinderAlreadyBound) {
                                tryBindWayfinderToWaystone = true;
                                const run = system.run(()=>{
                                    //CoroUtil.setBlockAtWithState(source.dimension, event.block, MODID + ":waystone_biome_" + waystone_name + "_pillar_activated", state, dir);
                                    let blockBase = CoroUtil.getBlock(event.source.dimension, posMasterBase);
                                    if (blockBase !== undefined && blockBase.permutation.matches(this.blockNameWaystoneMasterBase)) {
                                        blockBase.setPermutation(blockBase.permutation.withState(this.blockPermutationMasterWaystoneActivated, true));
                                    }
                                    let blockTop = CoroUtil.getBlock(event.source.dimension, CoroUtil.up(posMasterBase, 1));
                                    if (blockTop !== undefined && blockTop.permutation.matches(this.blockNameWaystoneMasterTop)) {
                                        blockTop.setPermutation(blockTop.permutation.withState(this.blockPermutationMasterWaystoneActivated, true));
                                    }
                                    source.playSound("random.levelup", {
                                        volume: 0.1
                                    });
                                    source.dimension.spawnParticle("razz_ew:wayfinder_charge_explode_activate", CoroUtil.centerXZ(event.block));
                                    let data = this.getOrCreateMasterWaystoneEntity(event.source.dimension, posMasterBase);
                                });
                            } else {
                                tryToBindToMasterWaystone = true;
                            }
                        }
                    } else {
                        //let master wayfinder try to add master waystone, already bound logic further down
                        tryBindWayfinderToWaystone = true;
                    }
                    if (!tryBindWayfinderToWaystone) {
                        for(const waystone_name in waystones.naturally_generated){
                            let waystone_data = waystones.naturally_generated[waystone_name];
                            //log(waystone_name)
                            if (event.block?.permutation.matches(MODID + ":waystone_biome_" + waystone_name + "_pillar")) {
                                let state = "minecraft:cardinal_direction";
                                let dir = event.block?.permutation.getState(state);
                                //log("activate")
                                tryBindWayfinderToWaystone = true;
                                //this.itemWasUsedOnBlock = true;
                                this.setPlayerUsedWayfinderOnBlock(event.source);
                                if (!wayfinderAlreadyBound) {
                                    const run = system.run(()=>{
                                        CoroUtil.setBlockAtWithState(source.dimension, event.block, MODID + ":waystone_biome_" + waystone_name + "_pillar_activated", state, dir);
                                        source.playSound("random.levelup", {
                                            volume: 0.1
                                        });
                                        source.dimension.spawnParticle("razz_ew:wayfinder_charge_explode_activate", CoroUtil.centerXZ(event.block));
                                    });
                                }
                                break;
                            }
                            if (event.block?.permutation.matches(MODID + ":waystone_biome_" + waystone_name + "_pillar_activated")) {
                                tryBindWayfinderToWaystone = true;
                                //this.itemWasUsedOnBlock = true;
                                this.setPlayerUsedWayfinderOnBlock(event.source);
                                break;
                            }
                        }
                    }
                    if (!tryBindWayfinderToWaystone) {
                        for(const waystone_name in waystones.player){
                            let waystone_data = waystones.player[waystone_name];
                            //log(waystone_name)
                            if (event.block?.permutation.matches(MODID + ":waystone_player_" + waystone_name)) {
                                let state = "minecraft:cardinal_direction";
                                let dir = event.block?.permutation.getState(state);
                                //log("activate")
                                tryBindWayfinderToWaystone = true;
                                //this.itemWasUsedOnBlock = true;
                                this.setPlayerUsedWayfinderOnBlock(event.source);
                                if (!wayfinderAlreadyBound) {
                                    const run = system.run(()=>{
                                        CoroUtil.setBlockAtWithState(source.dimension, event.block, MODID + ":waystone_player_" + waystone_name + "_activated", state, dir);
                                        source.playSound("random.levelup", {
                                            volume: 0.1
                                        });
                                        source.dimension.spawnParticle("razz_ew:wayfinder_charge_explode_activate", CoroUtil.centerXZ(event.block));
                                    });
                                }
                                break;
                            }
                            if (event.block?.permutation.matches(MODID + ":waystone_player_" + waystone_name + "_activated")) {
                                tryBindWayfinderToWaystone = true;
                                //this.itemWasUsedOnBlock = true;
                                this.setPlayerUsedWayfinderOnBlock(event.source);
                                break;
                            }
                        }
                    }
                    if (posBound !== undefined && dimBound !== undefined && event.block?.permutation.matches("minecraft:lectern")) {
                        const run = system.run(()=>{
                            if (posBound !== undefined && dimBound !== undefined) {
                                //this.itemWasUsedOnBlock = true;
                                this.setPlayerUsedWayfinderOnBlock(event.source);
                                let posEnt = CoroUtil.add(event.block, 0.5, 1.0, 0.5);
                                let entCheck = event.source.dimension.getEntities({
                                    type: this.entityNameWayfinder,
                                    location: posEnt,
                                    maxDistance: 0.5
                                });
                                if (entCheck.length == 0) {
                                    var ent = event.source.dimension.spawnEntity(this.entityNameWayfinder, posEnt);
                                    let id = 0;
                                    if (itemStack.typeId == this.itemNameWayfinderClairvoyant) {
                                        id = 1;
                                    } else if (itemStack.typeId == this.itemNameWayfinderEcho) {
                                        id = 2;
                                    }
                                    ent.setProperty("rb:wayfinder_id", id);
                                    let posRecall = this.getWayfinderPosFromLore(itemStack, true);
                                    let dimRecall = this.getWayfinderDimFromLore(itemStack, true);
                                    //log("???");
                                    if (posRecall == undefined) {
                                        posRecall = CoroUtil.copyVector3(posBound);
                                        posRecall.y = -999;
                                    }
                                    if (dimRecall == undefined) {
                                        dimRecall = dimBound;
                                    }
                                    let jsonProperty = {
                                        wayfinder_id: itemStack.typeId,
                                        dim: CoroUtil.dimStringIDToNumberID(dimBound.id),
                                        x: posBound.x,
                                        y: posBound.y,
                                        z: posBound.z,
                                        dim_recall: CoroUtil.dimStringIDToNumberID(dimRecall.id),
                                        x_recall: posRecall.x,
                                        y_recall: posRecall.y,
                                        z_recall: posRecall.z
                                    };
                                    ent.setDynamicProperty(this.propWayfinderEntityData, JSON.stringify(jsonProperty));
                                    CoroUtil.setSlot(event.source, undefined, EquipmentSlot.Mainhand);
                                }
                            }
                        });
                    }
                    if (itemStack.typeId !== this.itemNameWayfinderMaster) {
                        log("tryBindWayfinderToWaystone " + tryBindWayfinderToWaystone);
                        if (tryBindWayfinderToWaystone) {
                            this.setPlayerUsedWayfinderOnBlock(event.source);
                            event.source.setDynamicProperty(this.propPlayerWayfinderUseCooldown, this.playerWayfinderUseCooldownMax);
                            if (!wayfinderAlreadyBound) {
                                const run = system.run(()=>{
                                    this.setWayfinderPosToLore(itemStack, event.source.dimension, posToWarpTo, undefined, undefined);
                                    CoroUtil.setSlot(event.source, itemStack, EquipmentSlot.Mainhand);
                                    let str = (CoroUtil.vector3ToString(posToWarpTo) + "," + event.source.dimension.id.replace("minecraft:", "")).replace(new RegExp("\\,", "g"), ", ");
                                    this.subtitleRaw(event.source, '{"rawtext":[{"translate":"msg.wayfinder_bound1"}]}', '{"rawtext":[{"translate":"msg.wayfinder_bound2"}, {"text": " ' + str + '"}]}');
                                });
                            } else {
                                this.titleRaw(event.source, '{"rawtext":[{"translate":"msg.wayfinder_bound_already"}]}');
                                this.actionBarRaw(event.source, '{"rawtext":[{"translate":"msg.wayfinder_bound_recipe"}]}');
                            }
                        }
                    } else {
                        log("tryBindWayfinderToWaystone " + tryBindWayfinderToWaystone);
                        if (tryBindWayfinderToWaystone /*  || tryToBindToMasterWaystone */ ) {
                            let pos = event.block;
                            if (event.block.permutation.matches(this.blockNameWaystoneMasterTop)) {
                                pos = CoroUtil.up(pos, -1);
                            }
                            //if (!this.doesMasterWayfinderContainDestination(event.source, event.source.dimension, pos, itemStack, event.source.isSneaking)) {
                            //log("????")
                            const run = system.run(()=>{
                                let returnVal = this.useMasterWayfinderOnAnyWaystone(event.source, event.source.dimension, pos, itemStack, event.source.isSneaking);
                                //decided to handle player facing feedback of returnVal in useMasterWayfinderOnAnyWaystone, so its not needed here probably
                                event.source.setDynamicProperty(this.propPlayerWayfinderUseCooldown, this.playerWayfinderUseCooldownMax);
                            });
                            this.setPlayerUsedWayfinderOnBlock(event.source);
                            //}
                            if (wayfinderAlreadyBound && !event.source.isSneaking) {
                                this.subtitleRaw(event.source, '{"rawtext":[{"translate":"msg.wayfinder_master_bound_already1"}]}', '{"rawtext":[{"translate":"msg.wayfinder_master_bound_already2"}]}');
                            //this.actionBarRaw(event.source, '{"rawtext":[{"translate":"msg.wayfinder_bound_recipe"}]}')
                            } else if (!wayfinderAlreadyBound && event.source.isSneaking) {
                            //could say 'nothing to remove' but ehhh
                            }
                        }
                        if (tryToBindToMasterWaystone) {
                            log("no, not allowed");
                        }
                    }
                    //if no bind to wayfinder happened and its not a master wayfinder, bind it to master waystone
                    if (/* !tryBindWayfinderToWaystone &&  */ tryToBindToMasterWaystone && itemStack.typeId !== this.itemNameWayfinderMaster) {
                        //log(CoroUtil.toStr(event.block.location));
                        if (event.block.permutation.matches(this.blockNameWaystoneMasterBase) && CoroUtil.getBlock(event.source.dimension, CoroUtil.up(event.block, 1))?.permutation.matches(this.blockNameWaystoneMasterTop)) {
                            //log("wayfinder used on master waystone");
                            //this.itemWasUsedOnBlock = true;
                            this.setPlayerUsedWayfinderOnBlock(event.source);
                            const run = system.run(()=>{
                                this.useWayfinderOnMasterWaystone(event.source.dimension, event.block, itemStack, event.source.isSneaking);
                            });
                        } else if (event.block.permutation.matches(this.blockNameWaystoneMasterTop) && CoroUtil.getBlock(event.source.dimension, CoroUtil.up(event.block, -1))?.permutation.matches(this.blockNameWaystoneMasterBase)) {
                            //log("wayfinder used on master waystone");
                            //this.itemWasUsedOnBlock = true;
                            this.setPlayerUsedWayfinderOnBlock(event.source);
                            const run = system.run(()=>{
                                this.useWayfinderOnMasterWaystone(event.source.dimension, CoroUtil.up(event.block, -1), itemStack, event.source.isSneaking);
                            });
                        }
                    }
                } else {
                //log(CoroUtil.toStr(event.block.location));
                /* if (event.block.permutation.matches(this.blockNameWaystoneMasterBase)) {
						const run = system.run(() => {
							this.openMasterWaystoneGUI(event.source.dimension, event.source, event.block);
						});
					} else if (event.block.permutation.matches(this.blockNameWaystoneMasterTop)) {
						const run = system.run(() => {
							this.openMasterWaystoneGUI(event.source.dimension, event.source, CoroUtil.up(event.block, -1));
						});
					} */ }
            /* if (itemStack.typeId == this.itemNameWayfinderMaster) {
                    //this.setPlayerUsedMasterWayfinderOnBlock(event.source);

                    let posMasterBase: Vector3 | undefined = undefined;
                    if (event.block.permutation.matches(this.blockNameWaystoneMasterBase) && CoroUtil.getBlock(event.source.dimension, CoroUtil.up(event.block, 1))?.permutation.matches(this.blockNameWaystoneMasterTop)) {
                        posMasterBase = event.block;
                    } else if (event.block.permutation.matches(this.blockNameWaystoneMasterTop) && CoroUtil.getBlock(event.source.dimension, CoroUtil.up(event.block, -1))?.permutation.matches(this.blockNameWaystoneMasterBase)) {
                        posMasterBase = CoroUtil.up(event.block, -1);
                    }

                } */ }
        });
        world.afterEvents.itemStartUse.subscribe((event)=>{
            let itemStack = event.itemStack;
            log("itemStartUse " + itemStack.typeId);
            if (itemStack.typeId == this.itemNameWayfinderSimple || itemStack.typeId == this.itemNameWayfinderClairvoyant || itemStack.typeId == this.itemNameWayfinderEcho || itemStack.typeId == this.itemNameWayfinderWildcard) {
                if (event.source instanceof Player) {
                    let player = event.source;
                    if (CoroUtil.getPropNumber(player, this.propPlayerWayfinderUseCooldown) <= 0) {
                        if (this.getWayfinderPosFromLore(itemStack, false) !== undefined || itemStack.typeId == this.itemNameWayfinderWildcard) {
                            player.setDynamicProperty(this.propPlayerStartedWarpCount, true);
                        //player.setDynamicProperty(this.propPlayerIsUsingWayfinder, true);
                        } else {
                            this.subtitleRaw(player, '{"rawtext":[{"translate":"msg.wayfinder_not_bound1"}]}', '{"rawtext":[{"translate":"msg.wayfinder_not_bound2"}]}');
                        }
                    } else {
                        log("on cooldown");
                    }
                }
            }
            if (itemStack.typeId == this.itemNameWarperPlayerMaster) {
                if (event.source instanceof Player) {
                    let player = event.source;
                    if (this.configEnableMasterPlayerWayfinder) {
                        this.openMasterPlayerWayfinderGUI(player);
                    } else {
                        this.actionBarRaw(player, '{"rawtext":[{"translate":"msg.wayfinder_player_master_disabled"}]}');
                    }
                }
            }
            if (itemStack.typeId == this.itemNameWarperPlayer) {}
        });
        /* world.afterEvents.itemStopUse.subscribe(event => {
            let itemStack: ItemStack | undefined = event.itemStack;

            if (itemStack !== undefined) {
                log("itemStopUse " + itemStack.typeId);
                if (itemStack.typeId == this.itemNameWayfinderSimple
                    || itemStack.typeId == this.itemNameWayfinderClairvoyant
                    || itemStack.typeId == this.itemNameWayfinderEcho
                    || itemStack.typeId == this.itemNameWayfinderWildcard) {
                    if (event.source instanceof Player) {
                        let player: Player = event.source;
                        player.setDynamicProperty(this.propPlayerIsUsingWayfinder, false);
                    }
                }
            }
        }); */ world.beforeEvents.itemUse.subscribe((event)=>{
            const source = event.source;
            let itemStack = event.itemStack;
            //log("source: " + source.name);
            //log("item use " + itemStack.typeId);
            if (itemStack.typeId == this.itemNameDevTool) {
                //let gameMode = CoroUtil.getPropNumber(event.source, "gameMode");
                if (event.source instanceof Player) {
                    let player = event.source;
                    if (player.isSneaking || !player.isOnGround) {
                        //log("reload mod")
                        //player.runCommandAsync("reload");
                        player.runCommandAsync("tp ^ ^ ^100");
                    } else {
                        let gameMode = CoroUtil.getGameMode(player);
                        if (gameMode == GameMode.survival) {
                            player.runCommandAsync("gamemode creative @s");
                        } else if (gameMode == GameMode.creative) {
                            player.runCommandAsync("gamemode survival @s");
                        }
                    }
                }
            } else if (itemStack.typeId == this.itemNameWayfinderMaster) {
                if (!this.getPlayerUsedWayfinderOnBlock(event.source)) {
                    const run = system.run(()=>{
                        this.openMasterWaystoneGUIv1_1(event.source.dimension, event.source, itemStack);
                    });
                }
            }
        });
        world.afterEvents.itemUse.subscribe((event)=>{
            log("itemUse");
            const source = event.source;
            let itemStack = event.itemStack;
            if (itemStack.typeId == this.itemNameGuide) {
                if (event.source instanceof Player) {
                    let player = event.source;
                    player.playSound("mob.endermen.portal");
                    this.openGuidebook(player);
                }
            } else if (itemStack.typeId == this.itemNameWarperPlayer) {
                if (event.source instanceof Player) {
                    let player = event.source;
                    let closestPlayer;
                    let entities = player.getEntitiesFromViewDirection({
                        maxDistance: 3
                    });
                    let dist = 999;
                    for (let entity of entities){
                        if (entity.entity instanceof Player) {
                            if (entity.distance < dist) {
                                dist = entity.distance;
                                closestPlayer = entity.entity;
                            }
                        }
                    }
                    if (closestPlayer) {
                        //log("? " + closestPlayer.name);
                        if (itemStack.getDynamicProperty(this.propItemBoundPlayerUsername) === undefined) {
                            CoroUtil.setPropSafe(itemStack, this.propItemBoundPlayerUsername, closestPlayer.name);
                            itemStack.setLore([
                                "Bound to " + closestPlayer.name
                            ]);
                            CoroUtil.setSlot(event.source, itemStack, EquipmentSlot.Mainhand);
                            this.subtitleRaw(event.source, '{"rawtext":[{"translate":"msg.wayfinder_player_bound1"}', '{"rawtext":[{"translate":"msg.wayfinder_player_bound2"}, {"text": " ' + closestPlayer.name + '"}]}');
                            player.sendMessage({
                                rawtext: [
                                    {
                                        translate: "msg.wayfinder_player_bound"
                                    },
                                    {
                                        text: " " + closestPlayer.name
                                    }
                                ]
                            });
                        } else {
                            this.subtitleRaw(event.source, '{"rawtext":[{"translate":"msg.wayfinder_player_bound_already1"}]}', '{"rawtext":[{"translate":"msg.wayfinder_player_bound_already2"}]}');
                            this.actionBarRaw(event.source, '{"rawtext":[{"translate":"msg.wayfinder_player_bound_recipe"}]}');
                        }
                    } else {
                        let username = CoroUtil.getPropString(itemStack, this.propItemBoundPlayerUsername);
                        if (itemStack.getDynamicProperty(this.propItemBoundPlayerUsername) !== undefined) {
                            let username = CoroUtil.getPropString(itemStack, this.propItemBoundPlayerUsername);
                            let playerTarget = CoroUtil.getPlayerByName(username);
                            if (playerTarget) {
                                //this.tryTeleportWithCost(player, playerTarget.dimension, playerTarget.location, true);
                                //CoroUtil.setPropSafe(player, this.propPlayerPlayerWayfinderCountdownToWarp, this.warpDelay);
                                player.setDynamicProperty(this.propPlayerStartedWarpCount, true);
                                CoroUtil.setPropSafe(player, this.propPlayerPlayerWayfinderUsername, playerTarget.name);
                            } else {
                                let data = this.getPlayerMasterWayfinderData(player);
                                if (username in data) {
                                    this.openMasterPlayerWayfinderGUIConfirmTP(player, data[username]);
                                } else {
                                    this.actionBarRaw(player, '{"rawtext":[{"translate":"msg.wayfinder_player_offline"}]}');
                                }
                            }
                        } else {
                            this.actionBarRaw(player, '{"rawtext":[{"translate":"msg.wayfinder_player_not_bound"}]}');
                        }
                    }
                }
            }
        });
    }
    getRandomProperty(obj) {
        const keys = Object.keys(obj);
        const randomIndex = Math.floor(Math.random() * keys.length);
        return keys[randomIndex];
    }
    getFirstFarAwayWaystone(player, waystones, maxDist) {
        let shuffledProperties = this.shuffleProperties(waystones);
        let pos = undefined;
        let tryCount = 0;
        for(let waystonePos in shuffledProperties){
            pos = CoroUtil.stringToVector3(waystonePos);
            if (CoroUtil.distToCoord(player.location, pos) > maxDist) {
                return pos;
            }
            tryCount++;
            if (tryCount >= 50) break;
        }
        return undefined;
    }
    shuffleProperties(obj) {
        const keys = Object.keys(obj);
        const shuffledKeys = this.shuffleArray(keys);
        const newObj = {};
        for (const key of shuffledKeys){
            newObj[key] = obj[key];
        }
        return newObj;
    }
    shuffleArray(array) {
        const shuffled = [
            ...array
        ];
        for(let i = shuffled.length - 1; i > 0; i--){
            const j = Math.floor(Math.random() * (i + 1));
            [shuffled[i], shuffled[j]] = [
                shuffled[j],
                shuffled[i]
            ];
        }
        return shuffled;
    }
    titleClear(player) {
        player.runCommandAsync("title @s clear");
    }
    titleTimes(player, fadeIn, stay, fadeOut) {
        player.runCommandAsync("title @s times " + fadeIn + " " + stay + " " + fadeOut);
        this.titleTimesMax = fadeIn + stay + fadeOut;
    }
    /* subtitle(player: Player, text: string) {
		//player.runCommandAsync("title @s clear");
		player.runCommandAsync("title @s subtitle " + text);
		//player.runCommandAsync("title @s title §");
        player.setDynamicProperty(this.propPlayerTitleTimes, this.titleTimesMax)
	} */ titleRaw(player, titletext) {
        //player.runCommandAsync("title @s clear");
        player.runCommandAsync("titleraw @s title " + titletext);
        player.setDynamicProperty(this.propPlayerTitleTimes, this.titleTimesMax);
    }
    /**
     * NO LONGER USABLE with just a subtitle, HAS to display a title too to show it, and cant be blank, will always show the BS square translucent box
     */ subtitleRaw(player, titletext, subtitletext) {
        //player.runCommandAsync("title @s clear");
        player.runCommandAsync("titleraw @s subtitle " + subtitletext);
        player.runCommandAsync("titleraw @s title " + titletext);
        player.setDynamicProperty(this.propPlayerTitleTimes, this.titleTimesMax);
    }
    subtitleRawSimple(player, titletext, subtitletext) {
        //player.runCommandAsync("title @s clear");
        player.runCommandAsync("titleraw @s subtitle " + '{"rawtext":[{"translate":"' + subtitletext + '"}]}');
        player.runCommandAsync("titleraw @s title " + '{"rawtext":[{"translate":"' + titletext + '"}]}');
        player.setDynamicProperty(this.propPlayerTitleTimes, this.titleTimesMax);
    }
    actionBar(player, text) {
        //player.runCommandAsync("title @s clear");
        player.runCommandAsync("title @s actionbar " + text);
    //player.runCommandAsync("title @s title §");
    }
    actionBarRaw(player, text) {
        //player.runCommandAsync("title @s clear");
        player.runCommandAsync("titleraw @s actionbar " + text);
    //player.runCommandAsync("title @s title §");
    }
    actionBarRawSimple(player, text) {
        //player.runCommandAsync("title @s clear");
        player.runCommandAsync("titleraw @s actionbar " + '{"rawtext":[{"translate":"' + text + '"}]}');
    //player.runCommandAsync("title @s title §");
    }
    /**
	 * Try to replace the block if theres room, if there isnt, break block and give back item
	 * 
	 * @param pos 
	 */ tryReplaceMasterWaystoneMultiblockFromFullBlock(player, pos) {
        let dimension = player.dimension;
        if (CoroUtil.getBlock(dimension, CoroUtil.up(pos, 1))?.isAir) {
            /* CoroUtil.setBlockAt(dimension, { x: pos.x, 
				y: pos.y, 
				z: pos.z}, this.blockNameWaystoneMasterBase); */ var block = CoroUtil.getBlock(dimension, pos);
            if (block !== undefined) {
                let state = "minecraft:cardinal_direction";
                let dir = block.permutation.getState(state);
                //log("state: " + dir);
                block.setPermutation(BlockPermutation.resolve(this.blockNameWaystoneMasterBase).withState(state, dir));
                var blockUp = CoroUtil.getBlock(dimension, CoroUtil.up(pos, 1));
                if (blockUp !== undefined) {
                    blockUp.setPermutation(BlockPermutation.resolve(this.blockNameWaystoneMasterTop).withState(state, dir));
                }
            }
        /* CoroUtil.setBlockAt(dimension, { x: pos.x, 
				y: pos.y + 1, 
				z: pos.z}, this.blockNameWaystoneMasterTop); */ } else {
            CoroUtil.setBlockAt(dimension, {
                x: pos.x,
                y: pos.y,
                z: pos.z
            }, "air");
            if (CoroUtil.getGameMode(player) != GameMode.creative) {
                player.dimension.spawnItem(new ItemStack(this.blockNameWaystoneMasterFull), pos);
            }
        }
    }
    getWayfinderPosFromLore(itemStack, getRecalInstead) {
        let lore = itemStack.getLore();
        if (lore.length < 3) {
            //err("lore.length < 3");
            return undefined;
        }
        if (lore.length < 4 && getRecalInstead) {
            //err("lore.length < 4");
            return undefined;
        }
        try {
            let coordIndex = 2;
            if (getRecalInstead) coordIndex = 3;
            let pos = CoroUtil.stringifiedDimPosToVector3(CoroUtil.unhideText(lore[coordIndex]));
            return pos;
        /* let stringCoords: string[] = CoroUtil.unhideText(lore[coordIndex]).split(" ");
			if (stringCoords.length < 3) {
				return undefined;
			}
			return { x: +stringCoords[0], y: +stringCoords[1], z: +stringCoords[2] }; */ } catch (e) {
            err("error: " + e.message);
            return undefined;
        }
    }
    getWayfinderDimFromLore(itemStack, getRecalInstead) {
        let lore = itemStack.getLore();
        if (lore.length < 3) {
            //err("lore.length < 3");
            return undefined;
        }
        if (lore.length < 4 && getRecalInstead) {
            //err("lore.length < 4");
            return undefined;
        }
        try {
            let coordIndex = 2;
            if (getRecalInstead) coordIndex = 3;
            let dim = CoroUtil.stringifiedDimPosToDimension(CoroUtil.unhideText(lore[coordIndex]));
            return dim;
        /* let stringCoords: string[] = CoroUtil.unhideText(lore[coordIndex]).split(" ");
			if (stringCoords.length < 3) {
				return undefined;
			}
			return { x: +stringCoords[0], y: +stringCoords[1], z: +stringCoords[2] }; */ } catch (e) {
            err("error: " + e.message);
            return undefined;
        }
    }
    setWayfinderPosToLore(itemStack, dimBound, posBound, dimRecal, posRecal) {
        //Bound to xyz
        //"Next use recalls to xyz" or blank
        //bound data
        //recal data or blank
        let lore = itemStack.getLore();
        while(lore.length < 4){
            lore.push("");
        }
        let posBoundString = "";
        let posRecalString = "";
        if (posBound !== undefined && dimBound !== undefined) {
            posBoundString = posBound.x + " " + posBound.y + " " + posBound.z + ", " + dimBound.id.replace("minecraft:", "");
            lore[0] = "Bound to " + posBoundString;
            //lore[2] = posBoundString;
            lore[2] = CoroUtil.hideText(CoroUtil.dimVector3ToStringifiedDimPos(dimBound, posBound));
        }
        if (posRecal !== undefined && posRecal !== null && dimRecal !== undefined) {
            //debug("setting up recal pos")
            posRecalString = posRecal.x + " " + posRecal.y + " " + posRecal.z + ", " + dimRecal.id.replace("minecraft:", "");
            lore[1] = "Next use recalls to " + posRecalString;
            //lore[3] = posRecalString;
            lore[3] = CoroUtil.hideText(CoroUtil.dimVector3ToStringifiedDimPos(dimRecal, posRecal));
        } else if (posRecal == null) {
            //debug("blanking out recal data")
            posRecalString = "";
            lore[1] = "";
            lore[3] = posRecalString;
        //lore[3] = CoroUtil.hideText(posRecalString);
        }
        itemStack.setLore(lore);
    }
    hitMasterWaystoneMenuDestination(menuDestination, player) {
        ///nahhhhhhhhhh
        /* if (!player.hasComponent("equippable")) return;
		const equippable = player.getComponent("equippable") as EntityEquippableComponent;
		let name: string | undefined = equippable.getEquipment(EquipmentSlot.Mainhand)?.nameTag; */ this.lookedAtMasterWaystoneMenuDestination(menuDestination, player);
    }
    interactMasterWaystoneMenuDestination(menuDestination) {
        log("interact");
        //theres a weird edge case where menuDestination.location fails to exist, but the method that calls this identified the event.sourceEntity as our entity
        //happens when theres rapid teleporting, across dimensions / or maybe in/out of chunks causing entity unload
        try {
            let player = CoroUtil.getClosestPlayerToPos(menuDestination.location);
            let dataEntity = this.getWaystoneDataEntity(menuDestination);
            if (player !== undefined && dataEntity !== undefined) {
                let jsonArray = this.getWaystoneJsonData(dataEntity);
                let index = CoroUtil.getPropNumber(menuDestination, this.propMenuDataDestinationIndex);
                if (index <= jsonArray.length - 1) {
                    let count = 0;
                    let jsonDestination = jsonArray[index];
                    /* log("index " + index);
                    log("name: " + jsonDestination.name);
                    log(jsonDestination.x + ", " + jsonDestination.y + ", " + jsonDestination.z); */ if (!player.hasComponent("equippable")) return;
                    const equippable = player.getComponent("equippable");
                    let item = equippable.getEquipment(EquipmentSlot.Mainhand);
                    if (item != undefined && item.typeId == "minecraft:name_tag") {
                        let name = equippable.getEquipment(EquipmentSlot.Mainhand)?.nameTag;
                        if (name === undefined) {
                            name = "";
                        }
                        //log("set to " + name)
                        jsonDestination.name = name;
                        dataEntity.setDynamicProperty("data", JSON.stringify(jsonArray));
                        //force a refresh of the look at code to show new name
                        player.setDynamicProperty("getLastLookedAtEntity", -1);
                    } else {
                        log("jsonDestination: " + CoroUtil.vector3ToString({
                            x: jsonDestination.x,
                            y: jsonDestination.y,
                            z: jsonDestination.z
                        }));
                        if (CoroUtil.isLoaded(player.dimension, player.location)) {
                            player.dimension.spawnParticle("razz_ew:wayfinder_charge_explode_activate", player.location);
                        }
                        this.tryTeleportWithCost(player, world.getDimension(CoroUtil.dimNumberIDToStringID(jsonDestination.dim)), {
                            x: jsonDestination.x,
                            y: jsonDestination.y,
                            z: jsonDestination.z
                        });
                    //this.teleportWithDelayedSound(player, world.getDimension(CoroUtil.dimNumberIDToStringID(jsonDestination.dim)), this.getTeleportSpotNextToWaystone(world.getDimension(CoroUtil.dimNumberIDToStringID(jsonDestination.dim)), {x: jsonDestination.x, y: jsonDestination.y, z: jsonDestination.z}));
                    }
                }
            }
        } catch (error) {
            err("caught error: " + error.message);
        }
    }
    lookedAtMasterWaystoneMenuDestination(menuDestination, player) {
        //log("looked at")
        //let player: Player | undefined = CoroUtil.getClosestPlayerToPos(menuDestination.location);
        if (CoroUtil.getPropString(player, "getLastLookedAtEntity") != menuDestination.id) {
            let dataEntity = this.getWaystoneDataEntity(menuDestination);
            if (player !== undefined && dataEntity !== undefined) {
                let menuEntity = this.getWaystoneMenuEntity(dataEntity);
                let jsonArray = this.getWaystoneJsonData(dataEntity);
                let index = CoroUtil.getPropNumber(menuDestination, this.propMenuDataDestinationIndex);
                if (index <= jsonArray.length - 1 && menuEntity !== undefined) {
                    let isRight = (index + 1) % 2 == 0;
                    let count = 0;
                    let jsonDestination = jsonArray[index];
                    menuEntity.nameTag = jsonDestination.name + "\n" + jsonDestination.x + ", " + jsonDestination.y + ", " + jsonDestination.z + ", " + CoroUtil.dimNumberIDToStringID(jsonDestination.dim).replace("minecraft:", "");
                    //player.runCommandAsync("playsound mob.chicken.plop @s ~ ~ ~ 0.3");
                    //player.runCommandAsync("playsound mob.chicken.plop @s ~ ~ ~ 0.3");
                    player.playSound("click_on.stone_pressure_plate", {
                        volume: 0.3,
                        pitch: 1.5
                    });
                    menuDestination.runCommandAsync("playanimation @s " + (!isRight ? "pop_right" : "pop_left"));
                    //log("!isRight " + !isRight)
                    player.setDynamicProperty("getLastLookedAtEntity", menuDestination.id);
                }
            }
        }
    }
    getWaystoneDataEntity(entityDestination) {
        let dataEntityID = CoroUtil.getPropString(entityDestination, this.propDataEntID);
        if (dataEntityID !== "") {
            return world.getEntity(dataEntityID);
        }
        return undefined;
    }
    getWaystoneMenuEntity(entityData) {
        let menuEntityID = CoroUtil.getPropString(entityData, this.propMasterWaystoneDataMenuEntID);
        if (menuEntityID !== "") {
            return world.getEntity(menuEntityID);
        }
        return undefined;
    }
    getWaystoneJsonData(data) {
        let dataString = CoroUtil.getPropString(data, "data");
        if (dataString !== "") {
            //log(dataString);
            return JSON.parse(dataString);
        }
        return [];
    }
    useWayfinderOnMasterWaystone(dim, pos, item, isSneaking) {
        //log("useWayfinderOnMasterWaystone?: " + item.getLore())
        let data = this.getOrCreateMasterWaystoneEntity(dim, pos);
        let dataString = CoroUtil.getPropString(data, "data");
        let player = CoroUtil.getClosestPlayerToPos(data.location);
        //TEMP, change to removing entry not deleting all data
        /* if (isSneaking) {
			dataString = "";
		} */ if (!this.configMasterWaystoneGUIv1_1) {
            this.closeMasterWaystoneGUI(data, false);
        }
        let jsonArray = [];
        if (dataString !== "") {
            try {
                jsonArray = JSON.parse(dataString);
            } catch (error) {
                err("" + error.message);
            }
        }
        /* log("0: " + item.getLore()[0])
		log("1: " + item.getLore()[1])
		log("2: " + item.getLore()[2]) */ //let posTeleport: Vector3 = this.getPosFromLore(item.getLore());
        let posTeleport = this.getWayfinderPosFromLore(item, false);
        let dimTeleport = this.getWayfinderDimFromLore(item, false);
        if (posTeleport === undefined || posTeleport.x == null || posTeleport.x == undefined || Number.isNaN(posTeleport.x) || dimTeleport === undefined) {
            err("error couldnt get coords from wayfinder");
            if (player !== undefined) this.subtitleRawSimple(player, "msg.wayfinder_not_bound1", "msg.wayfinder_not_bound2");
            if (isSneaking) {
            //data.setDynamicProperty("data", JSON.stringify(jsonArray));
            //log("close gui");
            //this.closeGui(dim, pos);
            //this.closeMasterWaystoneGUI(data);
            }
            return;
        }
        //check for existing entry
        let foundEntry = false;
        let index = 0;
        for (const entry of jsonArray){
            if (entry.x == posTeleport.x && entry.y == posTeleport.y && entry.z == posTeleport.z) {
                err("entry already exists: " + index);
                if (!isSneaking) {
                    if (player !== undefined) this.subtitleRawSimple(player, "msg.waystone_master_added_already1", "msg.waystone_master_added_already2");
                }
                foundEntry = true;
                break;
            }
            index++;
        }
        let itemCustomName = item.nameTag;
        log("item name: " + itemCustomName);
        if (itemCustomName === undefined) {
            itemCustomName = "";
        }
        if (!foundEntry) {
            if (jsonArray.length >= 8 && !isSneaking) {
                if (player !== undefined) this.subtitleRawSimple(player, "msg.waystone_master_full1", "msg.waystone_master_full2");
                return;
            }
            let jsonProperty = {
                name: itemCustomName,
                icon_index: CoroUtil.randRange(this.masterPlayerWayfinderIconTextures.length),
                ent_id: "",
                dim: CoroUtil.dimStringIDToNumberID(dimTeleport.id),
                x: posTeleport.x,
                y: posTeleport.y,
                z: posTeleport.z
            };
            if (!isSneaking) {
                jsonArray.push(jsonProperty);
                log("added destination: " + jsonArray.length);
                if (player !== undefined) {
                    this.actionBarRawSimple(player, "msg.waystone_master_added");
                    player.dimension.getPlayers({
                        location: player.location,
                        maxDistance: 12
                    }).forEach((player)=>{
                        player.playSound("random.levelup", {
                            volume: 0.1
                        });
                    });
                    player.dimension.spawnParticle("razz_ew:wayfinder_charge_explode_activate", CoroUtil.centerXZ(pos));
                }
            }
        } else {
            if (isSneaking) {
                log("removing destination: " + index);
                if (player !== undefined) {
                    this.actionBarRawSimple(player, "msg.waystone_master_removed");
                    player.dimension.getPlayers({
                        location: player.location,
                        maxDistance: 12
                    }).forEach((player)=>{
                        player.playSound("random.levelup", {
                            volume: 0.1,
                            pitch: 0.75
                        });
                    });
                }
                jsonArray.splice(index, 1);
            } else {
                //log(jsonArray[index].name + " vs " + itemCustomName)
                if (jsonArray[index].name !== itemCustomName && itemCustomName !== "") {
                    log("updating name for destination");
                    if (player !== undefined) this.actionBarRawSimple(player, "msg.waystone_master_updated");
                    jsonArray[index].name = itemCustomName;
                }
            }
        }
        let blockBase = CoroUtil.getBlock(dim, pos);
        if (blockBase !== undefined && blockBase.permutation.matches(this.blockNameWaystoneMasterBase)) {
            blockBase.setPermutation(blockBase.permutation.withState(this.blockPermutationMasterWaystoneWayfinderCount, jsonArray.length));
        }
        let blockTop = CoroUtil.getBlock(dim, CoroUtil.up(pos, 1));
        if (blockTop !== undefined && blockTop.permutation.matches(this.blockNameWaystoneMasterTop)) {
            blockTop.setPermutation(blockTop.permutation.withState(this.blockPermutationMasterWaystoneWayfinderCount, jsonArray.length));
        }
        //log("close gui");
        //log("json: " + JSON.stringify(jsonArray));
        data.setDynamicProperty("data", JSON.stringify(jsonArray));
    /* data.setDynamicProperty("isOpen", false);
		let entities = dim.getEntities({ type: this.entityNameWaystoneMasterMenu, location: pos, maxDistance: 1.5 });
		for (const ent of entities) {
			ent.kill();
		}
		entities = dim.getEntities({ type: this.entityNameWaystoneMasterMenuDestination, location: pos, maxDistance: 30 });
		for (const ent of entities) {
			ent.kill();
		} */ //this.closeGui(dim, pos);
    //this.closeMasterWaystoneGUI(data);
    //let jsonArray2: any[] = JSON.parse(dataString);
    }
    doesMasterWayfinderContainDestination(player, dim, posAnyWaystone, itemMasterWayfinder, isSneaking) {
        let data = itemMasterWayfinder;
        let dataString = CoroUtil.getPropString(data, "data");
        let jsonArray = [];
        if (dataString !== "") {
            try {
                jsonArray = JSON.parse(dataString);
            } catch (error) {
                err("" + error.message);
            }
        }
        let posTeleport = posAnyWaystone;
        let dimTeleport = dim;
        //check for existing entry
        let foundEntry = false;
        let index = 0;
        for (const entry of jsonArray){
            if (entry.x == posTeleport.x && entry.y == posTeleport.y && entry.z == posTeleport.z) {
                return true;
                break;
            }
            index++;
        }
        return false;
    }
    /**
     * 
     * @param player 
     * @param dim 
     * @param posAnyWaystone 
     * @param itemMasterWayfinder 
     * @param isSneaking 
     * @returns 0 = added, bound, 1 = entry already exists, 2 = wayfinder full
     */ useMasterWayfinderOnAnyWaystone(player, dim, posAnyWaystone, itemMasterWayfinder, isSneaking) {
        let returnVal = 0;
        //log("useWayfinderOnMasterWaystone?: " + item.getLore())
        //let data: Entity = this.getOrCreateMasterWaystoneEntity(dim, pos);
        let data = itemMasterWayfinder;
        let dataString = CoroUtil.getPropString(data, "data");
        //let player = CoroUtil.getClosestPlayerToPos(posAnyWaystone);
        //TEMP, change to removing entry not deleting all data
        /* if (isSneaking) {
			dataString = "";
		} */ let jsonArray = [];
        if (dataString !== "") {
            try {
                jsonArray = JSON.parse(dataString);
            } catch (error) {
                err("" + error.message);
            }
        }
        /* log("0: " + item.getLore()[0])
		log("1: " + item.getLore()[1])
		log("2: " + item.getLore()[2]) */ //let posTeleport: Vector3 = this.getPosFromLore(item.getLore());
        //let posTeleport: Vector3 | undefined = this.getWayfinderPosFromLore(itemMasterWayfinder, false);
        //let dimTeleport: Dimension | undefined = this.getWayfinderDimFromLore(itemMasterWayfinder, false);
        let posTeleport = posAnyWaystone;
        let dimTeleport = dim;
        //check for existing entry
        let foundEntry = false;
        let index = 0;
        for (const entry of jsonArray){
            if (entry.x == posTeleport.x && entry.y == posTeleport.y && entry.z == posTeleport.z) {
                err("entry already exists: " + index);
                foundEntry = true;
                break;
            }
            index++;
        }
        let itemCustomName = itemMasterWayfinder.nameTag;
        log("2 item name: " + itemCustomName);
        if (itemCustomName === undefined) {
            itemCustomName = "";
        }
        if (!foundEntry) {
            if (jsonArray.length >= 6 && !isSneaking) {
                if (player !== undefined) this.subtitleRawSimple(player, "msg.wayfinder_master_full1", "msg.wayfinder_master_full2");
                return 2;
            }
            let jsonProperty = {
                name: itemCustomName,
                icon_index: CoroUtil.randRange(this.masterPlayerWayfinderIconTextures.length),
                ent_id: "",
                dim: CoroUtil.dimStringIDToNumberID(dimTeleport.id),
                x: posTeleport.x,
                y: posTeleport.y,
                z: posTeleport.z
            };
            if (!isSneaking) {
                jsonArray.push(jsonProperty);
                log("2 added destination: " + jsonArray.length);
                if (player !== undefined) {
                    this.actionBarRawSimple(player, "msg.wayfinder_master_added");
                    player.dimension.getPlayers({
                        location: player.location,
                        maxDistance: 12
                    }).forEach((player)=>{
                        player.playSound("random.levelup", {
                            volume: 0.1
                        });
                    });
                    player.dimension.spawnParticle("razz_ew:wayfinder_charge_explode_activate", CoroUtil.centerXZ(posAnyWaystone));
                }
            }
        } else {
            if (isSneaking) {
                log("2 removing destination: " + index);
                if (player !== undefined) {
                    this.actionBarRawSimple(player, "msg.wayfinder_master_removed");
                    player.dimension.getPlayers({
                        location: player.location,
                        maxDistance: 12
                    }).forEach((player)=>{
                        player.playSound("random.levelup", {
                            volume: 0.1,
                            pitch: 0.75
                        });
                    });
                }
                jsonArray.splice(index, 1);
            } else {
                //log(jsonArray[index].name + " vs " + itemCustomName)
                if (jsonArray[index].name !== itemCustomName && itemCustomName !== "") {
                    log("updating name for destination");
                    if (player !== undefined) this.actionBarRawSimple(player, "msg.wayfinder_master_updated");
                    jsonArray[index].name = itemCustomName;
                } else {
                    if (player !== undefined) this.subtitleRaw(player, '{"rawtext":[{"translate":"msg.wayfinder_master_bound_already1"}]}', '{"rawtext":[{"translate":"msg.wayfinder_master_bound_already2"}]}');
                    returnVal = 1;
                }
            }
        }
        /* let blockBase = CoroUtil.getBlock(dim, pos);
        if (blockBase !== undefined && blockBase.permutation.matches(this.blockNameWaystoneMasterBase)) {
            blockBase.setPermutation(blockBase.permutation.withState(this.blockPermutationMasterWaystoneWayfinderCount, jsonArray.length));
        }
        let blockTop = CoroUtil.getBlock(dim, CoroUtil.up(pos, 1));
        if (blockTop !== undefined && blockTop.permutation.matches(this.blockNameWaystoneMasterTop)) {
            blockTop.setPermutation(blockTop.permutation.withState(this.blockPermutationMasterWaystoneWayfinderCount, jsonArray.length));
        } */ //log("json: " + JSON.stringify(jsonArray));
        data.setDynamicProperty("data", JSON.stringify(jsonArray));
        const equippable = player.getComponent("equippable");
        equippable.setEquipment(EquipmentSlot.Mainhand, data);
        return returnVal;
    }
    /* closeGui(dim: Dimension, pos: Vector3) {
		let data: Entity = this.getOrCreateMasterWaystoneEntity(dim, pos);
		data.setDynamicProperty("isOpen", false);
		let entities = dim.getEntities({ type: this.entityNameWaystoneMasterMenu, location: pos, maxDistance: 1.5 });
		for (const ent of entities) {
			ent.kill();
		}
		entities = dim.getEntities({ type: this.entityNameWaystoneMasterMenuDestination, location: pos, maxDistance: 30 });
		for (const ent of entities) {
			ent.kill();
		}
	} */ openMasterPlayerWayfinderGUI(player) {
        let playerMasterWayfinderData = this.getPlayerMasterWayfinderData(player);
        let playerMasterWayfinderDataOnline = {};
        let playerMasterWayfinderDataOffline = {};
        let playerMasterWayfinderDataCombined = {};
        const form = new ActionFormData();
        form.title("Master Player Warper");
        form.body("Choose a player to warp to");
        form.button("Change player icons", "textures/razz/ew/gui/misc/ew_icon_edit_icon.png");
        let players = world.getAllPlayers();
        for(let key in playerMasterWayfinderData){
            let entry = playerMasterWayfinderData[key];
            if (player.id !== entry.uuid) {
                if (world.getPlayers({
                    name: entry.username
                }).length > 0) {
                    entry.online = true;
                    playerMasterWayfinderDataOnline[entry.username] = entry;
                } else {
                    entry.online = false;
                    playerMasterWayfinderDataOffline[entry.username] = entry;
                }
            }
        }
        //TEMP FOR GUI PICTURES
        /* let dataNew: PlayerMasterWayfinderDataEntry = {
            uuid: "",
            username: "GamerBro99",
            online: true,
            icon_index: 0,
            last_dim:  0,
            last_x: 0,
            last_y: 0,
            last_z: 0
        }
        playerMasterWayfinderDataOnline[dataNew.username] = dataNew;
        playerMasterWayfinderData[dataNew.username] = dataNew;
        dataNew = {
            uuid: "",
            username: "Steve",
            online: true,
            icon_index: 1,
            last_dim:  0,
            last_x: 0,
            last_y: 0,
            last_z: 0
        }
        playerMasterWayfinderDataOnline[dataNew.username] = dataNew;
        playerMasterWayfinderData[dataNew.username] = dataNew;
        dataNew = {
            uuid: "",
            username: "Alex",
            online: true,
            icon_index: 2,
            last_dim:  0,
            last_x: 0,
            last_y: 0,
            last_z: 0
        }
        playerMasterWayfinderDataOnline[dataNew.username] = dataNew;
        playerMasterWayfinderData[dataNew.username] = dataNew; */ let i = 0;
        for(let entry in playerMasterWayfinderDataOnline){
            let entryCast = playerMasterWayfinderData[entry];
            let name = entryCast.username;
            form.button(name, this.basePathMasterPlayerWayfinderTextures + this.masterPlayerWayfinderIconTextures[entryCast.icon_index ?? 0] + ".png");
            i++;
        }
        for(let entry in playerMasterWayfinderDataOffline){
            let entryCast = playerMasterWayfinderData[entry];
            let name = "" + entryCast.username + " §c[OFFLINE]";
            form.button(name, this.basePathMasterPlayerWayfinderTextures + this.masterPlayerWayfinderIconTextures[entryCast.icon_index ?? 0] + ".png");
            i++;
        }
        playerMasterWayfinderDataCombined = Object.assign({}, playerMasterWayfinderDataOnline, playerMasterWayfinderDataOffline);
        if (Object.keys(playerMasterWayfinderDataCombined).length == 0) {
            form.button("Get some friends to join first!");
        }
        form.show(player).then((response)=>{
            if (response.selection !== undefined) {
                if (response.selection == 0) {
                    this.openMasterPlayerWayfinderGUIEditDestinationIcons(player, playerMasterWayfinderDataCombined);
                } else {
                    if (Object.keys(playerMasterWayfinderDataCombined).length == 0) {
                    //nothing
                    } else {
                        let key = Object.keys(playerMasterWayfinderDataCombined)[response.selection - 1];
                        let data = playerMasterWayfinderDataCombined[key];
                        if (data.online) {
                            //CoroUtil.getClosestPlayer
                            let playerTarget = CoroUtil.getPlayerByName(data.username);
                            if (playerTarget) {
                                //this.tryTeleportWithCost(player, playerTarget.dimension, playerTarget.location, true);
                                player.setDynamicProperty(this.propPlayerStartedWarpCount, true);
                                CoroUtil.setPropSafe(player, this.propPlayerPlayerWayfinderUsername, data.username);
                            } else {
                                //player went offline after gui open but before they teleported
                                this.openMasterPlayerWayfinderGUIConfirmTP(player, data);
                            //this.tryTeleportWithCost(player, world.getDimension(CoroUtil.dimNumberIDToStringID(data.last_dim)), CoroUtil.vec(data.last_x, data.last_y, data.last_z), true);
                            }
                        } else {
                            this.openMasterPlayerWayfinderGUIConfirmTP(player, data);
                        //this.tryTeleportWithCost(player, world.getDimension(CoroUtil.dimNumberIDToStringID(data.last_dim)), CoroUtil.vec(data.last_x, data.last_y, data.last_z), true);
                        }
                    }
                }
            }
            return;
        });
    }
    openMasterPlayerWayfinderGUIConfirmTP(player, data) {
        const form = new ActionFormData();
        form.title("Confirm Warp");
        //form.body("Choose a waystone to warp to \n dsfsdfsdfsdf");
        form.body("\n" + data.username + " is §cOFFLINE§r \n\nWarp to last known location?\n");
        form.button("YES");
        form.button("NO");
        form.show(player).then((response)=>{
            if (response.selection !== undefined) {
                if (response.selection == 0) {
                    log("warp to " + data.username);
                    //this.tryTeleportWithCost(player, world.getDimension(CoroUtil.dimNumberIDToStringID(data.last_dim)), CoroUtil.vec(data.last_x, data.last_y, data.last_z), true);
                    //CoroUtil.setPropSafe(player, this.propPlayerPlayerWayfinderCountdownToWarp, this.warpDelay);
                    player.setDynamicProperty(this.propPlayerStartedWarpCount, true);
                    CoroUtil.setPropSafe(player, this.propPlayerPlayerWayfinderUsername, data.username);
                //now taken care of in delayed tp code
                /* CoroUtil.setPropSafe(player, this.propPlayerPlayerWayfinderLastDim, data.last_dim);
                    CoroUtil.setPropSafe(player, this.propPlayerPlayerWayfinderLastX, data.last_x);
                    CoroUtil.setPropSafe(player, this.propPlayerPlayerWayfinderLastY, data.last_y);
                    CoroUtil.setPropSafe(player, this.propPlayerPlayerWayfinderLastZ, data.last_z); */ }
            }
            return;
        });
    }
    //not needed until we have more than 1 setting type to change
    openMasterPlayerWayfinderGUISettings(player, playerMasterWayfinderDataCombined) {
        const form = new ActionFormData();
        form.title("Settings");
        form.button("Change destination icons", "textures/items/diamond_shovel");
        form.button("Back", "textures/items/diamond_shovel");
        form.show(player).then((response)=>{
            if (response.selection == 0) {
                this.openMasterPlayerWayfinderGUIEditDestinationIcons(player, playerMasterWayfinderDataCombined);
            } else if (response.selection == 1) {
                this.openMasterPlayerWayfinderGUI(player);
            }
        });
    }
    openMasterPlayerWayfinderGUIEditDestinationIcons(player, playerMasterWayfinderDataCombined) {
        const form = new ActionFormData();
        form.title("Master Player Warper");
        //form.body("Choose a waystone to warp to \n dsfsdfsdfsdf");
        form.body("Choose a player to change icon for");
        let i = 0;
        for(const key in playerMasterWayfinderDataCombined){
            let entry = playerMasterWayfinderDataCombined[key];
            //let name = entry.username
            let name = "" + entry.username + (!entry.online ? " §c[OFFLINE]" : "");
            form.button(name, this.basePathMasterPlayerWayfinderTextures + this.masterPlayerWayfinderIconTextures[entry.icon_index ?? 0] + ".png");
            i++;
        }
        let cancel = i;
        form.button("Back", "textures/razz/ew/gui/misc/ew_icon_return.png");
        form.show(player).then((response)=>{
            if (response.selection !== undefined) {
                if (response.selection != cancel) {
                    let key = Object.keys(playerMasterWayfinderDataCombined)[response.selection];
                    let data = playerMasterWayfinderDataCombined[key];
                    this.openMasterPlayerWayfinderGUIEditDestinationIcon(player, playerMasterWayfinderDataCombined, data);
                } else {
                    this.openMasterPlayerWayfinderGUI(player);
                }
            }
        });
    }
    openMasterPlayerWayfinderGUIEditDestinationIcon(player, playerMasterWayfinderDataCombined, entryToEdit) {
        const form = new ActionFormData();
        let i = 0;
        form.title("Icon for " + entryToEdit.username);
        form.body("\n Choose an icon for " + entryToEdit.username);
        for (const icon of this.masterPlayerWayfinderIcons){
            form.button(icon, this.basePathMasterPlayerWayfinderTextures + this.masterPlayerWayfinderIconTextures[i ?? 0] + ".png");
            i++;
        }
        let cancel = i;
        form.button("Cancel", "textures/razz/ew/gui/misc/ew_icon_return.png");
        form.show(player).then((response)=>{
            if (response.selection !== undefined) {
                if (response.selection != cancel) {
                    entryToEdit.icon_index = response.selection;
                    this.addOrUpdatePlayerMasterWayfinderDataEntryUsingData(player, entryToEdit);
                }
                this.openMasterPlayerWayfinderGUIEditDestinationIcons(player, playerMasterWayfinderDataCombined);
            }
        });
    }
    openMasterPlayerWayfinderGUIEditDestinationIconsv1(player, playerMasterWayfinderDataCombined) {
        const form = new ModalFormData();
        let i = 0;
        for(const key in playerMasterWayfinderDataCombined){
            let entry = playerMasterWayfinderDataCombined[key];
            let name = entry.username;
            //form.textField(name, "Add a name here...", entryInt.name);
            form.dropdown(name, this.masterPlayerWayfinderIcons, entry.icon_index ?? 0);
            //form.toggle("what", true);
            i++;
        }
        form.show(player).then((response)=>{
            if (response.formValues) {
                console.warn();
                let i = 0;
                for (const entry of response.formValues){
                    let key = Object.keys(playerMasterWayfinderDataCombined)[i];
                    let data = playerMasterWayfinderDataCombined[key];
                    data.icon_index = entry;
                    i++;
                }
                this.addOrUpdatePlayerMasterWayfinderDataEntrysUsingData(player, playerMasterWayfinderDataCombined);
                this.openMasterPlayerWayfinderGUI(player);
            }
        });
    }
    getMasterWaystoneDestinationsFromBlock(dim, player, pos) {
        return this.getMasterWaystoneDestinationsImpl(dim, player, this.getOrCreateMasterWaystoneEntity(dim, pos));
    }
    getMasterWaystoneDestinationsFromItemStack(dim, player, itemstack) {
        return this.getMasterWaystoneDestinationsImpl(dim, player, itemstack);
    }
    getMasterWaystoneDestinationsImpl(dim, player, data) {
        //let data: Entity = this.getOrCreateMasterWaystoneEntity(dim, pos);
        let returnData = [];
        let dataString = CoroUtil.getPropString(data, "data");
        //log("dataString " + dataString)
        if (dataString !== "") {
            let jsonArray = JSON.parse(dataString);
            if (jsonArray.length > 0) {
                for (const entry of jsonArray){
                    let entryInt = entry;
                    //console.warn("? " + entryInt.x);
                    returnData.push(entryInt);
                }
            }
        }
        return returnData;
    }
    openMasterWaystoneGUIv1_1(dim, player, dataSource) {
        let destinations;
        if (dataSource instanceof ItemStack) {
            destinations = this.getMasterWaystoneDestinationsFromItemStack(dim, player, dataSource);
        } else {
            destinations = this.getMasterWaystoneDestinationsFromBlock(dim, player, dataSource);
        }
        /* const equippable = player.getComponent("equippable") as EntityEquippableComponent;
        let itemStack: ItemStack | undefined = equippable.getEquipment(EquipmentSlot.Mainhand);
        itemStack?.getDynamicProperty */ if (destinations.length > 0) {
            /* const test = new MessageFormData();
            const test2 = new ModalFormData();
            test2.textField("test", "test2", "what");
            test2.dropdown("test", ["1", "2", "3"], 1); */ //test2.show(player);
            const form = new ActionFormData();
            if (dataSource instanceof ItemStack) {
                form.title("Master Wayfinder destinations");
            } else {
                form.title("Master Waystone destinations");
            }
            //form.body("Choose a waystone to warp to \n dsfsdfsdfsdf");
            form.body("Choose a waystone to warp to");
            /* form.button("Spleef", "textures/items/diamond_shovel");
            form.button("Murder Mystery", "textures/items/iron_sword");
            form.button("Bedwars", "textures/minigames/bedwars.png"); */ //form
            form.button("Settings", "textures/razz/ew/gui/misc/ew_icon_settings.png");
            let i = 0;
            for (const entry of destinations){
                if (entry == null) continue;
                let entryInt = entry;
                let name = entryInt.name;
                if (name == "") {
                    name = "Waystone " + i;
                }
                //form.button(name/*  + ": " + entryInt.x + " " + entryInt.y + " " + entryInt.z */, this.waystoneIconTextures[entryInt.icon_index]);
                //console.warn("? " + this.waystoneIconTextures[entryInt.icon_index ?? 0])
                form.button(name, this.basePathMasterWaystoneTextures + this.waystoneIconTextures[entryInt.icon_index ?? 0] + ".png");
                //form.button(name, "textures/razz/ew/item/guide.png");
                i++;
            }
            form.show(player).then((response)=>{
                if (response.selection !== undefined) {
                    if (response.selection == 0) {
                        this.openWaystoneGUISettings(dim, player, dataSource, destinations);
                    } else {
                        let destination = destinations[response.selection - 1];
                        //console.warn("?? " + destination);
                        //console.warn(destinations);
                        if (destination) {
                            this.tryTeleportWithCost(player, world.getDimension(CoroUtil.dimNumberIDToStringID(destination.dim)), CoroUtil.vec(destination.x, destination.y, destination.z));
                        }
                    }
                }
                if (response.canceled) {
                //player.sendMessage("Canceled due to " + response.cancelationReason);
                }
                return;
            });
        } else {
            if (dataSource instanceof ItemStack) {
                this.actionBarRawSimple(player, "msg.wayfinder_master_empty");
            } else {
                this.actionBarRawSimple(player, "msg.waystone_master_empty");
            }
        }
    }
    openMasterWaystonev1_1ChangesGUI(player) {
        const form = new ActionFormData();
        form.title({
            translate: "razz_ew:master_waystone_v1_1_changes.name"
        });
        form.body({
            rawtext: [
                {
                    translate: "razz_ew:master_waystone_v1_1_changes.part_01"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:master_waystone_v1_1_changes.part_02"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:master_waystone_v1_1_changes.part_03"
                },
                {
                    text: "§r\n\n"
                }
            ]
        });
        form.button({
            translate: "razz_ew:master_waystone_v1_1_changes_dont_show_again"
        });
        form.button({
            translate: "razz_ew:master_waystone_v1_1_changes_close_button"
        });
        form.show(player).then((response)=>{
            if (response.selection == 0) {
                CoroUtil.setPropSafe(player, this.propPlayerMasterWaystoneUpdateNotifyv1_1DontShowAgain, true);
            } else if (response.selection == 1) {}
        });
    }
    openV1_1ChangesGUI(player) {
        const form = new ActionFormData();
        form.title({
            translate: "razz_ew:addon_changes_v1_1.name"
        });
        form.body({
            rawtext: [
                {
                    translate: "razz_ew:addon_changes_v1_1.part_01"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:addon_changes_v1_1.part_02"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:addon_changes_v1_1.part_03"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:addon_changes_v1_1.part_04"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:addon_changes_v1_1.part_05"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:addon_changes_v1_1.part_06"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:addon_changes_v1_1.part_07"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:addon_changes_v1_1.part_08"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:addon_changes_v1_1.part_09"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:addon_changes_v1_1.part_10"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:addon_changes_v1_1.part_11"
                },
                {
                    text: "§r\n\n"
                }
            ]
        });
        form.button({
            translate: "razz_ew:addon_changes_v1_1_close_button"
        });
        form.show(player).then((response)=>{
            if (response.selection == 0) {
                this.openGuidebook(player);
            }
        });
    }
    openGuidebook(player) {
        const form = new ActionFormData();
        form.title({
            translate: "razz_ew:guide.name"
        });
        form.body({
            rawtext: [
                {
                    translate: "razz_ew:info.part_01"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_02"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_03"
                },
                {
                    text: "§r\n\n\n"
                },
                {
                    translate: "razz_ew:info.part_031"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_04"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_05"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_06"
                },
                {
                    text: "§r\n\n\n"
                },
                {
                    translate: "razz_ew:info.part_07"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_071"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_072"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_08"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_081"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_09"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_10"
                },
                {
                    text: "§r\n\n\n"
                },
                {
                    translate: "razz_ew:info.part_11"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_12"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_121"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_13"
                },
                {
                    text: "§r\n\n"
                },
                //{translate: "razz_ew:info.part_14"}, {text: "§r\n\n"},
                //{translate: "razz_ew:info.part_15"}, {text: "§r\n\n"},
                {
                    translate: "razz_ew:info.part_16"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_161"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_162"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_17"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_18"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_19"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_20"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_21"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_22"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_23"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_24"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_25"
                },
                {
                    text: "§r\n\n"
                },
                {
                    translate: "razz_ew:info.part_26"
                },
                {
                    text: "§r\n\n"
                }
            ]
        });
        form.button({
            translate: "razz_ew:guide_settings_button"
        });
        form.button({
            translate: "razz_ew:guide_changes_button"
        });
        form.button({
            translate: "razz_ew:guide_exit_button"
        });
        form.show(player).then((response)=>{
            if (response.selection == 0) {
                this.openAddonSettings(player);
            } else if (response.selection == 1) {
                this.openV1_1ChangesGUI(player);
            } else if (response.selection == 2) {
                player.playSound("mob.endermen.portal");
            }
        });
    }
    openAddonSettings(player) {
        const form = new ModalFormData();
        form.title({
            translate: "razz_ew:world_settings"
        });
        form.toggle({
            translate: "razz_ew:world_settings_new_waystone_gui"
        }, this.configMasterWaystoneGUIv1_1);
        form.toggle({
            translate: "razz_ew:world_settings_gen_waystones"
        }, this.configGenWaystones);
        form.toggle({
            translate: "razz_ew:world_settings_enable_master_player_wayfinder"
        }, this.configEnableMasterPlayerWayfinder);
        form.slider({
            translate: "razz_ew:world_settings_elevator_distance"
        }, 3, 150, 1, this.configElevatorSearchDist);
        form.show(player).then((response)=>{
            if (response.formValues) {
                let i = 0;
                for (const entry of response.formValues){
                    if (i == 0) {
                        this.setPropSafeAndUpdateRuntimeConfig(world, this.propConfigMasterWaystoneGUIv1_1, entry);
                    } else if (i == 1) {
                        this.setPropSafeAndUpdateRuntimeConfig(world, this.propConfigGenWaystones, entry);
                    } else if (i == 2) {
                        this.setPropSafeAndUpdateRuntimeConfig(world, this.propConfigEnableMasterPlayerWayfinder, entry);
                    } else if (i == 3) {
                        this.setPropSafeAndUpdateRuntimeConfig(world, this.propConfigElevatorSearchDist, entry);
                    }
                    i++;
                }
            }
            this.openGuidebook(player);
        });
    }
    openWaystoneGUISettings(dim, player, dataSource, destinations) {
        const form = new ActionFormData();
        form.title("Settings");
        form.button("Change destination names", "textures/razz/ew/gui/misc/ew_icon_edit_name.png");
        form.button("Change destination icons", "textures/razz/ew/gui/misc/ew_icon_edit_icon.png");
        form.button("Back", "textures/razz/ew/gui/misc/ew_icon_return.png");
        form.show(player).then((response)=>{
            if (response.selection == 0) {
                this.openWaystoneGUISettingsEditDestinationNames(dim, player, dataSource, destinations);
            } else if (response.selection == 1) {
                this.openWaystoneGUISettingsEditDestinationIcons(dim, player, dataSource, destinations);
            } else if (response.selection == 2) {
                this.openMasterWaystoneGUIv1_1(dim, player, dataSource);
            }
        });
    }
    openWaystoneGUISettingsEditDestinationNames(dim, player, dataSource, destinations) {
        const form = new ModalFormData();
        //form.textField("test", "test2", "what");
        let i = 0;
        for (const entry of destinations){
            let entryInt = entry;
            let name = entryInt.name;
            if (name == "") {
                name = "Waystone " + i;
            }
            form.textField(name, "Add a name here...", entryInt.name);
            i++;
        }
        form.show(player).then((response)=>{
            if (response.formValues) {
                let i = 0;
                for (const entry of response.formValues){
                    destinations[i].name = entry;
                    i++;
                }
                let data;
                if (dataSource instanceof ItemStack) {
                    data = dataSource;
                } else {
                    data = this.getOrCreateMasterWaystoneEntity(dim, dataSource);
                }
                data.setDynamicProperty("data", JSON.stringify(destinations));
                if (dataSource instanceof ItemStack) {
                    const equippable = player.getComponent("equippable");
                    equippable.setEquipment(EquipmentSlot.Mainhand, dataSource);
                }
                this.openWaystoneGUISettings(dim, player, dataSource, destinations);
            }
        });
    }
    openWaystoneGUISettingsEditDestinationIcons(dim, player, dataSource, destinations) {
        const form = new ActionFormData();
        let i = 0;
        for (const entryTemp of destinations){
            if (entryTemp == null) continue;
            let entry = entryTemp;
            let name = entry.name;
            if (name == "") {
                name = "Waystone " + i;
            }
            form.button(name, this.basePathMasterWaystoneTextures + this.waystoneIconTextures[entry.icon_index ?? 0] + ".png");
            i++;
        }
        let cancel = i;
        form.button("Back", "textures/razz/ew/gui/misc/ew_icon_return.png");
        form.show(player).then((response)=>{
            if (response.selection !== undefined) {
                if (response.selection != cancel) {
                    this.openWaystoneGUISettingsEditDestinationIcon(dim, player, dataSource, destinations, destinations[response.selection], response.selection);
                } else {
                    this.openWaystoneGUISettings(dim, player, dataSource, destinations);
                }
            }
        });
    }
    openWaystoneGUISettingsEditDestinationIcon(dim, player, dataSource, destinations, entryToEdit, index) {
        const form = new ActionFormData();
        let i = 0;
        form.title("Icon for " + entryToEdit.name);
        form.body("\n Choose an icon for " + entryToEdit.name);
        for (const icon of this.waystoneIcons){
            form.button(icon, this.basePathMasterWaystoneTextures + this.waystoneIconTextures[i ?? 0] + ".png");
            i++;
        }
        let cancel = i;
        form.button("Cancel", "textures/razz/ew/gui/misc/ew_icon_return.png");
        form.show(player).then((response)=>{
            if (response.selection !== undefined) {
                if (response.selection != cancel) {
                    entryToEdit.icon_index = response.selection;
                    destinations[index] = entryToEdit;
                    //let data: Entity = this.getOrCreateMasterWaystoneEntity(dim, dataSource);
                    let data;
                    if (dataSource instanceof ItemStack) {
                        data = dataSource;
                    } else {
                        data = this.getOrCreateMasterWaystoneEntity(dim, dataSource);
                    }
                    data.setDynamicProperty("data", JSON.stringify(destinations));
                    if (dataSource instanceof ItemStack) {
                        const equippable = player.getComponent("equippable");
                        equippable.setEquipment(EquipmentSlot.Mainhand, dataSource);
                    }
                //this.addOrUpdatePlayerMasterWayfinderDataEntryUsingData(playerMasterWayfinderDataEntry);
                //this.openMasterPlayerWayfinderGUIEditDestinationIcons(player, playerMasterWayfinderDataCombined);
                }
                this.openWaystoneGUISettingsEditDestinationIcons(dim, player, dataSource, destinations);
            }
        });
    }
    openWaystoneGUISettingsEditDestinationIconsv1(dim, player, pos, destinations) {
        const form = new ModalFormData();
        let i = 0;
        for (const entry of destinations){
            let entryInt = entry;
            let name = entryInt.name;
            if (name == "") {
                name = "Waystone " + i;
            }
            //form.textField(name, "Add a name here...", entryInt.name);
            form.dropdown(name, this.waystoneIcons, entryInt.icon_index ?? 0);
            i++;
        }
        form.show(player).then((response)=>{
            if (response.formValues) {
                console.warn();
                let i = 0;
                for (const entry of response.formValues){
                    destinations[i].icon_index = entry;
                    i++;
                }
                let data = this.getOrCreateMasterWaystoneEntity(dim, pos);
                data.setDynamicProperty("data", JSON.stringify(destinations));
                this.openWaystoneGUISettings(dim, player, pos, destinations);
            }
        });
    }
    openMasterWaystoneGUIChangesIfOldV1(dim, player, pos) {
        let data = this.getOrCreateMasterWaystoneEntity(dim, pos);
        let version = CoroUtil.getPropStringOrUndefined(data, this.propMasterWaystoneDataMenuVersion);
        //if old master waystone
        if (version === undefined) {
            let dontShowAgain = CoroUtil.getPropBool(player, this.propPlayerMasterWaystoneUpdateNotifyv1_1DontShowAgain);
            //if player hasent marked dont show again yet
            if (!dontShowAgain) {
                this.openMasterWaystonev1_1ChangesGUI(player);
            }
        }
    }
    openMasterWaystoneGUI(dim, player, pos) {
        let data = this.getOrCreateMasterWaystoneEntity(dim, pos);
        let isOpen = CoroUtil.getPropBool(data, "isOpen");
        if (!isOpen) {
            data.setDynamicProperty("isOpen", true);
            player.setDynamicProperty("getLastLookedAtEntity", 0);
            let dataString = CoroUtil.getPropString(data, "data");
            if (dataString !== "") {
                let jsonArray = JSON.parse(dataString);
                if (jsonArray.length > 0) {
                    if (this.configMasterWaystonePistonNoise) {
                        player.playSound("tile.piston.out", {
                            volume: 0.3,
                            pitch: 1.2
                        });
                    }
                }
                let spawnVecAdjust = CoroUtil.getAxisLockedScalarVectorBasedOnBlockState(dim, pos);
                //var menu: Entity = dim.spawnEntity(this.entityNameWaystoneMasterMenu, CoroUtil.add(pos, 0.5 + spawnVecAdjust.x, 1.7, 0.5 + spawnVecAdjust.z));
                var menu = dim.spawnEntity(this.entityNameWaystoneMasterMenu, CoroUtil.add(pos, 0.5, 2.1, 0.5));
                let rot = CoroUtil.toDegrees(CoroUtil.getAxisLockedRotationBasedOnBlockState(dim, pos)) / 90;
                menu.setProperty(MODID + ":rot", rot);
                menu.setDynamicProperty(this.propDataEntID, data.id);
                data.setDynamicProperty(this.propMasterWaystoneDataMenuEntID, menu.id);
                let count = 0;
                //let pushOut = 0.33
                let pushOut = 0.35;
                let yAdj = 0;
                let yAdjAmount = -0.3;
                for (const entry of jsonArray){
                    let leftRightAdj = -0.25;
                    if ((count + 1) % 2 == 0) {
                        leftRightAdj *= -1;
                    }
                    if (count + 1 >= 7) {
                        yAdj = yAdjAmount * 4;
                    } else if (count + 1 >= 5) {
                        yAdj = yAdjAmount * 3;
                    } else if (count + 1 >= 3) {
                        yAdj = yAdjAmount * 2;
                    } else {
                        yAdj = yAdjAmount * 1;
                    }
                    let posRelative = CoroUtil.add(pos, 0.5 + leftRightAdj * spawnVecAdjust.z + pushOut * spawnVecAdjust.x, 2.3 + yAdj, 0.5 + leftRightAdj * -spawnVecAdjust.x + pushOut * spawnVecAdjust.z);
                    var menu_entry = dim.spawnEntity(this.entityNameWaystoneMasterMenuDestination, posRelative);
                    menu_entry.setProperty(MODID + ":rot", rot);
                    menu_entry.setProperty(MODID + ":index", count);
                    let leftRight = (count + 1) % 2 == 0 ? "left" : "right";
                    //log("?? " + leftRight)
                    menu_entry.setProperty(MODID + ":model", leftRight);
                    menu_entry.setDynamicProperty(this.propDataEntID, data.id);
                    menu_entry.setDynamicProperty(this.propMenuDataDestinationIndex, count);
                    entry.ent_id = menu_entry.id;
                    count++;
                }
                data.setDynamicProperty("data", JSON.stringify(jsonArray));
            }
        //var menu: Entity = dim.spawnEntity(this.entityWaystoneMasterMenuName, CoroUtil.add(pos, 0, 0, 1));
        } else {
        //log("already open")
        }
    }
    isMasterWaystoneEntityHere(dim, posBlock) {
        let posEntity = CoroUtil.add(posBlock, 0.5, 0.5, 0.5);
        //log("making at " + CoroUtil.vector3ToString(posEntity));
        let entities = dim.getEntities({
            type: this.entityNameWaystoneMasterData,
            closest: 1,
            location: posEntity,
            maxDistance: 0.5
        });
        if (entities.length > 0) {
            return true;
        }
        return false;
    }
    getOrCreateMasterWaystoneEntity(dim, posBlock) {
        let posEntity = CoroUtil.add(posBlock, 0.5, 0.5, 0.5);
        //log("making at " + CoroUtil.vector3ToString(posEntity));
        let entities = dim.getEntities({
            type: this.entityNameWaystoneMasterData,
            closest: 1,
            location: posEntity,
            maxDistance: 0.5
        });
        if (entities.length > 0) {
            //log("fetched existing waystone entity")
            return entities[0];
        } else {
            //log("making new waystone entity")
            var ent = dim.spawnEntity(this.entityNameWaystoneMasterData, posEntity);
            ent.dimension.getPlayers({
                location: ent.location,
                maxDistance: 12
            }).forEach((player)=>{
                player.playSound("random.levelup", {
                    volume: 0.1
                });
            });
            ent.dimension.spawnParticle("razz_ew:wayfinder_charge_explode_activate", ent.location);
            //mark new master waystones as freshly made v1_1 ones, avoids the gui changes popup
            ent.setDynamicProperty(this.propMasterWaystoneDataMenuVersion, this.propMasterWaystoneDataMenuVersionDefinition_v1_1);
            return ent;
        }
    }
    /* getOrCreateDataForItemStack(dim: Dimension, itemStack: ItemStack): Entity {
		let posEntity: Vector3 = CoroUtil.add(posBlock, 0.5, 0.5, 0.5);
		//log("making at " + CoroUtil.vector3ToString(posEntity));
		let entities = dim.getEntities({ type: this.entityNameWaystoneMasterData, closest: 1, location: posEntity, maxDistance: 0.5 });
		if (entities.length > 0) {
			//log("fetched existing waystone entity")
			return entities[0];
		} else {
			//log("making new waystone entity")
			var ent: Entity = dim.spawnEntity(this.entityNameWaystoneMasterData, posEntity);
            ent.dimension.getPlayers({location: ent.location, maxDistance: 12}).forEach(player => { player.playSound("random.levelup", {volume: 0.1}); });
            ent.dimension.spawnParticle("razz_ew:wayfinder_charge_explode_activate", ent.location);
            //mark new master waystones as freshly made v1_1 ones, avoids the gui changes popup
            ent.setDynamicProperty(this.propMasterWaystoneDataMenuVersion, this.propMasterWaystoneDataMenuVersionDefinition_v1_1);
			return ent;
		}
	} */ removeMasterWaystoneEntity(dim, posBlock) {
        let posEntity = CoroUtil.add(posBlock, 0.5, 0.5, 0.5);
        let entities = dim.getEntities({
            type: this.entityNameWaystoneMasterData,
            closest: 1,
            location: posEntity,
            maxDistance: 0.5
        });
        log("killing at " + CoroUtil.vector3ToString(posEntity));
        if (entities.length > 0) {
            log("found existing waystone entity to kill");
            this.closeMasterWaystoneGUI(entities[0], true);
            CoroUtil.despawnEntity(entities[0]);
        //entities[0].kill();
        } else {
            log("didnt find existing waystone entity to kill");
        }
    }
    /**
	 * Scans downwards for a good spot
	 * 
	 * @param dim 
	 * @param pos y value not used, will use 160 and scan down
	 * @returns: the safe y value, above solid, if not safe, returns -999
	 */ getSafeSpotToSpawnWaystoneOnSurfaceHeightmapHere(dim, pos) {
        var posWithHeightmap = {
            x: pos.x,
            y: this.getHeightMap(dim, pos),
            z: pos.z
        };
        if (posWithHeightmap.y >= CoroUtil.getMinWorldHeight(dim)) {
            posWithHeightmap = {
                x: pos.x,
                y: this.getFirstNonLeafNonAirNonLiquidBlock(dim, posWithHeightmap),
                z: pos.z
            };
            if (posWithHeightmap.y >= CoroUtil.getMinWorldHeight(dim)) {
                var blockUp = CoroUtil.getBlock(dim, CoroUtil.up(posWithHeightmap, 1));
                if (blockUp != undefined && this.isPosReplaceableBlockOrAir(dim, blockUp)) {
                    blockUp = CoroUtil.getBlock(dim, CoroUtil.up(posWithHeightmap, 2));
                    if (blockUp != undefined && this.isPosReplaceableBlockOrAir(dim, blockUp)) {
                        //TODO post release: air around it too?, i think ill leave it to the tp code to find an air spot later via second tp after first + chunks load
                        return CoroUtil.up(posWithHeightmap, 1).y;
                    }
                }
            }
        }
        return -999;
    }
    getSafeSpotToSpawnWaystoneSolidGrowndBelowHere(dim, pos) {
        //var posWithHeightmap = { x: pos.x, y: this.getHeightMap(dim, pos), z: pos.z};
        if (pos.y >= CoroUtil.getMinWorldHeight(dim)) {
            pos = {
                x: pos.x,
                y: this.getFirstNonLeafNonAirNonLiquidBlock(dim, pos),
                z: pos.z
            };
            //log("getFirstNonLeafNonAirNonLiquidBlock y " + pos.y)
            if (pos.y >= CoroUtil.getMinWorldHeight(dim)) {
                var blockUp = CoroUtil.getBlock(dim, CoroUtil.up(pos, 1));
                if (blockUp != undefined) {
                    let canGen = this.isPosReplaceableBlockOrAir(dim, blockUp);
                    /* if (!canGen) {
						//log("1 " + CoroUtil.vector3ToString(block))
						for (var entry of blocks_allowed_to_gen_over) {
							if (blockUp?.permutation.matches("minecraft:" + entry)) {
								//log("can gen")
								canGen = true;
								break;
							}
						}
					} */ if (canGen) {
                        //log("2")
                        blockUp = CoroUtil.getBlock(dim, CoroUtil.up(pos, 2));
                        if (blockUp != undefined) {
                            canGen = this.isPosReplaceableBlockOrAir(dim, blockUp);
                            /* if (!canGen) {
								for (var entry of blocks_allowed_to_gen_over) {
									if (blockUp?.permutation.matches("minecraft:" + entry)) {
										//log("can gen2")
										canGen = true;
										break;
									}
								}
							} */ //TODO post release: air around it too?
                            if (canGen) {
                                return CoroUtil.up(pos, 1).y;
                            } else {
                            //log("couldnt gen2")
                            }
                        } else {
                        //log("couldnt gen")
                        }
                    }
                }
            }
        }
        return -998;
    }
    getFirstNonLeafNonAirNonLiquidBlock(dim, pos) {
        var i = 0;
        for(; i < this.configMaxScanDownDistanceFromPosCave; i++){
            var posScan = pos;
            posScan.y -= i;
            //if (posScan.y < CoroUtil.getMinWorldHeight(dim)) return -999;
            var block = CoroUtil.getBlock(dim, posScan);
            if (block !== undefined) {
                //log("try " + CoroUtil.vector3ToString(block))
                if (!block.isAir && !block.isLiquid) {
                    let foundReplaceable = false;
                    if (this.isPosReplaceableBlockOrAir(dim, block)) {
                        log("found a replacable");
                        foundReplaceable = true;
                    }
                    /* 
					for (var entry of blocks_allowed_to_gen_over) {
						if (block?.permutation.matches("minecraft:" + entry)) {
							log("found a replacable: " + entry)
							foundReplaceable = true;
							break;
						}
					} */ //?????????
                    if (foundReplaceable) {
                        continue;
                    }
                    var foundLeaves = false;
                    var block_list = [
                        "leaves",
                        "leaves2",
                        "acacia_leaves",
                        "birch_leaves",
                        "dark_oak_leaves",
                        "jungle_leaves",
                        "oak_leaves",
                        "azalea_leaves",
                        "azalea_leaves_flowered",
                        "cherry_leaves",
                        "mangrove_leaves"
                    ];
                    for (var entry of block_list){
                        if (block.permutation.matches("minecraft:" + entry)) {
                            foundLeaves = true;
                            break;
                        }
                    }
                    if (!foundLeaves) {
                        //log("!!!" + CoroUtil.vector3ToString(block))
                        for (var entry of solid_ground_blocks_to_gen_on){
                            if (block?.permutation.matches("minecraft:" + entry)) {
                                log("matches " + entry);
                                return posScan.y;
                            }
                        }
                        return -999;
                    }
                }
            }
        }
        return -999;
    }
    getHeightMap(dim, pos) {
        if (CoroUtil.getBlock(dim, pos) !== undefined) {
            var result = dim.getBlockFromRay({
                x: pos.x,
                y: 160,
                z: pos.z
            }, {
                x: 0,
                y: -1,
                z: 0
            });
            if (result !== undefined) {
                var y = result.block.location.y;
                if (y > CoroUtil.getMinWorldHeight(dim)) {
                    return y;
                }
            }
        }
        return -999;
    }
    tick() {
        super.tick();
        if (!this.modActive) return;
        //this.itemWasUsedOnBlock = false;
        /* let rand = Math.random()
        let test = Math.floor(rand * 6)
        log("test: " + test); */ //let overworld = world.getDimension('overworld');
        if (this.echoWayfinderFixSetLoreDelay > 0) {
            this.echoWayfinderFixSetLoreDelay--;
            if (this.echoWayfinderFixSetLoreDelay == 0) {
                if (this.echoWayfinderFixTempItemStack !== undefined && this.echoWayfinderFixTempPlayerID !== "") {
                    let playerTemp = world.getEntity(this.echoWayfinderFixTempPlayerID);
                    if (playerTemp instanceof Player) {
                        CoroUtil.setSlot(playerTemp, this.echoWayfinderFixTempItemStack, EquipmentSlot.Mainhand);
                        this.echoWayfinderFixTempPlayerID = "";
                        this.echoWayfinderFixTempItemStack = undefined;
                    }
                }
            }
        }
        let players = world.getAllPlayers();
        for (const player of players){
            //after events fire, reset this for next event use check
            if (this.getPlayerUsedWayfinderOnBlock(player)) {
                log("resetting player used wayfinder on block for player " + player.name);
                player.setDynamicProperty(this.propPlayerItemWasUsedOnBlock, false);
            }
            player.setDynamicProperty(this.propPlayerMasterWayfinderWasUsedOnBlock, false);
            //TEMP TO TEST GUI
            //CoroUtil.setPropSafe(player, this.propPlayerMasterWaystoneUpdateNotifyv1_1DontShowAgain, false);
            if (this.configMasterWaystoneGUIv1_1 && system.currentTick % 20 == 0) {
                //new changes gui open, might only work for 1 player if many near, no big deal
                //let player: Player | undefined = CoroUtil.getClosestPlayerToPosWithCutoff(CoroUtil.center(data.location), 3);
                let entities = player.dimension.getEntities({
                    type: this.entityNameWaystoneMasterData,
                    location: player.location,
                    maxDistance: 3,
                    closest: 1
                });
                var dataPlayer = this.getPlayerData(player);
                if (entities.length > 0) {
                    if (!dataPlayer.nearMasterWaystoneForOldGui) {
                        dataPlayer.nearMasterWaystoneForOldGui = true;
                        for (const data of entities){
                            let posAdjustedToBlockCoord = CoroUtil.uncenter(data.location);
                            this.openMasterWaystoneGUIChangesIfOldV1(player.dimension, player, posAdjustedToBlockCoord);
                        }
                    }
                } else {
                    dataPlayer.nearMasterWaystoneForOldGui = false;
                }
            }
            /* if (this.configSceneTest) {
                player.runCommandAsync("/effect @e[family=monster] slowness 1 255 true");
                player.runCommandAsync("/effect @e[type=hoglin] slowness 1 255 true");
                player.runCommandAsync("/kill @e[type=arrow]");
                player.runCommandAsync("/kill @e[type=fireball]");
                player.runCommandAsync("/kill @e[type=small_fireball]");
                player.runCommandAsync("/effect @a regeneration 3 255 true");
            } */ let dim = player.dimension;
            //log(dim.id)
            //dim.runCommandAsync("execute at @e[type=razz_ew:master_waystone_menu_destination] run particle minecraft:heart_particle ~ ~ ~");
            var data = this.getPlayerData(player);
            //misc particle effects
            let maxRange = 6;
            let maxRangeY = 2;
            let tryPos = {
                x: player.location.x + Math.random() * maxRange - Math.random() * maxRange,
                y: player.location.y + Math.random() * maxRangeY - Math.random() * maxRangeY,
                z: player.location.z + Math.random() * maxRange - Math.random() * maxRange
            };
            //tryPos = CoroUtil.up(player.location, -1);
            if (CoroUtil.isLoaded(dim, tryPos)) {
                let block = CoroUtil.getBlock(dim, tryPos);
                if (block !== undefined) {
                    if (this.isWaystoneActivated(block)) {
                        //log("particles");
                        dim.spawnParticle("razz_ew:waystone_shimmer_long", CoroUtil.up(CoroUtil.centerXZ(block), -1));
                    }
                }
            }
            //elevator logic
            if (player.isSneaking || !player.isOnGround) {
                let pos = player.location;
                pos.y--;
                let block = CoroUtil.getBlock(dim, pos);
                if (player.isSneaking && !data.wasSneaking) {
                    if (block !== undefined && this.isElevator(block)) {
                        let elevatorPos = this.findElevator(dim, pos, -1);
                        if (elevatorPos !== undefined) {
                            //log(CoroUtil.vector3ToString(block))
                            //log(CoroUtil.vector3ToString(elevatorPos))
                            dim.spawnParticle("razz_ew:wayfinder_charge_explode_small", CoroUtil.up(CoroUtil.centerXZ(CoroUtil.floor(block)), 1.1));
                            this.teleportWithDelayedSound(player, dim, CoroUtil.up(elevatorPos, 1));
                        } else {
                            if (this.configElevatorSearchDist != -1) {
                                this.actionBarRaw(player, '{"rawtext":[{"translate":"msg.elevator_none_found"}, {"text": " ' + this.configElevatorSearchDist + ' "}, {"translate":"msg.elevator_none_found_2"}]}');
                            } else {
                                this.actionBarRaw(player, '{"rawtext":[{"translate":"msg.elevator_none_found_3"}]}');
                            }
                        }
                    }
                } else if (!player.isOnGround && data.wasOnGround) {
                    if (block !== undefined && this.isElevator(block)) {
                        let elevatorPos = this.findElevator(dim, pos, 1);
                        if (elevatorPos !== undefined) {
                            dim.spawnParticle("razz_ew:wayfinder_charge_explode_small", CoroUtil.up(CoroUtil.centerXZ(CoroUtil.floor(block)), 1.1));
                            this.teleportWithDelayedSound(player, dim, CoroUtil.up(elevatorPos, 0.9));
                        //log(CoroUtil.vector3ToString(elevatorPos));
                        } else {
                            if (this.configElevatorSearchDist != -1) {
                                this.actionBarRaw(player, '{"rawtext":[{"translate":"msg.elevator_none_found"}, {"text": " ' + this.configElevatorSearchDist + ' "}, {"translate":"msg.elevator_none_found_2"}]}');
                            } else {
                                this.actionBarRaw(player, '{"rawtext":[{"translate":"msg.elevator_none_found_3"}]}');
                            }
                        }
                    }
                }
            }
            //actionbar logic
            let titleTicks = CoroUtil.getPropNumber(player, this.propPlayerTitleTimes);
            if (titleTicks > 0) {
                titleTicks--;
                player.setDynamicProperty(this.propPlayerTitleTimes, titleTicks);
            }
            let wayfinderCooldownTicks = CoroUtil.getPropNumber(player, this.propPlayerWayfinderUseCooldown);
            if (wayfinderCooldownTicks > 0) {
                wayfinderCooldownTicks--;
                player.setDynamicProperty(this.propPlayerWayfinderUseCooldown, wayfinderCooldownTicks);
            }
            //wayfinder lore updates
            if (system.currentTick % 5 == 0 && titleTicks == 0) {
                let itemStack = CoroUtil.getSlot(player, EquipmentSlot.Mainhand);
                if (itemStack !== undefined) {
                    if (itemStack.typeId == this.itemNameWayfinderSimple || itemStack.typeId == this.itemNameWayfinderClairvoyant || itemStack.typeId == this.itemNameWayfinderEcho) {
                        let propItemID = CoroUtil.getPropString(player, this.propPlayerLastItemInHandID);
                        let propItemLoreFirstLine = CoroUtil.getPropString(player, this.propPlayerLastItemInHandLoreFirstLine);
                        let lore = itemStack.getLore()[0] ?? "";
                        let loreRecal = itemStack.getLore()[1] ?? "";
                        if (loreRecal !== "" /*  && loreRecal.includes("Recal") */ ) {
                            lore = loreRecal;
                        }
                        //this.titleTimes(player, 0, 20, 5);
                        if (propItemID !== itemStack.typeId || propItemLoreFirstLine !== lore) {
                            /* log("1 " + propItemID + " - " + itemStack.typeId);
                                log("1 " + propItemLoreFirstLine + " - " + lore); */ if (lore !== "") {
                                this.actionBar(player, lore);
                            } else {
                                this.actionBarRawSimple(player, "msg.wayfinder_not_bound");
                            }
                        } else {}
                        player.setDynamicProperty(this.propPlayerLastItemInHandID, itemStack.typeId);
                        player.setDynamicProperty(this.propPlayerLastItemInHandLoreFirstLine, lore);
                    } else {
                        player.setDynamicProperty(this.propPlayerLastItemInHandID, itemStack.typeId);
                        player.setDynamicProperty(this.propPlayerLastItemInHandLoreFirstLine, "");
                    }
                } else {
                    player.setDynamicProperty(this.propPlayerLastItemInHandID, "");
                    player.setDynamicProperty(this.propPlayerLastItemInHandLoreFirstLine, "");
                }
            }
            //lectern wayfinder logic
            let isUsingLecternWayfinder = CoroUtil.getPropBool(player, this.propPlayerIsUsingLecternWayfinder);
            if (system.currentTick % 10 == 0 || isUsingLecternWayfinder) {
                //log("player sneaking? " + player.isSneaking)
                let stillUsing = false;
                if (player.isSneaking) {
                    let ent = this.getClosestLecternWayfinderToPlayer(player);
                    if (ent !== undefined) {
                        stillUsing = true;
                        isUsingLecternWayfinder = true;
                        player.setDynamicProperty(this.propPlayerIsUsingLecternWayfinder, true);
                        if (system.currentTick % 10 == 0) {
                            dim.spawnParticle("razz_ew:wayfinder_charge_emit", CoroUtil.up(ent.location, 0.5));
                        }
                    }
                }
                if (!stillUsing) {
                    isUsingLecternWayfinder = false;
                    player.setDynamicProperty(this.propPlayerIsUsingLecternWayfinder, false);
                }
            }
            data.wasOnGround = player.isOnGround;
            data.wasSneaking = player.isSneaking;
            //master waystone look at logic
            if (system.currentTick % 5 == 0 && !this.configMasterWaystoneGUIv1_1) {
                let entities = dim.getEntities({
                    type: this.entityNameWaystoneMasterData,
                    location: player.location,
                    maxDistance: 7
                });
                if (entities.length > 0) {
                    let hitResult = CoroUtil.customRaytrace(player, this.entityNameWaystoneMasterMenuDestination, 0.2, 0.25, 5);
                    for (let entry of hitResult){
                        //log(entry.typeId);
                        if (entry.typeId == this.entityNameWaystoneMasterMenuDestination) {
                            this.lookedAtMasterWaystoneMenuDestination(entry, player);
                        }
                    }
                }
            }
            //log("speed: " + CoroUtil.getSpeed(player));
            //using wayfinders logic
            let startedWarpCount = CoroUtil.getPropBool(player, this.propPlayerStartedWarpCount);
            //let isUsingWayfinder: boolean = CoroUtil.getPropBool(player, this.propPlayerIsUsingWayfinder);
            //log(isUsingWayfinder + " - " + isUsingLecternWayfinder);
            let speed = CoroUtil.getSpeed(player);
            //log("speed: " + speed)
            if (startedWarpCount || isUsingLecternWayfinder) {
                let ticks = CoroUtil.getPropNumber(player, this.propPlayerUsingWayfinderTicks);
                player.setDynamicProperty(this.propPlayerUsingWayfinderTicks, ticks + 1);
                let start = 0;
                if (ticks >= start && ticks % 3 == 0) {
                    const particleOptions = new MolangVariableMap();
                    //let height = Math.min(1.2, 0.15 * ((ticks - start) / 5));
                    let height = Math.min(1.2, 0.45 * ((ticks - start) / 5));
                    //log("? " + height)
                    particleOptions.setFloat("height", 0);
                    particleOptions.setFloat("height_range", height);
                    if (CoroUtil.isLoaded(dim, player.location)) {
                        dim.spawnParticle("razz_ew:wayfinder_charge_body4", player.location, particleOptions);
                    }
                }
                if (ticks == 5) player.playSound("portal.portal", {
                    location: player.location,
                    pitch: 1.5 /*  + (ticks / 15) */ 
                });
                //log("ticks: " + ticks);
                if (ticks >= this.warpDelay) {
                    /* let ticks = CoroUtil.getPropNumber(player, this.propPlayerLowSpeedTicks);
                    player.setDynamicProperty(this.propPlayerLowSpeedTicks, ticks+1); */ player.setDynamicProperty(this.propPlayerUsingWayfinderTicks, 0);
                    if (CoroUtil.isLoaded(dim, player.location)) {
                        dim.spawnParticle("razz_ew:wayfinder_charge_explode", player.location);
                    }
                    if (startedWarpCount) {
                        let itemStack = CoroUtil.getSlot(player, EquipmentSlot.Mainhand);
                        if (itemStack !== undefined) {
                            if (itemStack.typeId == this.itemNameWayfinderSimple) {
                                if (!this.getPlayerUsedWayfinderOnBlock(player)) {
                                    let lore = itemStack.getLore();
                                    if (this.getWayfinderPosFromLore(itemStack, false) !== undefined) {
                                        //log("itemUse");
                                        /* let heightMap: number = this.getHeightMap(player.dimension, player.location);
                                        if (heightMap <= player.location.y) { */ const run = system.run(()=>{
                                            if (itemStack === undefined) return;
                                            let pos = this.getWayfinderPosFromLore(itemStack, false);
                                            let dim = this.getWayfinderDimFromLore(itemStack, false);
                                            if (pos !== undefined && dim !== undefined) {
                                                this.tryTeleportWithCost(player, dim, pos);
                                            }
                                        });
                                    /* } else {
                                            this.subtitleRaw(player, '{"rawtext":[{"translate":"msg.wayfinder_cant_see_sky"}]}');
                                        } */ } else {
                                        //wont fire, stopped earlier now
                                        this.subtitleRaw(player, '{"rawtext":[{"translate":"msg.wayfinder_not_bound1"}]}', '{"rawtext":[{"translate":"msg.wayfinder_not_bound2"}]}');
                                    }
                                }
                            }
                            if (itemStack.typeId == this.itemNameWayfinderClairvoyant) {
                                let lore = itemStack.getLore();
                                if (this.getWayfinderPosFromLore(itemStack, false) !== undefined) {
                                    //log("itemUse");
                                    if (!this.getPlayerUsedWayfinderOnBlock(player)) {
                                        const run = system.run(()=>{
                                            if (itemStack === undefined) return;
                                            let pos = this.getWayfinderPosFromLore(itemStack, false);
                                            let dim = this.getWayfinderDimFromLore(itemStack, false);
                                            if (pos !== undefined && dim !== undefined) {
                                                this.tryTeleportWithCost(player, dim, pos);
                                            }
                                        });
                                    }
                                } else {
                                    this.subtitleRawSimple(player, "msg.wayfinder_not_bound1", "msg.wayfinder_not_bound2");
                                }
                            }
                            if (itemStack.typeId == this.itemNameWayfinderEcho && this.echoWayfinderFixSetLoreDelay == 0) {
                                let lore = itemStack.getLore();
                                if (this.getWayfinderPosFromLore(itemStack, false) !== undefined) {
                                    //log("itemUse");
                                    if (!this.getPlayerUsedWayfinderOnBlock(player)) {
                                        const run = system.run(()=>{
                                            if (itemStack === undefined) return;
                                            let posBound = this.getWayfinderPosFromLore(itemStack, false);
                                            let dimBound = this.getWayfinderDimFromLore(itemStack, false);
                                            let posRecal = this.getWayfinderPosFromLore(itemStack, true);
                                            let dimRecal = this.getWayfinderDimFromLore(itemStack, true);
                                            if (posRecal !== undefined && dimRecal !== undefined) {
                                                //CoroUtil.setSlot(player, undefined, EquipmentSlot.Mainhand);
                                                //CoroUtil.setSlot(player, itemStack, EquipmentSlot.Mainhand);
                                                //log("recal tp");
                                                //this.teleportWithDelayedSound(player, dimRecal, posRecal);
                                                if (this.tryTeleportWithCost(player, dimRecal, posRecal, true)) {
                                                    debug("tp to posRecal: " + posRecal.x + ", " + posRecal.y + ", " + posRecal.z + ", " + dimRecal.id);
                                                    //we use null here to tell the method to delete the recal entry now that we've used it
                                                    this.setWayfinderPosToLore(itemStack, undefined, undefined, player.dimension, null);
                                                    this.echoWayfinderFixTempPlayerID = player.id;
                                                    this.echoWayfinderFixTempItemStack = itemStack;
                                                    this.echoWayfinderFixSetLoreDelay = 1;
                                                }
                                            } else if (posBound !== undefined && dimBound !== undefined) {
                                                let posPrev = CoroUtil.floor(player.location);
                                                let dimPrev = player.dimension;
                                                //CoroUtil.setSlot(player, undefined, EquipmentSlot.Mainhand);
                                                //CoroUtil.setSlot(player, itemStack, EquipmentSlot.Mainhand);
                                                //let posGo = this.getTeleportSpotNextToWaystone(player.dimension, posBound);
                                                //debug("tp to posGo " + CoroUtil.vector3ToString(posGo))
                                                if (this.tryTeleportWithCost(player, dimBound, posBound)) {
                                                    debug("tp to bound pos " + CoroUtil.vector3ToString(posBound));
                                                    this.setWayfinderPosToLore(itemStack, undefined, undefined, dimPrev, posPrev);
                                                    this.echoWayfinderFixTempPlayerID = player.id;
                                                    this.echoWayfinderFixTempItemStack = itemStack;
                                                    this.echoWayfinderFixSetLoreDelay = 1;
                                                }
                                            }
                                        });
                                    }
                                } else {
                                    this.subtitleRawSimple(player, "msg.wayfinder_not_bound1", "msg.wayfinder_not_bound2");
                                }
                            }
                            if (itemStack.typeId == this.itemNameWayfinderWildcard) {
                                const run = system.run(()=>{
                                    let jsonObj = this.getWaystoneLocationData();
                                    let waystones = jsonObj.overworld;
                                    if (player.dimension.id == "minecraft:nether") {
                                        waystones = jsonObj.nether;
                                    } else if (player.dimension.id == "minecraft:the_end") {
                                        waystones = jsonObj.the_end;
                                    }
                                    if (Object.keys(waystones).length > 0) {
                                        let pos = this.getFirstFarAwayWaystone(player, waystones, 100);
                                        if (pos !== undefined) {
                                            //let pos = CoroUtil.stringToVector3(this.getRandomProperty(waystones));
                                            let posGo = this.getTeleportSpotNextToWaystone(player.dimension, pos);
                                            this.teleportWithDelayedSound(player, player.dimension, posGo);
                                            player.setDynamicProperty(this.propPlayerStartedWarpCount, false);
                                            if (CoroUtil.getGameMode(player) != GameMode.creative) {
                                                CoroUtil.setSlot(player, undefined, EquipmentSlot.Mainhand);
                                            }
                                        //log("? " + waystone);
                                        } else {
                                            this.actionBarRaw(player, '{"rawtext":[{"translate":"msg.wayfinder_wildcard_cant_find"}]}');
                                        }
                                    } else {
                                        this.actionBarRaw(player, '{"rawtext":[{"translate":"msg.wayfinder_wildcard_cant_find"}]}');
                                    }
                                });
                            }
                            //its possible they switch away from this after starting it, dont care
                            if (itemStack.typeId == this.itemNameWarperPlayer || itemStack.typeId == this.itemNameWarperPlayerMaster) {
                                let username = CoroUtil.getPropString(player, this.propPlayerPlayerWayfinderUsername);
                                if (username !== "") {
                                    let playerTarget = CoroUtil.getPlayerByName(username);
                                    if (playerTarget) {
                                        this.tryTeleportWithCost(player, playerTarget.dimension, playerTarget.location, true);
                                        //clear
                                        player.setDynamicProperty(this.propPlayerPlayerWayfinderUsername);
                                    //CoroUtil.setPropSafe(player, this.propPlayerPlayerWayfinderUsername, undefined);
                                    } else {
                                        let data = this.getPlayerMasterWayfinderData(player);
                                        if (username in data) {
                                            //this.openMasterPlayerWayfinderGUIConfirmTP(player, data[username]);
                                            this.tryTeleportWithCost(player, world.getDimension(CoroUtil.dimNumberIDToStringID(data[username].last_dim)), CoroUtil.vec(data[username].last_x, data[username].last_y, data[username].last_z), true);
                                            player.setDynamicProperty(this.propPlayerPlayerWayfinderUsername);
                                        } else {
                                            this.actionBarRaw(player, '{"rawtext":[{"translate":"msg.wayfinder_player_offline"}]}');
                                        }
                                    }
                                }
                            }
                        }
                        player.setDynamicProperty(this.propPlayerWayfinderUseCooldown, this.playerWayfinderUseCooldownMax);
                        player.setDynamicProperty(this.propPlayerStartedWarpCount, false);
                    } else if (isUsingLecternWayfinder) {
                        let jsonString = this.getClosestLecternWayfinderDataToPlayer(player);
                        if (jsonString !== undefined && jsonString !== "") {
                            let wayfinderData = JSON.parse(jsonString);
                            player.setDynamicProperty(this.propPlayerUsingWayfinderTicks, 0);
                            this.tryTeleportWithCost(player, world.getDimension(CoroUtil.dimNumberIDToStringID(wayfinderData.dim)), {
                                x: wayfinderData.x,
                                y: wayfinderData.y,
                                z: wayfinderData.z
                            });
                        }
                    }
                }
            } else {
                player.setDynamicProperty(this.propPlayerUsingWayfinderTicks, 0);
            }
            //player.addExperience
            //wayfinder breaking
            if (system.currentTick % 40 == 0) {
                let pos = CoroUtil.getPropVector3OrUndefined(player, this.propPlayerLastWaystoneTPPos);
                let dimString = CoroUtil.getPropStringOrUndefined(player, this.propPlayerLastWaystoneTPDim);
                if (pos !== undefined && dimString != undefined) {
                    let dim = world.getDimension(dimString);
                    if (CoroUtil.isLoaded(dim, pos)) {
                        let block = CoroUtil.getBlock(dim, pos);
                        if (block !== undefined) {
                            if (!this.isWaystone(block)) {
                                log("detected missing waystone, destroying wayfinder or removing lore");
                                let itemStack = CoroUtil.getSlot(player, EquipmentSlot.Mainhand);
                                if (itemStack !== undefined) {
                                    if (itemStack.typeId == this.itemNameWayfinderSimple || itemStack.typeId == this.itemNameWayfinderClairvoyant || itemStack.typeId == this.itemNameWayfinderEcho) {
                                        if (CoroUtil.getGameMode(player) == GameMode.creative) {
                                            //CoroUtil.setSlot(player, undefined, EquipmentSlot.Mainhand);
                                            itemStack.setLore([]);
                                            CoroUtil.setSlot(player, itemStack, EquipmentSlot.Mainhand);
                                        } else {
                                            CoroUtil.setSlot(player, undefined, EquipmentSlot.Mainhand);
                                            this.actionBarRawSimple(player, "msg.waystone_missing");
                                        }
                                    } else if (itemStack.typeId == this.itemNameWayfinderMaster) {
                                        this.useMasterWayfinderOnAnyWaystone(player, dim, block, itemStack, true);
                                    }
                                }
                            }
                        }
                        player.setDynamicProperty(this.propPlayerLastWaystoneTPPos, undefined);
                        player.setDynamicProperty(this.propPlayerLastWaystoneTPDim, undefined);
                    }
                }
            }
            if (system.currentTick % 20 == 0) {
                this.replaceOldWayfinders(player);
            }
            if (system.currentTick % 20 == 0) {
                this.addHelpfulLore(player);
            }
            if (system.currentTick % 40 == 0) {
                let playersToUpdateFor = world.getAllPlayers();
                for (const playerToUpdateFor of players){
                //temp off
                //this.addOrUpdatePlayerMasterWayfinderDataEntry(playerToUpdateFor, player);
                }
            }
        /* let countdownToTP = CoroUtil.getPropNumber(player, this.propPlayerPlayerWayfinderCountdownToWarp);
            if (countdownToTP > 0) {
                countdownToTP--;
                if (countdownToTP == 0) {
                    let username = CoroUtil.getPropString(player, this.propPlayerPlayerWayfinderUsername);
                    if (username !== "") {
                        let playerTarget: Player | undefined = CoroUtil.getPlayerByName(username);
                        if (playerTarget) {
                            this.tryTeleportWithCost(player, playerTarget.dimension, playerTarget.location, true);
                            //clear
                            player.setDynamicProperty(this.propPlayerPlayerWayfinderUsername);
                            //CoroUtil.setPropSafe(player, this.propPlayerPlayerWayfinderUsername, undefined);
                        } else {
                            let data: PlayerMasterWayfinderData = this.getPlayerMasterWayfinderData(player);
                            if (username in data) {
                                //this.openMasterPlayerWayfinderGUIConfirmTP(player, data[username]);
                                this.tryTeleportWithCost(player, world.getDimension(CoroUtil.dimNumberIDToStringID(data[username].last_dim)), 
                                CoroUtil.vec(data[username].last_x, data[username].last_y, data[username].last_z), true);
                                player.setDynamicProperty(this.propPlayerPlayerWayfinderUsername);
                            } else {
                                this.actionBarRaw(player, '{"rawtext":[{"translate":"msg.wayfinder_player_offline"}]}');
                            }
                        }
                        
                    }
                }
                CoroUtil.setPropSafe(player, this.propPlayerPlayerWayfinderCountdownToWarp, countdownToTP);
            } */ }
        //let chunks load in for a bit before this code activates
        //chunkgen
        if (system.currentTick % 20 == 0 && this.configGenWaystones) {
            this.tickGenAlternator = !this.tickGenAlternator;
            for (const player of players){
                let dim = player.dimension;
                if (ticksSinceModInit > 200) {
                    if (this.tickGenAlternator && dim != world.getDimension("nether")) {
                        //log("tickPlayerWaystoneGenSurface")
                        this.tickPlayerWaystoneGenSurface(player);
                    }
                    if (!this.tickGenAlternator && dim != world.getDimension("the_end")) {
                        //log("tickPlayerWaystoneGenCave")
                        this.tickPlayerWaystoneGenCave(player);
                    }
                }
            }
        }
        const dimensions = [
            'overworld',
            'nether',
            'the_end'
        ];
        for (const entry of dimensions){
            //debug("tick " + entry)
            this.tickDimension(world.getDimension(entry));
        }
    }
    tickDimension(dim) {
        //manage master waystone gui and removal of data entity
        if (system.currentTick % 10 == 0) {
            let entities = dim.getEntities({
                type: this.entityNameWaystoneMasterData
            });
            for (const data of entities){
                if (data.dimension != dim) continue;
                //check for open guis, close and kill menu entities if player not near menu
                if (CoroUtil.getPropBool(data, "isOpen")) {
                    let player = CoroUtil.getClosestPlayerToPosWithCutoff(CoroUtil.center(data.location), 3.5);
                    if (player == undefined) {
                        //log("close menu, no player near");
                        this.closeMasterWaystoneGUI(data, false);
                    }
                } else {
                    if (!this.configMasterWaystoneGUIv1_1) {
                        let player = CoroUtil.getClosestPlayerToPosWithCutoff(CoroUtil.center(data.location), 3);
                        if (player !== undefined) {
                            let posAdjustedToBlockCoord = CoroUtil.uncenter(data.location);
                            this.openMasterWaystoneGUI(player.dimension, player, posAdjustedToBlockCoord);
                        }
                    }
                }
                if (CoroUtil.isLoaded(dim, CoroUtil.up(data.location, 4))) {
                    dim.spawnParticle("razz_ew:wayfinder_charge_emit_tiny", CoroUtil.up(data.location, 1.84));
                    dim.spawnParticle("razz_ew:waystone_shimmer", CoroUtil.up(data.location, -1));
                }
                //kill entity if associated block is missing
                let block = CoroUtil.getBlock(dim, data.location);
                //make sure block is loaded
                if (block !== undefined) {
                    if (!block.permutation.matches(this.blockNameWaystoneMasterBase) || !CoroUtil.getBlock(dim, CoroUtil.up(data.location, 1))?.permutation.matches(this.blockNameWaystoneMasterTop)) {
                        debug("detected no block for master waystone data, killing entity");
                        this.closeMasterWaystoneGUI(data, false);
                        //data.kill();
                        CoroUtil.despawnEntity(data);
                    }
                }
            }
        }
        //remove entity wayfinder if lectern missing
        if (system.currentTick % 100 == 0) {
            let entities = dim.getEntities({
                type: this.entityNameWayfinder
            });
            for (const entity of entities){
                if (entity.dimension != dim) continue;
                let block = CoroUtil.getBlock(dim, CoroUtil.up(entity.location, -1));
                if (block !== undefined && !block.permutation.matches("minecraft:lectern")) {
                    this.turnWayfinderEntityIntoItem(entity);
                }
            }
        }
    }
    addOrUpdatePlayerMasterWayfinderDataEntry(playerToSetDataOn, player) {
        let data = {
            uuid: player.id,
            username: player.name,
            online: true,
            icon_index: 0,
            last_dim: CoroUtil.dimStringIDToNumberID(player.dimension.id),
            last_x: player.location.x,
            last_y: player.location.y,
            last_z: player.location.z
        };
        this.addOrUpdatePlayerMasterWayfinderDataEntryUsingData(playerToSetDataOn, data);
    }
    addOrUpdatePlayerMasterWayfinderDataEntrysUsingData(player, data) {
        let jsonObj = this.getPlayerMasterWayfinderData(player);
        for(const key in data){
            let entry = data[key];
            jsonObj[entry.username] = entry;
        }
        let jsonString = JSON.stringify(jsonObj);
        log(jsonString);
        log("new json char length: " + jsonString.length + " - " + jsonString.length / CoroUtil.propStringLengthLimit * 100 + "%");
        CoroUtil.setPropSafe(player, this.propAllPlayerLocationData, jsonString);
    }
    addOrUpdatePlayerMasterWayfinderDataEntryUsingData(player, data) {
        let jsonObj = this.getPlayerMasterWayfinderData(player);
        //dont overwrite everything if it exists already, eg keep icon index
        if (data.username in jsonObj) {
            log("exists");
            jsonObj[data.username].uuid = data.uuid;
            jsonObj[data.username].username = data.username;
            if (data.icon_index !== undefined) jsonObj[data.username].icon_index = data.icon_index;
            if (data.last_x !== undefined) jsonObj[data.username].last_x = data.last_x;
            if (data.last_y !== undefined) jsonObj[data.username].last_y = data.last_y;
            if (data.last_z !== undefined) jsonObj[data.username].last_z = data.last_z;
            if (data.last_dim !== undefined) jsonObj[data.username].last_dim = data.last_dim;
            if (data.online !== undefined) jsonObj[data.username].online = data.online;
        } else {
            log("doesnt exists");
            let dataNew = {
                uuid: data.uuid,
                username: data.username,
                online: true,
                icon_index: data.icon_index,
                last_dim: data.last_dim ?? 0,
                last_x: data.last_x ?? 0,
                last_y: data.last_y ?? 0,
                last_z: data.last_z ?? 0
            };
            jsonObj[data.username] = dataNew;
        }
        let jsonString = JSON.stringify(jsonObj);
        log(jsonString + " " + player.name);
        //log("new json char length: " + jsonString.length + " - " + ((jsonString.length / CoroUtil.propStringLengthLimit) * 100) + "%");
        CoroUtil.setPropSafe(player, this.propAllPlayerLocationData, jsonString);
    }
    getPlayerMasterWayfinderData(player) {
        let jsonObj = {};
        let jsonStr = CoroUtil.getPropString(player, this.propAllPlayerLocationData);
        if (jsonStr !== "") {
            jsonObj = JSON.parse(jsonStr);
        }
        //log("data: " + jsonStr);
        return jsonObj;
    }
    closeMasterWaystoneGUI(data, fastDespawn) {
        let jsonArray = this.getWaystoneJsonData(data);
        //log("jsonArray: " + jsonArray)
        let count = 0;
        for (const entry of jsonArray){
            //had a waystone with bad data, needed this to fix script
            if (entry == null) continue;
            if (entry.ent_id !== "") {
                //log("entry.ent_id " + entry.ent_id)
                let entity = world.getEntity(entry.ent_id);
                //entity?.kill();
                if (fastDespawn) {
                    entity?.triggerEvent("event:despawn_real");
                } else {
                    CoroUtil.despawnEntity(entity);
                }
                let isRight = (count + 1) % 2 == 0;
                //menuDestination.runCommandAsync("playanimation @s " + (!isRight ? "pop_right" : "pop_left"));
                entity?.runCommandAsync('playanimation @s ' + (!isRight ? "close_right" : "close_left") + ' "" 0 "" ""');
            }
            count++;
        }
        let ent_id_menu = CoroUtil.getPropString(data, this.propMasterWaystoneDataMenuEntID);
        if (ent_id_menu !== "") {
            let entity = world.getEntity(ent_id_menu);
            //entity?.kill();
            CoroUtil.despawnEntity(entity);
        }
        data.setDynamicProperty("isOpen", false);
        if (jsonArray.length > 0) {
            if (this.configMasterWaystonePistonNoise) {
                data.runCommandAsync("execute as @a[r=8] at @s run playsound tile.piston.out @s ~ ~ ~ 0.3 1.2");
            }
        }
    //data.runCommandAsync('playanimation @e[type=razz_ew:master_waystone_menu_destination] close_right "" 0 "" ""');
    //data.runCommandAsync("execute as @a[r=8] at @s run say what");
    //player.playSound("tile.piston.out", {volume: 0.3, pitch: 1.2});
    }
    isElevator(block) {
        for(const name in elevators){
            if (block?.permutation.matches(this.blockNameElevatorBase + name) || block?.permutation.matches(this.blockNameElevatorBase + name + "_activated")) {
                return true;
            }
        }
        return false;
    }
    getElevatorOrUndefined(dim, pos) {
        let block = CoroUtil.getBlock(dim, pos);
        if (block === undefined) return undefined;
        for(const name in elevators){
            if (block?.permutation.matches(this.blockNameElevatorBase + name)) {
                return this.blockNameElevatorBase + name;
            } else if (block?.permutation.matches(this.blockNameElevatorBase + name + "_activated")) {
                return this.blockNameElevatorBase + name + "_activated";
            }
        }
        return undefined;
    }
    /**
	 * Assumes the origin position is an activated elevator
	 * Check upper and lower for elevators, then for those, check again, if they dont have another neighbor, set them to disabled
	 * 
	 * @param dim 
	 * @param pos 
	 */ updateElevatorsActivatedState(dim, pos) {
        this.updateElevatorsActivatedStateInDirection(dim, pos, 1);
        this.updateElevatorsActivatedStateInDirection(dim, pos, -1);
    }
    updateElevatorsActivatedStateInDirection(dim, pos, direction) {
        let posElevatorNeighbor = this.findElevator(dim, pos, direction);
        if (posElevatorNeighbor !== undefined) {
            let blockElevatorNeighborActivated = this.getElevatorOrUndefined(dim, posElevatorNeighbor);
            if (blockElevatorNeighborActivated !== undefined) {
                let posElevatorNeighborToTheNeighbor = this.findElevator(dim, posElevatorNeighbor, direction);
                //no other found, need to deactivate the elevator Neighbor
                if (posElevatorNeighborToTheNeighbor === undefined) {
                    posElevatorNeighborToTheNeighbor = this.findElevator(dim, posElevatorNeighbor, -direction);
                    if (posElevatorNeighborToTheNeighbor === undefined) {
                        CoroUtil.setBlockAt(dim, posElevatorNeighbor, blockElevatorNeighborActivated.replace("_activated", ""));
                    }
                }
            }
        }
    }
    updateElevatorsDeactivatedState(dim, pos) {
        this.updateElevatorsDeactivatedStateInDirection(dim, pos, 1);
        this.updateElevatorsDeactivatedStateInDirection(dim, pos, -1);
    }
    updateElevatorsDeactivatedStateInDirection(dim, pos, direction) {
        let posElevatorNeighbor = this.findElevator(dim, pos, direction);
        if (posElevatorNeighbor !== undefined) {
            let blockElevatorNeighbor = this.getElevatorOrUndefined(dim, posElevatorNeighbor);
            if (blockElevatorNeighbor !== undefined) {
                if (!blockElevatorNeighbor.includes("_activated")) {
                    CoroUtil.setBlockAt(dim, posElevatorNeighbor, blockElevatorNeighbor + "_activated");
                }
                let blockElevatorDeactivated = this.getElevatorOrUndefined(dim, pos);
                if (blockElevatorDeactivated !== undefined) {
                    if (!blockElevatorDeactivated.includes("_activated")) {
                        CoroUtil.setBlockAt(dim, pos, blockElevatorDeactivated + "_activated");
                    }
                }
            /* CoroUtil.setBlockAt(dim, posElevatorNeighbor, blockElevatorNeighborDeactivated.replace("_inactive_", "_active_"));
				let blockElevatorDeactivated: string | undefined = this.getElevatorOrUndefined(dim, pos);
				if (blockElevatorDeactivated !== undefined) {
					CoroUtil.setBlockAt(dim, pos, blockElevatorDeactivated.replace("_inactive_", "_active_"));
				} */ }
        }
    }
    findElevator(dim, pos, direction) {
        let found = false;
        let offset = 0;
        let diff = 0;
        while(!found && pos.y + offset < CoroUtil.getMaxWorldHeight(dim) && pos.y + offset > CoroUtil.getMinWorldHeight(dim) + 1 && (this.configElevatorSearchDist == -1 || diff <= this.configElevatorSearchDist)){
            offset += direction;
            let checkPos = {
                x: pos.x,
                y: pos.y + offset,
                z: pos.z
            };
            let block = CoroUtil.getBlock(dim, checkPos);
            if (block !== undefined && this.isElevator(block)) {
                return {
                    x: pos.x,
                    y: checkPos.y,
                    z: pos.z
                };
            }
            diff++;
        }
        return undefined;
    }
    tickPlayerWaystoneGenSurface(player) {
        let dim = player.dimension;
        let posRegion = this.getRegionFromPos(player.location);
        //log("region: " + posRegion.x + " - " + posRegion.z);
        let propname = this.propGennedWaystonePrefix + dim.id + "_" + posRegion.x + "_" + posRegion.z;
        let region_genned = world.getDynamicProperty(propname);
        if (region_genned) {
        //log("marked as genned")
        } else {
            //log("try gen: " + propname)
            //log("no")
            //var heightMap = this.getHeightMap(ent.dimension, ent.location);
            //log("height: " + heightMap);
            let minRange = 64;
            let maxRange = 150;
            //TODO post release: refine these:
            //x make sure it doesnt drift into a different region
            //check against other player positions
            //apply these changes to cave gen too
            let posRegionCenter = {
                x: posRegion.x * this.waystoneRegionSize + this.waystoneRegionSize / 2,
                y: 0,
                z: posRegion.z * this.waystoneRegionSize + this.waystoneRegionSize / 2
            };
            //64 block buffer between max and region border, so biome waystones shouldnt be any closer than 128 of eachother, ever
            let maxDistFromCenter = 196;
            let tryPos;
            for(let i = 0; i < this.configTimesPerIterationToFindGoodXZPosSurface; i++){
                tryPos = {
                    x: player.location.x + Math.random() * maxRange - Math.random() * maxRange,
                    y: player.location.y,
                    z: player.location.z + Math.random() * maxRange - Math.random() * maxRange
                };
                if (CoroUtil.distXZToCoord(player.location, tryPos) < minRange || CoroUtil.distXZToCoord(posRegionCenter, tryPos) > maxDistFromCenter || !CoroUtil.entityIsInLoadedChunk(dim, tryPos)) {
                    tryPos = undefined;
                } else {
                    break;
                }
            //TODO: TEMP
            //let maxRange = 15;
            /* tryPos = { 
                    x: player.location.x + (Math.random() * maxRange) - (Math.random() * maxRange), 
                    y: player.location.y, 
                    z: player.location.z + (Math.random() * maxRange) - (Math.random() * maxRange) };

                if (
                    !CoroUtil.entityIsInLoadedChunk(dim, tryPos)) {
                    tryPos = undefined;
                } else {
                    break;
                } */ }
            if (tryPos !== undefined) {
                var ent = dim.spawnEntity(MODID + ":biome_getter", {
                    x: tryPos.x,
                    y: tryPos.y,
                    z: tryPos.z
                });
                //need to get biome here first so we alter checks for ocean biome
                let waystone = this.getWaystoneNameFromBiomeGetter(ent);
                CoroUtil.despawnEntity(ent);
                if (waystone !== undefined) {
                    //let forOcean: boolean = waystone == "ocean";
                    var height = this.getSafeSpotToSpawnWaystoneOnSurfaceHeightmapHere(dim, tryPos);
                    if (height >= CoroUtil.getMinWorldHeight(dim)) {
                        if (CoroUtil.outputDebug) player.sendMessage("genned " + waystone + " waystone for surface at " + Math.floor(tryPos.x) + ", " + Math.floor(tryPos.y) + ", " + Math.floor(tryPos.z));
                        log("genned " + waystone + " waystone for surface at " + Math.floor(tryPos.x) + ", " + Math.floor(tryPos.y) + ", " + Math.floor(tryPos.z));
                        CoroUtil.setBlockAt(player.dimension, {
                            x: tryPos.x,
                            y: height,
                            z: tryPos.z
                        }, MODID + ":waystone_biome_" + waystone + "_base");
                        CoroUtil.setBlockAt(player.dimension, {
                            x: tryPos.x,
                            y: height + 1,
                            z: tryPos.z
                        }, MODID + ":waystone_biome_" + waystone + "_pillar");
                        this.addWaystoneLocation(dim, {
                            x: tryPos.x,
                            y: height + 1,
                            z: tryPos.z
                        });
                        //let bytes = world.getDynamicPropertyTotalByteCount();
                        //world.setDynamicProperty(propname, true);
                        CoroUtil.setPropSafe(world, propname, true);
                    }
                } else {
                    //log("failed to find a waystone to use at " + tryPos.x + ", " + tryPos.z);
                    log("failed to find a good spot for surface waystone");
                }
            } else {
                log("failed to find a good spot for surface waystone within good distances");
            }
        }
    }
    tickPlayerWaystoneGenCave(player) {
        if (player.location.y > 55 && player.dimension.id == "minecraft:overworld") return;
        let dim = player.dimension;
        let posRegion = this.getRegionFromPos(player.location);
        let propname = this.propGennedWaystoneCavePrefix + dim.id + "_" + posRegion.x + "_" + posRegion.z;
        let region_genned = world.getDynamicProperty(propname);
        if (region_genned) {
        //log("marked as genned")
        } else {
            //log("try gen: " + propname)
            let minRange = 64;
            let maxRange = 150;
            let rangeY = 4;
            /* let range = 20;
                */ /* let tryPos: Vector3 = CoroUtil.floor({ 
                x: player.location.x + (Math.random() * range) - (Math.random() * range), 
                y: player.location.y + (Math.random() * rangeY), 
                z: player.location.z + (Math.random() * range) - (Math.random() * range) }); */ //TODO: TEMP
            /* tryPos = CoroUtil.floor({ 
                x: player.location.x, 
                y: player.location.y, 
                z: player.location.z }); */ let posRegionCenter = {
                x: posRegion.x * this.waystoneRegionSize + this.waystoneRegionSize / 2,
                y: 0,
                z: posRegion.z * this.waystoneRegionSize + this.waystoneRegionSize / 2
            };
            //64 block buffer between max and region border, so biome waystones shouldnt be any closer than 128 of eachother, ever
            let maxDistFromCenter = 196;
            let tryPos;
            for(let i = 0; i < this.configTimesPerIterationToFindGoodXZPosCave; i++){
                tryPos = {
                    x: player.location.x + Math.random() * maxRange - Math.random() * maxRange,
                    y: player.location.y + Math.random() * rangeY,
                    z: player.location.z + Math.random() * maxRange - Math.random() * maxRange
                };
                if (CoroUtil.distXZToCoord(player.location, tryPos) < minRange || CoroUtil.distXZToCoord(posRegionCenter, tryPos) > maxDistFromCenter || !CoroUtil.entityIsInLoadedChunk(dim, tryPos)) {
                    tryPos = undefined;
                } else {
                    break;
                }
            }
            if (tryPos !== undefined) {
                var ent = dim.spawnEntity(MODID + ":biome_getter", {
                    x: tryPos.x,
                    y: tryPos.y,
                    z: tryPos.z
                });
                //need to get biome here first so we alter checks for ocean biome
                let waystone = this.getWaystoneNameFromBiomeGetter(ent);
                CoroUtil.despawnEntity(ent);
                //log("w " + waystone)
                if (waystone !== undefined && (waystone == "lush_caves" || waystone == "basalt_deltas")) {
                    //let forOcean: boolean = waystone == "ocean";
                    var height = this.getSafeSpotToSpawnWaystoneSolidGrowndBelowHere(dim, tryPos);
                    //log("h " + height)
                    if (height >= CoroUtil.getMinWorldHeight(dim)) {
                        if (CoroUtil.outputDebug) player.sendMessage("genned " + waystone + " waystone for caves at " + Math.floor(tryPos.x) + ", " + Math.floor(tryPos.y) + ", " + Math.floor(tryPos.z));
                        debug("genned " + waystone + " waystone for caves at " + Math.floor(tryPos.x) + ", " + Math.floor(tryPos.y) + ", " + Math.floor(tryPos.z));
                        CoroUtil.setBlockAt(player.dimension, {
                            x: tryPos.x,
                            y: height,
                            z: tryPos.z
                        }, MODID + ":waystone_biome_" + waystone + "_base");
                        CoroUtil.setBlockAt(player.dimension, {
                            x: tryPos.x,
                            y: height + 1,
                            z: tryPos.z
                        }, MODID + ":waystone_biome_" + waystone + "_pillar");
                        this.addWaystoneLocation(dim, {
                            x: tryPos.x,
                            y: height + 1,
                            z: tryPos.z
                        });
                        //world.setDynamicProperty(propname, true);
                        CoroUtil.setPropSafe(world, propname, true);
                    }
                } else {
                //log("failed to find spot for cave waystone to gen at " + tryPos.x + ", " + tryPos.z);
                }
            } else {
            //log("failed to find spot for cave waystone to gen");
            }
        }
    }
    getBiomeFromBiomeGetter(ent) {
        let biome_id = "none";
        for(let i = 0; i < 6; i++){
            biome_id = ent.getProperty(MODID + ":biome_id_" + i);
            //log(MODID + ":biome_id_" + i + ": " + biome_id)
            if (biome_id !== "none" && biome_id !== undefined) {
                break;
            }
        }
        return biome_id;
    }
    getWaystoneNameFromBiomeGetter(ent) {
        let biome_id = "none";
        for(let i = 0; i < 6; i++){
            biome_id = ent.getProperty(MODID + ":biome_id_" + i);
            //log(MODID + ":biome_id_" + i + ": " + biome_id)
            if (biome_id !== "none" && biome_id !== undefined) {
                break;
            }
        }
        let theme_name = this.getBiomeThemeFromBiome(biome_id);
        if (theme_name !== undefined) {
            if (theme_name in biome_themes_to_waystone) {
                //log("theme_name: " + theme_name)
                let waystone = biome_themes_to_waystone[theme_name];
                //log("waystone: " + waystone)
                return waystone;
            } else {
                err("failed to get waystone from biome theme: " + theme_name);
            }
        } else {
            err("failed to get a biome theme from biome id: " + biome_id);
        }
        return undefined;
    }
    getBiomeThemeFromBiome(biome_id_to_find) {
        for(const theme_name in biome_themes_from_biome){
            let biome_names = biome_themes_from_biome[theme_name];
            for (const biome_id of biome_names){
                //log(biome_id_to_find + " vs " + biome_id)
                if (biome_id_to_find == biome_id) {
                    return theme_name;
                }
            }
        }
        return undefined;
    }
    /**
	 * Also its causing tick spikes, resorting to returning +x
	 * This actually doesnt work most of the time, because the blocks arent loaded
	 * 
	 * @param dim 
	 * @param posWaystone 
	 * @returns 
	 */ getTeleportSpotNextToWaystone(dim, posWaystone) {
        /* if (CoroUtil.getBlock(dim, CoroUtil.add(posWaystone, 1, 0, 0))?.isAir) return CoroUtil.add(CoroUtil.centerXZ(posWaystone), 1, 0, 0);
		if (CoroUtil.getBlock(dim, CoroUtil.add(posWaystone, -1, 0, 0))?.isAir) return CoroUtil.add(CoroUtil.centerXZ(posWaystone), -1, 0, 0);
		if (CoroUtil.getBlock(dim, CoroUtil.add(posWaystone, 0, 0, 1))?.isAir) return CoroUtil.add(CoroUtil.centerXZ(posWaystone), 0, 0, 1);
		if (CoroUtil.getBlock(dim, CoroUtil.add(posWaystone, 0, 0, -1))?.isAir) return CoroUtil.add(CoroUtil.centerXZ(posWaystone), 0, 0, -1); */ //log("found no good")
        //return posWaystone;
        let posFinal = CoroUtil.add(CoroUtil.centerXZ(posWaystone), 1, 0, 0);
        //log("centered: " + CoroUtil.vector3ToString(posFinal))
        return posFinal;
    }
    tryTeleportWithCost(player, dim, pos, notToWaystone) {
        let creative = CoroUtil.getGameMode(player) == GameMode.creative;
        let xpCost = this.getTeleportCost(player.dimension, player.location, dim, pos);
        //if (player.level >= xpCost || creative) {
        //if (!creative) player.addLevels(-xpCost);
        //workaround for stop use not triggering when master waystone destination interacted with
        player.setDynamicProperty(this.propPlayerStartedWarpCount, false);
        //player.setDynamicProperty(this.propPlayerIsUsingWayfinder, false);
        if (notToWaystone) {
            this.teleportWithDelayedSound(player, dim, pos);
        } else {
            this.teleportWithDelayedSound(player, dim, this.getTeleportSpotNextToWaystone(dim, pos));
            player.setDynamicProperty(this.propPlayerLastWaystoneTPPos, pos);
            //log("set pos " + CoroUtil.vector3ToString(pos));
            player.setDynamicProperty(this.propPlayerLastWaystoneTPDim, dim.id);
        }
        return true;
    /* } else {
            player.setDynamicProperty(this.propPlayerTitleTimes, this.titleTimesMax)
            this.actionBarRaw(player, '{"rawtext":[{"translate":"msg.wayfinder_experience"}, {"text": " ' + xpCost + '"}]}');
            return false;
        } */ }
    teleportWithDelayedSound(player, dim, pos) {
        //log("teleport to " + CoroUtil.vector3ToString(pos) + ", " + dim.id);
        player.teleport(pos, {
            dimension: dim
        });
        system.runTimeout(()=>{
            player.playSound("mob.endermen.portal", {
                location: pos
            });
            if (CoroUtil.isLoaded(dim, pos)) {
                dim.spawnParticle("razz_ew:wayfinder_charge_explode_small", pos);
            } else {
                debug("tp destination not loaded for particle: " + CoroUtil.vector3ToString(pos));
            }
        }, 3);
    }
    addWaystoneLocation(dim, pos) {
        pos = CoroUtil.floor(pos);
        let jsonObj = this.getWaystoneLocationData();
        let waystones = jsonObj.overworld;
        if (dim.id == "minecraft:nether") {
            waystones = jsonObj.nether;
        } else if (dim.id == "minecraft:the_end") {
            waystones = jsonObj.the_end;
        }
        let posString = CoroUtil.vector3ToString(pos);
        if (waystones[posString] == undefined) {
            //waystones.push(posString);
            waystones[posString] = true;
            debug("added new waystone entry: " + posString + ", entry count for this dim: " + Object.keys(waystones).length);
        } else {
            debug("already exists: " + posString);
        }
        let jsonString = JSON.stringify(jsonObj);
        //log(jsonString);
        log("new json char length: " + jsonString.length + " - " + jsonString.length / CoroUtil.propStringLengthLimit * 100 + "%");
        CoroUtil.setPropSafe(world, this.propWaystoneLocations, jsonString);
    }
    deleteFromObject(keyPart, obj) {
        for(var k in obj){
            if (~k.indexOf(keyPart)) {
                delete obj[k]; // Delete obj[key];
            }
        }
    }
    removeWaystoneLocation(dim, pos) {
        pos = CoroUtil.floor(pos);
        let jsonObj = this.getWaystoneLocationData();
        let waystones = jsonObj.overworld;
        if (dim.id == "minecraft:nether") {
            waystones = jsonObj.nether;
        } else if (dim.id == "minecraft:the_end") {
            waystones = jsonObj.the_end;
        }
        let posString = CoroUtil.vector3ToString(pos);
        if (waystones[posString] !== undefined) {
            this.deleteFromObject(posString, waystones);
            debug("removed waystone entry: " + posString + ", entry count: " + Object.keys(waystones).length);
        } else {
            debug("doesnt exist in array: " + posString);
        }
        CoroUtil.setPropSafe(world, this.propWaystoneLocations, JSON.stringify(jsonObj));
    }
    getWaystoneLocationData() {
        let jsonObj = {
            overworld: {},
            nether: {},
            the_end: {}
        };
        let jsonStr = CoroUtil.getPropString(world, this.propWaystoneLocations);
        if (jsonStr !== "") {
            jsonObj = JSON.parse(jsonStr);
        }
        //log("data: " + jsonStr);
        return jsonObj;
    }
    isWaystone(block) {
        if (block?.permutation.matches(this.blockNameWaystoneMasterBase) || block?.permutation.matches(this.blockNameWaystoneMasterTop)) {
            return true;
        }
        for(const waystone_name in waystones.naturally_generated){
            let waystone_data = waystones.naturally_generated[waystone_name];
            //log(waystone_name)
            if (block?.permutation.matches(MODID + ":waystone_biome_" + waystone_name + "_pillar") || block?.permutation.matches(MODID + ":waystone_biome_" + waystone_name + "_pillar_activated")) {
                return true;
            }
        }
        for(const waystone_name in waystones.player){
            let waystone_data = waystones.player[waystone_name];
            //log(waystone_name)
            if (block?.permutation.matches(MODID + ":waystone_player_" + waystone_name) || block?.permutation.matches(MODID + ":waystone_player_" + waystone_name + "_activated")) {
                return true;
            }
        }
        return false;
    }
    isWaystoneActivated(block) {
        //nah, im not iterating each state to check, dont need to, we know its activated if data ent exists
        /* if (block !== undefined) {
            if (block.permutation.matches(this.blockNameWaystoneMasterBase, {"razz_ew:activated": true}) || block.permutation.matches(this.blockNameWaystoneMasterTop, {"razz_ew:activated": true})) {
                return true;
            }
        } */ for(const waystone_name in waystones.naturally_generated){
            let waystone_data = waystones.naturally_generated[waystone_name];
            //log(waystone_name)
            if (block?.permutation.matches(MODID + ":waystone_biome_" + waystone_name + "_pillar_activated")) {
                return true;
            }
        }
        for(const waystone_name in waystones.player){
            let waystone_data = waystones.player[waystone_name];
            //log(waystone_name)
            if (block?.permutation.matches(MODID + ":waystone_player_" + waystone_name + "_activated")) {
                return true;
            }
        }
        return false;
    }
    turnWayfinderEntityIntoItem(entity) {
        //log("?? " + typeof entity);
        //just bedrock things
        if (entity === undefined) return;
        try {
            let jsonString = CoroUtil.getPropString(entity, this.propWayfinderEntityData);
            log("data: " + jsonString);
            if (jsonString !== "") {
                let wayfinderData = JSON.parse(jsonString);
                let itemStack = new ItemStack(wayfinderData.wayfinder_id);
                let posRecall = undefined;
                let dimRecall = undefined;
                if (wayfinderData.y_recall !== -999) {
                    posRecall = {
                        x: wayfinderData.x_recall,
                        y: wayfinderData.y_recall,
                        z: wayfinderData.z_recall
                    };
                    dimRecall = world.getDimension(CoroUtil.dimNumberIDToStringID(wayfinderData.dim_recall));
                }
                this.setWayfinderPosToLore(itemStack, world.getDimension(CoroUtil.dimNumberIDToStringID(wayfinderData.dim)), {
                    x: wayfinderData.x,
                    y: wayfinderData.y,
                    z: wayfinderData.z
                }, dimRecall, posRecall);
                entity.dimension.spawnItem(itemStack, entity.location);
            }
            log("despawn: " + entity.id);
            CoroUtil.despawnEntity(entity);
            //bedrock fails to despawn entity sometimes
            const run = system.runTimeout(()=>{
                if (entity !== undefined && entity.isValid()) {
                    entity.kill();
                }
            }, 5);
        } catch (e) {
            err("bedrock being bedrock: " + e.message);
        }
    }
    getTeleportCost(dimFrom, posFrom, dimTo, posTo) {
        if (dimFrom !== dimTo) {
            return 3;
        }
        let dist = CoroUtil.distToCoord(posFrom, posTo);
        if (dist < 250) return 1;
        if (dist < 500) return 2;
        return 3;
    }
    getClosestLecternWayfinderToPlayer(player) {
        let entities = player.dimension.getEntities({
            type: this.entityNameWayfinder,
            closest: 1,
            location: player.location,
            maxDistance: 3
        });
        if (entities.length > 0) {
            let ent = entities[0];
            return ent;
        }
        return undefined;
    }
    getClosestLecternWayfinderDataToPlayer(player) {
        let ent = this.getClosestLecternWayfinderToPlayer(player);
        if (ent !== undefined) {
            return CoroUtil.getPropString(ent, this.propWayfinderEntityData);
        }
        return undefined;
    }
    setPlayerUsedWayfinderOnBlock(player) {
        log("setPlayerUsedWayfinderOnBlock");
        player.setDynamicProperty(this.propPlayerItemWasUsedOnBlock, true);
    }
    getPlayerUsedWayfinderOnBlock(player) {
        return CoroUtil.getPropBool(player, this.propPlayerItemWasUsedOnBlock);
    }
    /* setPlayerUsedMasterWayfinderOnBlock(player: Player) {
        player.setDynamicProperty(this.propPlayerMasterWayfinderWasUsedOnBlock, true);
    }

    getPlayerUsedMasterWayfinderOnBlock(player: Player) {
        return CoroUtil.getPropBool(player, this.propPlayerMasterWayfinderWasUsedOnBlock);
    } */ isPosReplaceableBlockOrAir(dim, pos) {
        let block = CoroUtil.getBlock(dim, pos);
        if (block?.isAir) return true;
        for (var entry of blocks_allowed_to_gen_over){
            if (block?.permutation.matches("minecraft:" + entry)) {
                //log("found a replacable: " + entry)
                return true;
            }
        }
        return false;
    }
    testGui(player) {
        const formData = new ActionFormData();
        formData.title("title");
        formData.body("body");
        formData.button("text", "icon path");
        formData.button("text2");
        formData.show(player).then((response)=>{
            log("sel " + response.selection);
        });
    }
    replaceOldWayfinders(player) {
        let wayfinderClairvoyant = new ItemStack(this.itemNameWayfinderClairvoyant);
        let inventory = player.getComponent(EntityInventoryComponent.componentId);
        if (inventory.container !== undefined) {
            let foundCount = 0;
            for(let i = 0; i < inventory.container.size; i++){
                const item = inventory.container.getItem(i);
                if (item !== undefined && item.typeId == wayfinderClairvoyant.typeId) {
                    let lore = item.getLore();
                    let newItemStack = new ItemStack(this.itemNameWayfinderSimple);
                    newItemStack.setLore(item.getLore());
                    inventory.container.setItem(i, newItemStack);
                }
            }
        }
    }
    addHelpfulLore(player) {
        let inventory = player.getComponent(EntityInventoryComponent.componentId);
        if (inventory.container !== undefined) {
            let foundCount = 0;
            for(let i = 0; i < inventory.container.size; i++){
                const item = inventory.container.getItem(i);
                if (item !== undefined) {
                    let lore = item.getLore();
                    //NOTE: DONT USE MORE THAN 2 LINES, or checks elsewhere will malfunction
                    //at least until we replace lore use with dynamic properties fully
                    if (lore.length == 0) {
                        let updated = false;
                        if (item.typeId == this.itemNameWarperPlayer) {
                            item.setLore([
                                "Warps to a player you bound it to",
                                "Use on player to bind to them"
                            ]);
                            updated = true;
                        } else if (item.typeId == this.itemNameWarperPlayerMaster) {
                            item.setLore([
                                "Lets you choose any player to warp to in the world"
                            ]);
                            updated = true;
                        } else if (item.typeId == this.itemNameWayfinderMaster) {
                            item.setLore([
                                "Warps to many bound waystones, up to 6",
                                "Use on a Waystone to add to it"
                            ]);
                            updated = true;
                        } else if (item.typeId == this.itemNameWayfinderEcho) {
                            item.setLore([
                                "Warps between your position and a bound waystone",
                                "Use on a waystone to bind it"
                            ]);
                            updated = true;
                        } else if (item.typeId == this.itemNameWayfinderSimple) {
                            item.setLore([
                                "Warps to a bound waystone",
                                "Use on a waystone to bind it"
                            ]);
                            updated = true;
                        } else if (item.typeId == this.itemNameWayfinderWildcard) {
                            item.setLore([
                                "Warps to a random waystone, one time use"
                            ]);
                            updated = true;
                        }
                        if (updated) {
                            inventory.container.setItem(i, item);
                        }
                    }
                }
            }
        }
    }
    inventoryHasItemAndRemove(player, questItemStack, removeIfHasAll) {
        let conditionsMet = false;
        let inventory = player.getComponent(EntityInventoryComponent.componentId);
        if (inventory.container !== undefined) {
            let foundCount = 0;
            for(let i = 0; i < inventory.container.size; i++){
                const item = inventory.container.getItem(i);
                if (item !== undefined && item.typeId == questItemStack.typeId) {
                    foundCount += item.amount;
                    if (foundCount >= questItemStack.amount) {
                        conditionsMet = true;
                        break;
                    }
                }
            }
            //log("questItemStack.amount " + questItemStack.amount)
            let removedCount = 0;
            if (conditionsMet && removeIfHasAll) {
                for(let i = 0; i < inventory.container.size; i++){
                    const item = inventory.container.getItem(i);
                    if (item !== undefined && item.typeId == questItemStack.typeId) {
                        if (item.amount >= questItemStack.amount) {
                            //log("item.amount " + item.amount)
                            //bedrock hates item amounts being 0, throws error, we have to plan for it and just remove the item right away instead
                            if (item.amount - questItemStack.amount == 0) {
                                inventory.container.setItem(i, undefined);
                            } else {
                                item.amount -= questItemStack.amount;
                                inventory.container.setItem(i, item);
                            }
                            removedCount += questItemStack.amount;
                        } else {
                            removedCount += item.amount;
                            inventory.container.setItem(i, undefined);
                        }
                        if (removedCount >= questItemStack.amount) {
                            if (removedCount > questItemStack.amount) {
                                log("uhhhh took too many: " + removedCount);
                            }
                            break;
                        }
                    }
                }
            }
        }
        return conditionsMet;
    }
    constructor(dbName){
        super(dbName);
        this.playerToData = new Map();
        this.blockNameElevatorBase = MODID + ":elevator_";
        this.blockNameWaystone = MODID + ":waystone";
        this.blockNameWaystoneMasterFull = MODID + ":waystone_master_full";
        this.blockNameWaystoneMasterBase = MODID + ":waystone_master_base";
        this.blockNameWaystoneMasterTop = MODID + ":waystone_master_top";
        this.itemNameWayfinderSimple = MODID + ":wayfinder_simple";
        this.itemNameWayfinderMaster = MODID + ":wayfinder_master";
        this.itemNameWayfinderClairvoyant = MODID + ":wayfinder_clairvoyant";
        this.itemNameWayfinderWildcard = MODID + ":wayfinder_wildcard";
        this.itemNameWayfinderEcho = MODID + ":wayfinder_echo";
        this.itemNameWarperPlayer = MODID + ":wayfinder_player";
        this.itemNameWarperPlayerMaster = MODID + ":wayfinder_player_master";
        this.itemNameDevTool = MODID + ":dev_tool";
        this.itemNameGuide = MODID + ":guide";
        this.entityNameWaystoneMasterData = MODID + ":master_waystone_data";
        this.entityNameWaystoneMasterMenu = MODID + ":master_waystone_menu";
        this.entityNameWaystoneMasterMenuDestination = MODID + ":master_waystone_menu_destination";
        this.entityNameWayfinder = MODID + ":wayfinder";
        this.blockPermutationMasterWaystoneActivated = MODID + ":activated";
        this.blockPermutationMasterWaystoneWayfinderCount = MODID + ":wayfinder_count";
        //entity dynamic properties
        this.propMasterWaystoneDataMenuEntID = "menu_ent_id";
        this.propMasterWaystoneDataMenuDestinationEntIDs = "menu_dest_ent_ids";
        this.propMasterWaystoneDataMenuVersion = "menu_version";
        this.propMasterWaystoneDataMenuVersionDefinition_v1_1 = "v1_1";
        this.propDataEntID = "data_ent_id";
        this.propMenuDataDestinationIndex = "dest_index";
        //propWayfinderDestination: string = "dest";
        this.propWayfinderEntityData = "wayfinder_data";
        this.propPlayerLowSpeedTicks = MODID + ":low_speed_ticks";
        //for any warping tool now
        this.propPlayerUsingWayfinderTicks = MODID + ":using_wayfinder_ticks";
        //propPlayerPlayerWayfinderCountdownToWarp: string = MODID + ":player_wayfinder_countdown_to_warp";
        this.propPlayerStartedWarpCount = MODID + ":player_started_warp_count";
        this.propPlayerPlayerWayfinderUsername = MODID + ":player_wayfinder_username";
        /* propPlayerPlayerWayfinderLastDim: string = MODID + ":player_wayfinder_last_dim";
	propPlayerPlayerWayfinderLastX: string = MODID + ":player_wayfinder_last_X";
	propPlayerPlayerWayfinderLastY: string = MODID + ":player_wayfinder_last_Y";
	propPlayerPlayerWayfinderLastZ: string = MODID + ":player_wayfinder_last_Z"; */ //propPlayerIsUsingWayfinder: string = MODID + ":using_wayfinder";
        //propPlayerIsUsingPlayerWayfinder: string = MODID + ":using_player_wayfinder";
        this.propPlayerIsUsingLecternWayfinder = MODID + ":using_lectern_wayfinder";
        this.propPlayerLastItemInHandID = MODID + ":last_item_in_hand_id";
        this.propPlayerLastItemInHandLoreFirstLine = MODID + ":last_item_in_hand_lore";
        this.propPlayerTitleTimes = MODID + ":title_times";
        this.propPlayerWayfinderUseCooldown = MODID + ":wayfinder_use_cooldown";
        this.propPlayerLastWaystoneTPPos = MODID + ":wayfinder_last_tp_pos";
        this.propPlayerLastWaystoneTPDim = MODID + ":wayfinder_last_tp_dim";
        this.propPlayerItemWasUsedOnBlock = MODID + ":item_was_used_on_block";
        this.propPlayerMasterWayfinderWasUsedOnBlock = MODID + ":master_wayfinder_was_used_on_block";
        this.propPlayerMasterWaystoneUpdateNotifyv1_1DontShowAgain = MODID + ":master_waystone_v1_1_changes_gui_dont_show_again";
        this.warpDelay = 30;
        //item dynamic properties
        //propItemBoundPlayerUUID: string = MODID + ":bound_player_uuid";
        this.propItemBoundPlayerUsername = MODID + ":bound_player_username";
        //world dynamic properties, must be namespaced
        this.propGennedWaystonePrefix = MODID + ":genned_waystone_";
        this.propGennedWaystoneCavePrefix = MODID + ":genned_waystone_cave_";
        this.propWaystoneDimPosPrefix = MODID + ":dimpos_waystone_";
        this.propWaystoneLocations = MODID + ":waystone_locations";
        this.propWaystoneMasterNewChangeNotifyCount = MODID + ":waystone_master_new_change_notify_count";
        this.propAllPlayerLocationData = MODID + ":all_player_location_data";
        this.propConfigLoggingDebug = MODID + ":config_logging_debug";
        this.propConfigLoggingLog = MODID + ":config_logging_log";
        this.propConfigLoggingError = MODID + ":config_logging_error";
        this.propConfigElevatorSearchDist = MODID + ":config_elevator_search_dist";
        this.propConfigTimesPerIterationToFindGoodXZPosSurface = MODID + ":config_surface_gen_tries";
        this.propConfigTimesPerIterationToFindGoodXZPosCave = MODID + ":config_cave_gen_tries";
        this.propConfigMaxScanDownDistanceFromPosCave = MODID + ":config_cave_gen_scan_dist_y";
        this.propConfigMasterWaystonePistonNoise = MODID + ":config_master_waystone_piston_noise";
        this.propConfigMasterWaystoneGUIv1_1 = MODID + ":config_master_waystone_GUI_v1_1";
        this.propConfigGenWaystones = MODID + ":config_gen_waystones";
        this.propConfigEnableMasterPlayerWayfinder = MODID + ":config_enable_master_player_wayfinder";
        this.waystoneIcons = [
            "Arid Biome",
            "Cave Biome",
            "End Biome",
            "Flatlands Biome",
            "Highlands Biome",
            "Nether Biome",
            "Ocean Biome",
            "Wetlands Biome",
            "Woodlands Biome",
            "Abandoned Village",
            "Ancient City",
            "Base",
            "Bastion Remnant",
            "Desert Pyramid",
            "End City",
            "Fortress",
            "Igloo",
            "Jungle Pyramid",
            "Mine",
            "Nether Fortress",
            "Nether Fossil",
            "Ocean Monument",
            "Ocean Ruin",
            "Pillager Outpost",
            "Portal",
            "Ruined Portal",
            "Shipwreck",
            "Swamp Hut",
            "Trail Chamber",
            "Trail Ruin",
            "Treasure",
            "Village",
            "Woodland Mansion",
            "Circle",
            "Creeper",
            "Cross",
            "Dot",
            "Fish",
            "Horse Shoe",
            "Scythe",
            "Skull",
            "Square",
            "Star",
            "Sword",
            "Triangle"
        ];
        this.basePathMasterWaystoneTextures = "textures/razz/ew/gui/destinations/waystone/";
        this.waystoneIconTextures = [
            "ew_icon_biome_arid",
            "ew_icon_biome_cave",
            "ew_icon_biome_end",
            "ew_icon_biome_flatlands",
            "ew_icon_biome_highlands",
            "ew_icon_biome_nether",
            "ew_icon_biome_ocean",
            "ew_icon_biome_wetlands",
            "ew_icon_biome_woodlands",
            "ew_icon_abandoned_village",
            "ew_icon_ancient_city",
            "ew_icon_base",
            "ew_icon_bastion_remnant",
            "ew_icon_desert_pyramid",
            "ew_icon_end_city",
            "ew_icon_fortress",
            "ew_icon_igloo",
            "ew_icon_jungle_pyramid",
            "ew_icon_mine",
            "ew_icon_nether_fortress",
            "ew_icon_nether_fossil",
            "ew_icon_ocean_monument",
            "ew_icon_ocean_ruin",
            "ew_icon_pillager_outpost",
            "ew_icon_portal",
            "ew_icon_ruined_portal",
            "ew_icon_shipwreck",
            "ew_icon_swamp_hut",
            "ew_icon_trail_chamber",
            "ew_icon_trail_ruin",
            "ew_icon_treasure",
            "ew_icon_village",
            "ew_icon_woodland_mansion",
            "ew_icon_misc_circle",
            "ew_icon_misc_creeper",
            "ew_icon_misc_cross",
            "ew_icon_misc_dot",
            "ew_icon_misc_fish",
            "ew_icon_misc_horse_shoe",
            "ew_icon_misc_scythe",
            "ew_icon_misc_skull",
            "ew_icon_misc_square",
            "ew_icon_misc_star",
            "ew_icon_misc_sword",
            "ew_icon_misc_triangle"
        ];
        this.masterPlayerWayfinderIcons = [
            "Player Icon 1",
            "Player Icon 2",
            "Player Icon 3",
            "Player Icon 4",
            "Player Icon 5",
            "Player Icon 6",
            "Player Icon 7",
            "Player Icon 8",
            "Player Icon 9",
            "Player Icon 10"
        ];
        this.basePathMasterPlayerWayfinderTextures = "textures/razz/ew/gui/destinations/player/";
        //basePathPlayer: string = this.basePath;
        //masterPlayerWayfinderIconTextures: string[] = this.waystoneIconTextures;
        this.masterPlayerWayfinderIconTextures = [
            "ew_icon_player1",
            "ew_icon_player2",
            "ew_icon_player3",
            "ew_icon_player4",
            "ew_icon_player5",
            "ew_icon_player6",
            "ew_icon_player7",
            "ew_icon_player8",
            "ew_icon_player9",
            "ew_icon_player10"
        ];
        //playerWayfinderIcons: string[] = 
        //dataNameWaystoneDestinationX: string = "dest_"
        this.rotTest = 0;
        //itemWasUsedOnBlock: boolean = false;
        this.waystoneRegionSize = 512;
        //there is a bug, with specifically echo wayfinder, where using it desyncs the itemslot, preventing moving it until you try to drop the item, or reload, or tp across dimension
        //delaying the setting of the item into the slot by 1 tick fixes it, this is the implementation of that
        //this prevents others using echo wayfinder for that 1 tick while it does its thing, should be safe enough
        //if somehow someone uses it and disconnects, itll still let someone else use it, and orig player wont get their updated itemstack
        this.echoWayfinderFixTempPlayerID = "";
        this.echoWayfinderFixSetLoreDelay = 0;
        this.echoWayfinderFixTempItemStack = undefined;
        this.titleTimesMax = 60;
        this.playerWayfinderUseCooldownMax = 10;
        this.tickGenAlternator = false;
        this.modActive = false;
    }
}
