public enum Metal {
    IRON(true),
    STEEL,
    TIN,
    PEWTER,
    ZINC,
    BRASS,
    COPPER(true),
    BRONZE,
    ALUMINUM,
    DURALUMIN,
    CHROMIUM,
    NICROSIL,
    GOLD(true),
    ELECTRUM,
    CADMIUM,
    BENDALLOY;


    private final boolean vanilla;

    Metal() {
        this(false);
    }

    Metal(boolean isVanilla) {
        this.vanilla = isVanilla;
    }

    public static Metal getMetal(int index) {
        for (Metal metal : values()) {
            if (metal.getIndex() == index) {
                return metal;
            }
        }
        throw new IllegalArgumentException("Allomancy: Bad Metal Index");
    }

    public boolean isVanilla() {
        return this.vanilla;
    }

    public String getName() {
        return name().toLowerCase(Locale.ROOT);
    }

    public int getIndex() {
        return ordinal();
    }


} public class CombatSetup {
    public static final DeferredRegister<EntityType<?>> ENTITIES = DeferredRegister.create(ForgeRegistries.ENTITIES, Allomancy.MODID);
    public static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, Allomancy.MODID);
    public static final RegistryObject<CoinBagItem> COIN_BAG = ITEMS.register("coin_bag", CoinBagItem::new);
    public static final RegistryObject<ObsidianDaggerItem> OBSIDIAN_DAGGER = ITEMS.register("obsidian_dagger", ObsidianDaggerItem::new);
    public static final RegistryObject<KolossBladeItem> KOLOSS_BLADE = ITEMS.register("koloss_blade", KolossBladeItem::new);
    public static final ArmorMaterial WoolArmor = new ArmorMaterial() {
        @Override
        public int getDurabilityForSlot(EquipmentSlot slotIn) {
            return 50;
        }

        @Override
        public int getDefenseForSlot(EquipmentSlot slotIn) {
            return slotIn == EquipmentSlot.CHEST ? 4 : 0;
        }

        @Override
        public int getEnchantmentValue() {
            return 15;
        }

        @Override
        public SoundEvent getEquipSound() {
            return SoundEvents.ARMOR_EQUIP_LEATHER;
        }

        @Override
        public Ingredient getRepairIngredient() {
            return Ingredient.of(Items.GRAY_WOOL);
        }

        @Override
        public String getName() {
            return "allomancy:wool";
        }

        @Override
        public float getToughness() {
            return 0;
        }

        @Override
        public float getKnockbackResistance() {
            return 0;
        }

    };
    public static final RegistryObject<MistcloakItem> MISTCLOAK = ITEMS.register("mistcloak", MistcloakItem::new);
    public static final RegistryObject<EntityType<ProjectileNuggetEntity>> NUGGET_PROJECTILE = ENTITIES.register("nugget_projectile", () -> EntityType.Builder
            .<ProjectileNuggetEntity>of(ProjectileNuggetEntity::new, MobCategory.MISC)
            .setShouldReceiveVelocityUpdates(true)
            .setUpdateInterval(20)
            .setCustomClientFactory((spawnEntity, world) -> new ProjectileNuggetEntity(world, spawnEntity.getEntity()))
            .sized(0.25F, 0.25F)
            .build("nugget_projectile"));

    public static void register() {
        ENTITIES.register(FMLJavaModLoadingContext.get().getModEventBus());
        ITEMS.register(FMLJavaModLoadingContext.get().getModEventBus());
    }


} public class PowerUtils {

    public static final byte PUSH = 1;
    public static final byte PULL = -1;
    private static final Predicate<Goal> isAggroGoal = (goal) -> goal instanceof SwellGoal || goal instanceof AIAttackOnCollideExtended || goal instanceof MeleeAttackGoal ||
                                                                 goal instanceof TargetGoal || goal instanceof PanicGoal || goal.getClass().getName().contains("Fireball") ||
                                                                 goal.getClass().getName().contains("Attack") || goal.getClass().getName().contains("Anger");

    private static final Pattern ACTIVE_METAL_REGEX = Pattern.compile(
            ".*(iron|steel|tin_|pewter|zinc|brass|copper|bronze|duralumin|chromium|nicrosil|gold|electrum|cadmium|bendalloy|lead_|silver|platinum|nickle).*");


    public static boolean doesResourceContainsMetal(ResourceLocation input) {
        return ACTIVE_METAL_REGEX.matcher(input.getPath()).matches();
    }


    /**
     * Block state wrapper on {@link PowerUtils#isBlockMetal}
     *
     * @param state BlockState to check
     * @return whether the block state is metal
     */
    public static boolean isBlockStateMetal(BlockState state) {
        return isBlockMetal(state.getBlock());
    }

    /**
     * Determines if a block is metal or not
     *
     * @param block to be checked
     * @return Whether the block is metal
     */
    public static boolean isBlockMetal(Block block) {
        return isOnWhitelist(block.getRegistryName().toString());
    }

    /**
     * Determines if an item is metal or not
     *
     * @param item to be checked
     * @return Whether the item is metal
     */
    public static boolean isItemMetal(ItemStack item) {
        return isOnWhitelist(item.getItem().getRegistryName().toString());
    }

    private static boolean isOnWhitelist(String s) {
        return PowersConfig.whitelist.contains(s);
    }

    /**
     * Determines if an entity is metal or not
     *
     * @param entity to be checked
     * @return Whether the entity is metallic
     */
    public static boolean isEntityMetal(Entity entity) {
        if (entity == null) {
            return false;
        }
        if (entity instanceof ItemEntity item) {
            return isItemMetal(item.getItem());
        }
        if (entity instanceof ItemFrame itemFrame) {
            return isItemMetal(itemFrame.getItem());
        }
        if (entity instanceof FallingBlockEntity fbe) {
            return isBlockStateMetal(fbe.getBlockState());
        }
        if (entity instanceof ProjectileNuggetEntity) {
            return true;
        }
        if (entity instanceof AbstractMinecart) {
            return true;
        }
        if (entity instanceof LivingEntity ent) {
            if (ent instanceof IronGolem) {
                return true;
            }
            if (isItemMetal(ent.getItemInHand(InteractionHand.MAIN_HAND)) || isItemMetal(ent.getItemInHand(InteractionHand.OFF_HAND))) {
                return true;
            }
            for (ItemStack itemStack : ent.getArmorSlots()) {
                if (isItemMetal(itemStack)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Wipe all metals from the player and sync to tracking entities. Used by Aluminum and Nicrosil
     *
     * @param player The player to wipe
     */
    public static void wipePlayer(Player player) {
        player.getCapability(AllomancerCapability.PLAYER_CAP).ifPresent(data -> {
            data.drainMetals(Metal.values());
            player.removeAllEffects();

            if (player instanceof ServerPlayer) {
                Network.sync(data, player);
            }
        });
    }

    /**
     * Move an entity either toward or away from an anchor point
     *
     * @param directionScalar the direction and (possibly) scalar multiple of the magnitude
     * @param toMove          the entity to move
     * @param block           the point being moved toward or away from
     */
    public static void move(double directionScalar, Entity toMove, BlockPos block) {

        if (toMove.isPassenger()) {
            toMove = toMove.getVehicle();
        }

        Vec3 motion = toMove.position().subtract(Vec3.atCenterOf(block)).normalize().scale(directionScalar * 1.1);
        Vec3 mod = clamp(cutoff(motion.add(toMove.getDeltaMovement()), 0.1), abs(motion).reverse(), abs(motion));
        toMove.setDeltaMovement(mod);
        toMove.hurtMarked = true;

        // Only save players from fall damage
        if (toMove instanceof ServerPlayer) {
            toMove.fallDistance = 0;
        }
    }


    /*
     * Three helper functions for working with Vector3ds
     */
    private static Vec3 clamp(Vec3 value, Vec3 min, Vec3 max) {
        return new Vec3(Mth.clamp(value.x, min.x, max.x), Mth.clamp(value.y, min.y, max.y), Mth.clamp(value.z, min.z, max.z));
    }

    private static Vec3 abs(Vec3 vec) {
        return new Vec3(Math.abs(vec.x), Math.abs(vec.y), Math.abs(vec.z));
    }

    private static Vec3 cutoff(Vec3 value, double e) {
        Vec3 mag = abs(value);
        return new Vec3(mag.x < e ? 0 : value.x, mag.y < e ? 0 : value.y, mag.z < e ? 0 : value.z);
    }


    /**
     * Teleports a player to the given dimension and blockpos
     *
     * @param player    The player to move
     * @param world     The server world. Fails if clientside
     * @param dimension Dimension to call {@link Entity#changeDimension} on
     * @param pos       BlockPos to move the player to using {@link Entity#teleportToWithTicket}
     */
    public static void teleport(Player player, Level world, ResourceKey<Level> dimension, BlockPos pos) {
        if (!world.isClientSide) {
            if (player != null) {
                if (player.isPassenger()) {
                    player.stopRiding();
                }

                if (player.level.dimension() != dimension) {
                    //change dimension
                    player = (Player) player.changeDimension(world.getServer().getLevel(dimension), new ITeleporter() {
                        @Override
                        public Entity placeEntity(Entity entity, ServerLevel currentWorld, ServerLevel destWorld, float yaw, Function<Boolean, Entity> repositionEntity) {
                            Entity repositionedEntity = repositionEntity.apply(false);
                            repositionedEntity.teleportTo(pos.getX(), pos.getY(), pos.getZ());
                            return repositionedEntity;
                        }
                    });
                }

                player.teleportToWithTicket(pos.getX(), pos.getY() + 1.5, pos.getZ());
                player.fallDistance = 0.0F;
            }
        }
    }

    public static void riotEntity(PathfinderMob target, Player allomancer, boolean enhanced) {
        try {
            if (!enhanced) {
                //Enable Targeting goals
                target.targetSelector.enableControlFlag(Goal.Flag.TARGET);
                //Add new goals
                target.setTarget(allomancer);
                target.setLastHurtByMob(allomancer);
                // TODO: try to use PrioritizedGoal::startExecuting for already hostiles
                target.targetSelector.addGoal(1, new AIAttackOnCollideExtended(target, 1d, false));
                target.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(target, Player.class, false));
                target.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(target, target.getClass(), false));
                target.goalSelector.addGoal(4, new RandomLookAroundGoal(target));
                target.targetSelector.addGoal(2, new HurtByTargetGoal(target).setAlertOthers());
                if (target.getAttribute(Attributes.ATTACK_DAMAGE) != null && !(target instanceof Guardian)) {
                    target.goalSelector.addGoal(3, new MeleeAttackGoal(target, 1.2D, true));
                }

                target.setAggressive(true);

                if (target instanceof Creeper creeper) {
                    target.goalSelector.addGoal(1, new SwellGoal(creeper));
                }
                if (target instanceof Rabbit rabbit) {
                    target.goalSelector.addGoal(1, new AIEvilAttack(rabbit));
                }
                if (target instanceof AbstractSkeleton skeleton) {
                    target.goalSelector.addGoal(1, new RangedBowAttackGoal<>(skeleton, 1.0D, 20, 15.0F));
                }
                if (target instanceof Illusioner illusioner) {
                    target.goalSelector.addGoal(1, new RangedBowAttackGoal<>(illusioner, 0.5D, 20, 15.0F));
                }
                if (target instanceof Pillager pillager) {
                    target.goalSelector.addGoal(2, new RangedCrossbowAttackGoal<>(pillager, 1.0D, 8.0F));
                }
            } else {
                target.level.explode(target, target.position().x(), target.position().y(), target.position().z(), 1.2F, false, Explosion.BlockInteraction.BREAK);
                target.kill();
            }
        } catch (Exception e) {
            Allomancy.LOGGER.error("Failed to riot entity " + target + "! Please report this error!", e);
        }
    }

    public static void sootheEntity(PathfinderMob target, Player allomancer, boolean enhanced) {
        try {
            if (!enhanced) {
                if (target.isNoAi()) {
                    target.setNoAi(false);
                }
                // Reset all current aggro goals
                target.goalSelector.getRunningGoals().filter(isAggroGoal).forEach(WrappedGoal::stop);
                target.targetSelector.getRunningGoals().filter(isAggroGoal).forEach(WrappedGoal::stop);
                target.goalSelector.tick();
                target.targetSelector.tick();
                target.setTarget(null);
                target.setLastHurtByMob(null);
                //Disable targeting as a whole
                target.targetSelector.disableControlFlag(Goal.Flag.TARGET);
                target.setAggressive(false);
                //Add new goals
                target.goalSelector.addGoal(7, new LookAtPlayerGoal(target, Player.class, 6.0F));

                if (target instanceof TamableAnimal animal) {
                    if (Math.random() < 0.3) {
                        animal.tame(allomancer);
                    }
                }
                if (target instanceof AbstractHorse horse) {
                    if (Math.random() < 0.3) {
                        horse.tameWithName(allomancer);
                    }
                }
                if (target instanceof Sheep) {
                    target.goalSelector.addGoal(1, new EatBlockGoal(target));
                }
                if (target instanceof Villager villager) {
                    villager.onReputationEventFrom(ReputationEventType.TRADE, allomancer);
                }
                if (target instanceof WanderingTrader) {
                    target.goalSelector.addGoal(1, new TradeWithPlayerGoal((AbstractVillager) target));
                }
            } else { // Completely remove all AI if enhanced
                target.setNoAi(true);
            }

        } catch (Exception e) {
            Allomancy.LOGGER.error("Failed to soothe entity " + target + "! Please report this error!", e);
        }

    }
} public class PowersConfig {

    public static final Set<String> whitelist = new HashSet<>();
    public static ForgeConfigSpec.IntValue max_metal_detection;
    public static ForgeConfigSpec.BooleanValue animate_selection;
    public static ForgeConfigSpec.BooleanValue enable_more_keybinds;
    public static ForgeConfigSpec.BooleanValue enable_overlay;
    public static ForgeConfigSpec.EnumValue<SCREEN_LOC> overlay_position;
    public static ForgeConfigSpec.BooleanValue random_mistings;
    public static ForgeConfigSpec.BooleanValue generate_whitelist;
    public static ForgeConfigSpec.BooleanValue respect_player_UUID;
    public static ForgeConfigSpec.ConfigValue<List<? extends String>> cfg_whitelist;
    private static HashSet<String> defaultList;

    public static void init(ForgeConfigSpec.Builder server_builder, ForgeConfigSpec.Builder client_builder) {

        server_builder.comment("Settings for the gameplay elements of the mod").push("gameplay");
        random_mistings = server_builder.comment("Spawn players as a random Misting").define("random_mistings", true);
        generate_whitelist = server_builder.comment("Regenerate the metal whitelist").define("regenerate_whitelist", true);
        respect_player_UUID = server_builder.comment("Decides whether your spawn metal is based off your UUID (this will cause it to be consistent across worlds)").define("respect_player_UUID", false);
        cfg_whitelist = server_builder
                .comment("List of registry names of items and blocks that are counted as 'metal'")
                .defineList("whitelist", new ArrayList<>(), o -> o instanceof String);
        server_builder.pop();

        client_builder.push("graphics");
        max_metal_detection = client_builder.comment("Maximum iron/steelsight distance. Can have a HUGE impact on performance").defineInRange("max_metal_distance", 15, 3, 30);
        animate_selection = client_builder.comment("Animate the selection wheel").define("animate_selection", true);
        enable_overlay = client_builder.comment("Enable the screen overlay").define("overlay_enabled", true);
        overlay_position = client_builder.comment("Screen Overlay Position").defineEnum("overlay_position", SCREEN_LOC.TOP_LEFT);
        client_builder.pop();

        client_builder.push("controls");
        enable_more_keybinds = client_builder.comment("Register extra keys, one for each metal, which toggle that metal specifically").define("advanced_keybinds", false);
        client_builder.pop();


    }

    public static void refresh(final ModConfigEvent e) {
        ModConfig cfg = e.getConfig();
        if (cfg.getSpec() == AllomancyConfig.SERVER_CONFIG) {
            refresh_whitelist();
        }
    }

    private static void refresh_whitelist() {
        whitelist.clear();
        whitelist.addAll(cfg_whitelist.get());
    }

    public static void load_whitelist(final ModConfigEvent.Loading e) {
        ModConfig cfg = e.getConfig();
        if (cfg.getSpec() == AllomancyConfig.SERVER_CONFIG) {
            if (generate_whitelist.get()) {
                ArrayList<String> list = new ArrayList<>(default_whitelist());
                list.sort(String::compareTo);
                cfg_whitelist.set(list);
                generate_whitelist.set(false);
            }
            refresh_whitelist();
        }
    }

    private static Set<String> default_whitelist() {
        defaultList = new HashSet<>();

        add(Items.CHAINMAIL_BOOTS);
        add(Items.BUCKET);
        add(Items.LAVA_BUCKET);
        add(Items.MILK_BUCKET);
        add(Items.COD_BUCKET);
        add(Items.PUFFERFISH_BUCKET);
        add(Items.SALMON_BUCKET);
        add(Items.TROPICAL_FISH_BUCKET);
        add(Items.WATER_BUCKET);
        add(Items.CAULDRON);
        add(Items.COMPASS);
        add(Items.CHAINMAIL_HELMET);
        add(Items.CHAINMAIL_LEGGINGS);
        add(Items.MINECART);
        add(Items.CHEST_MINECART);
        add(Items.HOPPER_MINECART);
        add(Items.FURNACE_MINECART);
        add(Items.TNT_MINECART);
        add(Items.CHAINMAIL_CHESTPLATE);
        add(Items.CLOCK);
        add(Items.SHEARS);
        add(Items.SHIELD);
        add(Items.NETHERITE_INGOT);
        add(Items.NETHERITE_SCRAP); // Op?
        add(Items.NETHERITE_HELMET);
        add(Items.NETHERITE_CHESTPLATE);
        add(Items.NETHERITE_LEGGINGS);
        add(Items.NETHERITE_BOOTS);
        add(Items.NETHERITE_HOE);
        add(Items.NETHERITE_PICKAXE);
        add(Items.NETHERITE_SHOVEL);
        add(Items.NETHERITE_SWORD);
        add(Items.NETHERITE_AXE);
        add(Items.CROSSBOW);

        add(Blocks.ANVIL);
        add(Blocks.CHIPPED_ANVIL);
        add(Blocks.DAMAGED_ANVIL);
        add(Blocks.CAULDRON);
        add(Blocks.SMITHING_TABLE);
        add(Blocks.STONECUTTER);
        add(Blocks.CHAIN);
        add(Blocks.HOPPER);
        add(Blocks.PISTON_HEAD);
        add(Blocks.MOVING_PISTON);
        add(Blocks.STICKY_PISTON);
        add(Blocks.BLAST_FURNACE);
        add(Blocks.BELL);
        add(Blocks.PISTON);
        add(Blocks.LIGHT_WEIGHTED_PRESSURE_PLATE);
        add(Blocks.HEAVY_WEIGHTED_PRESSURE_PLATE);
        add(Blocks.RAIL);
        add(Blocks.ACTIVATOR_RAIL);
        add(Blocks.DETECTOR_RAIL);
        add(Blocks.POWERED_RAIL);
        add(Blocks.LANTERN);
        add(Blocks.TRAPPED_CHEST);
        add(Blocks.TRIPWIRE_HOOK);
        add(Blocks.SOUL_LANTERN);
        add(Blocks.NETHERITE_BLOCK);
        add(Blocks.ANCIENT_DEBRIS); // OP? TODO: consider if this should not be here, alongside scrap
        add(Blocks.LODESTONE);
        add(Blocks.GILDED_BLACKSTONE);

        add(ConsumeSetup.VIAL.get());
        add(ConsumeSetup.LERASIUM_NUGGET.get());
        add(ConsumeSetup.ALLOMANTIC_GRINDER.get());
        add(CombatSetup.COIN_BAG.get());


        ForgeRegistries.ITEMS
                .getValues()
                .stream()
                .map(ForgeRegistryEntry::getRegistryName)
                .filter(Objects::nonNull)
                .filter(PowerUtils::doesResourceContainsMetal)
                .forEach(PowersConfig::add);

        ForgeRegistries.BLOCKS
                .getValues()
                .stream()
                .map(ForgeRegistryEntry::getRegistryName)
                .filter(Objects::nonNull)
                .filter(PowerUtils::doesResourceContainsMetal)
                .forEach(PowersConfig::add);

        return defaultList;

    }


    private static void add(String s) {
        Allomancy.LOGGER.info("Adding " + s + " to the default whitelist!");
        defaultList.add(s);
    }

    private static void add(ResourceLocation r) {
        add(r.toString());
    }

    private static void add(ForgeRegistryEntry<?> registryEntry) {
        add(registryEntry.getRegistryName());
    }


    public enum SCREEN_LOC {
        TOP_RIGHT,
        BOTTOM_RIGHT,
        TOP_LEFT,
        BOTTOM_LEFT
    }

} public class MetalOverlay {
    private static final Point[] Frames = new Point[4];
    private static final ResourceLocation meterLoc = new ResourceLocation("allomancy", "textures/gui/overlay/meter.png");
    private static int animationCounter = 0;
    private static int currentFrame = 0;

    static {
        int x = 0;
        int firsty = 22;
        for (int i = 0; i < 4; i++) {
            Frames[i] = new Point(x, firsty + (4 * i));
        }
    }

    /**
     * Draws the overlay for the metals
     *
     * @param matrix
     */
    public static void drawMetalOverlay(PoseStack matrix) {
        Minecraft mc = Minecraft.getInstance();
        LocalPlayer player = mc.player;
        Window res = mc.getWindow();

        if (!player.isAlive()) {
            return;
        }
        int renderX, renderY;

        // Set the offsets of the overlay based on config
        switch (PowersConfig.overlay_position.get()) {
            case TOP_RIGHT -> {
                renderX = res.getGuiScaledWidth() - 145;
                renderY = 10;
            }
            case BOTTOM_RIGHT -> {
                renderX = res.getGuiScaledWidth() - 145;
                renderY = res.getGuiScaledHeight() - 50;
            }
            case BOTTOM_LEFT -> {
                renderX = 5;
                renderY = res.getGuiScaledHeight() - 50;
            }
            default -> { // TOP_LEFT
                renderX = 5;
                renderY = 10;
            }
        }

        ForgeIngameGui gui = new ForgeIngameGui(mc);
        RenderSystem.setShader(GameRenderer::getPositionTexShader);
        RenderSystem.setShaderTexture(0, meterLoc);

        player.getCapability(AllomancerCapability.PLAYER_CAP).ifPresent(data -> {

            if (data.isUninvested()) {
                return;
            }

            /*
             * The rendering for the overlay
             */
            for (Metal mt : Metal.values()) {
                if (data.hasPower(mt)) {
                    int metalY = 9 - data.getAmount(mt);
                    int i = mt.getIndex();
                    int offset = (i / 2) * 4; // Adding a gap between pairs
                    // Draw the bars first
                    blit(matrix, gui, renderX + 1 + (7 * i) + offset, renderY + 5 + metalY, 7 + (6 * i), 1 + metalY, 3, 10 - metalY);
                    // Draw the gauges second, so that highlights and decorations show over the bar.
                    blit(matrix, gui, renderX + (7 * i) + offset, renderY, 0, 0, 5, 20);
                    // Draw the fire if it is burning
                    if (data.isBurning(mt)) {
                        blit(matrix, gui, renderX + (7 * i) + offset, renderY + 4 + metalY, Frames[currentFrame].x, Frames[currentFrame].y, 5, 3);
                    }
                }

            }
        });

        // Update the animation counters
        animationCounter++;
        if (animationCounter > 6) {
            animationCounter = 0;
            currentFrame++;
            if (currentFrame > 3) {
                currentFrame = 0;
            }
        }
    }

    private static void blit(PoseStack matrix, ForgeIngameGui gui, int x, int y, float uOffset, float vOffset, int uWidth, int vHeight) {
        ForgeIngameGui.blit(matrix, x, y, gui.getBlitOffset(), uOffset, vOffset, uWidth, vHeight, 128, 128);
    }
} @OnlyIn(Dist.CLIENT)
public class MetalSelectScreen extends Screen {

    private static final String[] METAL_NAMES = Arrays.stream(Metal.values()).map(Metal::getName).toArray(String[]::new);
    private static final String GUI_METAL = "allomancy:textures/gui/metals/%s_symbol.png";
    private static final String[] METAL_LOCAL = Arrays.stream(METAL_NAMES).map(s -> "metals." + s).toArray(String[]::new);
    private static final ResourceLocation[] METAL_ICONS = Arrays.stream(METAL_NAMES).map(s -> new ResourceLocation(String.format(GUI_METAL, s))).toArray(ResourceLocation[]::new);
    final Minecraft mc;
    int timeIn = PowersConfig.animate_selection.get() ? 0 : 16; // Config setting for whether the wheel animates open or instantly appears
    int slotSelected = -1;

    public MetalSelectScreen() {
        super(new TextComponent("allomancy_gui"));
        this.mc = Minecraft.getInstance();
    }

    private static double mouseAngle(int x, int y, int mx, int my) {
        return (Mth.atan2(my - y, mx - x) + Math.PI * 2) % (Math.PI * 2);
    }

    private static int toMetalIndex(int segment) {
        return (segment + 5) % Metal.values().length;
    }

    @Override
    public void render(PoseStack matrixStack, int mx, int my, float partialTicks) {
        super.render(matrixStack, mx, my, partialTicks);

        this.mc.player.getCapability(AllomancerCapability.PLAYER_CAP).ifPresent(data -> {

            int x = this.width / 2;
            int y = this.height / 2;
            int maxRadius = 80;

            double angle = mouseAngle(x, y, mx, my);

            int segments = METAL_NAMES.length;
            float step = (float) Math.PI / 180;
            float degPer = (float) Math.PI * 2 / segments;

            this.slotSelected = -1;

            Tesselator tess = Tesselator.getInstance();
            BufferBuilder buf = tess.getBuilder();


            RenderSystem.disableCull();
            RenderSystem.disableTexture();
            RenderSystem.enableBlend();
            RenderSystem.setShader(GameRenderer::getPositionColorShader);

            buf.begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION_COLOR);

            for (int seg = 0; seg < segments; seg++) {
                Metal mt = Metal.getMetal(toMetalIndex(seg));
                boolean mouseInSector = data.hasPower(mt) && (degPer * seg < angle && angle < degPer * (seg + 1));
                float radius = Math.max(0F, Math.min((this.timeIn + partialTicks - seg * 6F / segments) * 40F, maxRadius));
                if (mouseInSector) {
                    this.slotSelected = seg;
                    radius *= 1.025f;
                }

                int gs = 0x55;
                if (seg % 2 == 0) {
                    gs += 0x19;
                }

                gs = (!data.hasPower(mt) || data.getAmount(mt) == 0) ? 0 : gs;

                int r = data.isBurning(mt) ? 0xFF : gs;
                int g = gs;
                int b = gs;
                int a = 0x99;

                if (seg == 0) {
                    buf.vertex(x, y, 0).color(0x19, 0x19, 0x19, 0x15).endVertex();
                }

                for (float v = 0; v < degPer + step / 2; v += step) {
                    float rad = v + seg * degPer;
                    float xp = x + Mth.cos(rad) * radius;
                    float yp = y + Mth.sin(rad) * radius;

                    if (v == 0) {
                        buf.vertex(xp, yp, 0).color(r, g, b, a).endVertex();
                    }
                    buf.vertex(xp, yp, 0).color(r, g, b, a).endVertex();
                }
            }
            tess.end();

            RenderSystem.enableTexture();

            for (int seg = 0; seg < segments; seg++) {
                Metal mt = Metal.getMetal(toMetalIndex(seg));
                boolean mouseInSector = data.hasPower(mt) && (degPer * seg < angle && angle < degPer * (seg + 1));
                float radius = Math.max(0F, Math.min((this.timeIn + partialTicks - seg * 6F / segments) * 40F, maxRadius));
                if (mouseInSector) {
                    radius *= 1.025f;
                }


                float rad = (seg + 0.5f) * degPer;
                float xp = x + Mth.cos(rad) * radius;
                float yp = y + Mth.sin(rad) * radius;

                float xsp = xp - 4;
                float ysp = yp;
                String name = (mouseInSector ? ChatFormatting.UNDERLINE : ChatFormatting.RESET) + new TranslatableComponent(METAL_LOCAL[toMetalIndex(seg)]).getString();
                int textwidth = this.mc.font.width(name);

                if (xsp < x) {
                    xsp -= textwidth - 8;
                }
                if (ysp < y) {
                    ysp -= 9;
                }

                this.mc.font.drawShadow(matrixStack, name, xsp, ysp, 0xFFFFFF);

                double mod = 0.8;
                int xdp = (int) ((xp - x) * mod + x);
                int ydp = (int) ((yp - y) * mod + y);
                RenderSystem.setShader(GameRenderer::getPositionTexShader);
                RenderSystem.setShaderTexture(0, METAL_ICONS[toMetalIndex(seg)]);
                blit(matrixStack, xdp - 8, ydp - 8, 0, 0, 16, 16, 16, 16);

            }

            RenderSystem.enableBlend();
            RenderSystem.blendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 1, 0);
            RenderSystem.disableBlend();
        });

    }

    @Override
    public boolean mouseClicked(double mouseX, double mouseY, int mouseButton) {
        toggleSelected();
        return super.mouseClicked(mouseX, mouseY, mouseButton);
    }

    @Override
    public void tick() { // tick
        this.timeIn++;
    }

    @Override
    public boolean keyReleased(int keysym, int scancode, int modifiers) {
        if (PowersClientSetup.burn.matches(keysym, scancode)) {
            this.mc.setScreen(null);
            this.mc.mouseHandler.grabMouse();
            return true;
        }
        return super.keyReleased(keysym, scancode, modifiers);
    }


    @Override
    public boolean mouseReleased(double mouseX, double mouseY, int button) {
        if (PowersClientSetup.burn.matchesMouse(button)) {
            this.mc.setScreen(null);
            this.mc.mouseHandler.grabMouse();
            return true;
        }
        return super.mouseReleased(mouseX, mouseY, button);
    }


    /**
     * Toggles the metal the mouse is currently over
     */
    private void toggleSelected() {
        if (this.slotSelected != -1) {
            Metal mt = Metal.getMetal(toMetalIndex(this.slotSelected));
            this.mc.player.getCapability(AllomancerCapability.PLAYER_CAP).ifPresent(data -> {
                ClientUtils.toggleBurn(mt, data);
                this.mc.player.playSound(SoundEvents.UI_BUTTON_CLICK, 0.1F, 2.0F);
            });
        }
    }

    @Override
    public boolean isPauseScreen() {
        return false;
    }
} public class SoundParticleData implements ParticleOptions {


    public static final ParticleOptions.Deserializer<SoundParticleData> DESERIALIZER = new ParticleOptions.Deserializer<>() {

        @Override
        public SoundParticleData fromCommand(ParticleType<SoundParticleData> particleTypeIn, StringReader reader) {
            return new SoundParticleData(SoundSource.AMBIENT);
        }

        @Override
        public SoundParticleData fromNetwork(ParticleType<SoundParticleData> particleTypeIn, FriendlyByteBuf buffer) {
            return new SoundParticleData(buffer.readEnum(SoundSource.class));
        }
    };
    private final SoundSource type;

    public SoundParticleData(SoundSource type) {
        this.type = type;
    }

    protected SoundSource getSoundType() {
        return this.type;
    }

    @Override
    public ParticleType<?> getType() {
        return PowersClientSetup.SOUND_PARTICLE_TYPE.get();
    }

    @Override
    public void writeToNetwork(FriendlyByteBuf buffer) {
        buffer.writeEnum(this.type);
    }

    @Override
    public String writeToString() {
        return Registry.PARTICLE_TYPE.getKey(this.getType()) + " " + this.type.toString();
    }
} @OnlyIn(Dist.CLIENT)
public class ClientUtils {

    private static Minecraft mc = Minecraft.getInstance();
    private static final LocalPlayer player = mc.player;

    /**
     * Adapted from vanilla, allows getting mouseover at given distances
     *
     * @param dist the distance requested
     * @return a RayTraceResult for the requested raytrace
     */
    @Nullable
    public static HitResult getMouseOverExtended(float dist) {
        mc = Minecraft.getInstance();
        float partialTicks = mc.getFrameTime();
        HitResult objectMouseOver = null;
        Entity entity = mc.getCameraEntity();
        if (entity != null) {
            if (mc.level != null) {
                objectMouseOver = entity.pick(dist, partialTicks, false);
                Vec3 vec3d = entity.getEyePosition(partialTicks);
                boolean flag = false;
                int i = 3;
                double d1;

                d1 = objectMouseOver.getLocation().distanceToSqr(vec3d);

                Vec3 vec3d1 = entity.getViewVector(1.0F);
                Vec3 vec3d2 = vec3d.add(vec3d1.x * dist, vec3d1.y * dist, vec3d1.z * dist);
                float f = 1.0F;
                AABB axisalignedbb = entity.getBoundingBox().expandTowards(vec3d1.scale(dist)).inflate(1.0D, 1.0D, 1.0D);
                EntityHitResult entityraytraceresult = ProjectileUtil.getEntityHitResult(entity, vec3d, vec3d2, axisalignedbb, (e) -> true, d1);
                if (entityraytraceresult != null) {
                    Entity entity1 = entityraytraceresult.getEntity();
                    Vec3 vec3d3 = entityraytraceresult.getLocation();
                    double d2 = vec3d.distanceToSqr(vec3d3);
                    if (d2 < d1) {
                        objectMouseOver = entityraytraceresult;
                    }
                }

            }
        }
        return objectMouseOver;

    }

    /**
     * Draws a line from the player (denoted pX,Y,Z) to the given set of
     * coordinates (oX,Y,Z) in a certain color (r,g,b)
     *
     * @param player
     * @param dest
     * @param width  the width of the line
     */
    public static void drawMetalLine(PoseStack stack, Vec3 player, Vec3 dest, float width, float r, float g, float b) {

        //        RenderSystem.lineWidth(width);
        Tesselator tessellator = Tesselator.getInstance();
        BufferBuilder builder = tessellator.getBuilder();

        builder.begin(VertexFormat.Mode.LINES, DefaultVertexFormat.POSITION_COLOR);
        Matrix4f matrix4f = stack.last().pose();
        builder.vertex(matrix4f, (float) player.x, (float) player.y, (float) player.z).color(r, g, b, 0.6f).endVertex();
        builder.vertex(matrix4f, (float) dest.x, (float) dest.y, (float) dest.z).color(r, g, b, 0.6f).endVertex();
        RenderSystem.lineWidth(width);

        tessellator.end();

    }


    /**
     * Used to toggle a metal's burn state and play a sound effect
     *
     * @param metal      the index of the metal to toggle
     * @param capability the capability being handled
     */
    public static void toggleBurn(Metal metal, IAllomancerData capability) {
        if (!capability.hasPower(metal)) {
            return;
        }

        Network.sendToServer(new UpdateBurnPacket(metal, !capability.isBurning(metal)));

        if (capability.getAmount(metal) > 0) {
            capability.setBurning(metal, !capability.isBurning(metal));
        }
        // play a sound effect
        if (capability.isBurning(metal)) {
            player.playSound(new SoundEvent(new ResourceLocation("item.flintandsteel.use")), 1, 5);
        } else {
            player.playSound(new SoundEvent(new ResourceLocation("block.fire.extinguish")), 1, 4);
        }
    }
} public class MetalBlockBlob {

    private static final Level level = Minecraft.getInstance().level;
    private final Set<BlockPos> blocks = new HashSet<>();
    private Vec3 center = null;

    public MetalBlockBlob(BlockPos initial) {
        this.add(initial);
    }

    public MetalBlockBlob() {

    }

    public static MetalBlockBlob merge(MetalBlockBlob blob1, MetalBlockBlob blob2) {
        if (blob1 == null) {
            return blob2;
        } else if (blob2 == null) {
            return blob1;
        }

        MetalBlockBlob blob3 = new MetalBlockBlob();
        blob3.blocks.addAll(blob1.blocks);
        blob3.blocks.addAll(blob2.blocks);
        blob3.center = blob1.center.scale(blob1.blocks.size()).add(blob2.center.scale(blob2.blocks.size())).scale(1.0 / blob3.blocks.size());
        return blob3;
    }

    private static Vec3 getCenterOfBlock(BlockPos pos) {
        try {
            return Vec3.atLowerCornerOf(pos).add(level.getBlockState(pos).getShape(level, pos).bounds().getCenter());
        } catch (UnsupportedOperationException e) {
            return Vec3.atCenterOf(pos);
        }
    }

    public boolean isMatch(BlockPos pos) {
        return this.blocks.stream().anyMatch(bp -> Vec3.atCenterOf(bp).distanceTo(Vec3.atCenterOf(pos)) <= 1.5);
    }

    public int size() {
        return this.blocks.size();
    }

    public boolean add(BlockPos pos) {
        pos = pos.immutable();

        if (this.blocks.add(pos)) {

            if (this.center == null) {
                this.center = getCenterOfBlock(pos);
            } else {
                int count = this.blocks.size();
                this.center = this.center.scale(count - 1).add(getCenterOfBlock(pos)).scale(1.0D / count);

            }
            return true;
        }
        return false;
    }

    public boolean isIn(BlockPos pos) {
        return this.blocks.contains(pos);
    }

    public Vec3 getCenter() {
        return this.center;
    }

} public class AllomancerCapability {

    public static final Capability<IAllomancerData> PLAYER_CAP = CapabilityManager.get(new CapabilityToken<>() {
    });

    public static final ResourceLocation IDENTIFIER = new ResourceLocation(Allomancy.MODID, "allomancy_data");

    public static void registerCapability(final RegisterCapabilitiesEvent event) {
        event.register(IAllomancerData.class);
    }

} public class ChangeEmotionPacket {

    private final int entityID;
    private final boolean make_aggressive;


    /**
     * Make a mob either angry or passive, depending on aggro
     *
     * @param entityID        the mob to be effected
     * @param make_aggressive true if mob should be aggressive
     */
    public ChangeEmotionPacket(int entityID, boolean make_aggressive) {
        this.entityID = entityID;
        this.make_aggressive = make_aggressive;
    }

    public static ChangeEmotionPacket decode(FriendlyByteBuf buf) {
        return new ChangeEmotionPacket(buf.readInt(), buf.readBoolean());
    }

    public void encode(FriendlyByteBuf buf) {
        buf.writeInt(this.entityID);
        buf.writeBoolean(this.make_aggressive);
    }

    public void handle(Supplier<NetworkEvent.Context> ctx) {
        ctx.get().enqueueWork(() -> {
            Player allomancer = ctx.get().getSender();
            PathfinderMob target = (PathfinderMob) allomancer.level.getEntity(this.entityID);
            if (target == null) {
                return;
            }
            boolean enhanced = allomancer.getCapability(AllomancerCapability.PLAYER_CAP).filter(IAllomancerData::isEnhanced).isPresent();
            if (this.make_aggressive) {
                PowerUtils.riotEntity(target, allomancer, enhanced);
            } else {
                PowerUtils.sootheEntity(target, allomancer, enhanced);
            }
        });
        ctx.get().setPacketHandled(true);
    }
} public class TryPushPullBlock {

    private final BlockPos blockPos;
    private final int direction;

    /**
     * Send a request to the server to use iron or steel on a block
     *
     * @param block     the block
     * @param direction the direction (1 for push, -1 for pull)
     */
    public TryPushPullBlock(BlockPos block, int direction) {
        this.blockPos = block;
        this.direction = direction;
    }

    public static TryPushPullBlock decode(FriendlyByteBuf buf) {
        return new TryPushPullBlock(buf.readBlockPos(), buf.readInt());
    }

    public void encode(FriendlyByteBuf buf) {
        buf.writeBlockPos(this.blockPos);
        buf.writeInt(this.direction);
    }

    public void handle(Supplier<NetworkEvent.Context> ctx) {
        ctx.get().enqueueWork(() -> {
            ServerPlayer player = ctx.get().getSender();
            BlockPos pos = this.blockPos;
            // Sanity check to make sure  the block is loaded in the server
            if (player.level.isLoaded(pos)) {
                // activate blocks
                if (player.level.getBlockState(pos).getBlock() instanceof IAllomanticallyUsableBlock block) {
                    block.useAllomantically(player.level.getBlockState(pos), player.level, pos, player, this.direction > 0);
                } else if (PowerUtils.isBlockStateMetal(player.level.getBlockState(pos)) // Check whitelist on server
                           || (player.getMainHandItem().getItem() == CombatSetup.COIN_BAG.get() // check coin bag
                               && (!player.getProjectile(player.getMainHandItem()).isEmpty()) && this.direction > 0)) {
                    PowerUtils.move(this.direction, player, pos);
                }
            }
        });
        ctx.get().setPacketHandled(true);
    }
} public class TryPushPullEntity {

    private final int entityIDOther;
    private final int direction;

    /**
     * Send a request to the server to use iron or steel on an entity
     *
     * @param entityIDOther the entity you are requesting the data of
     * @param direction     the direction (1 for push, -1 for pull)
     */
    public TryPushPullEntity(int entityIDOther, int direction) {
        this.entityIDOther = entityIDOther;
        this.direction = direction;

    }

    public static TryPushPullEntity decode(FriendlyByteBuf buf) {
        return new TryPushPullEntity(buf.readInt(), buf.readInt());
    }

    public void encode(FriendlyByteBuf buf) {
        buf.writeInt(this.entityIDOther);
        buf.writeInt(this.direction);
    }

    public void handle(Supplier<NetworkEvent.Context> ctx) {
        ctx.get().enqueueWork(() -> {
            ServerPlayer player = ctx.get().getSender();
            Entity target = player.level.getEntity(this.entityIDOther);
            if (target != null) {
                if (PowerUtils.isEntityMetal(target)) {
                    // The player moves
                    if (target instanceof IronGolem || target instanceof ItemFrame) {
                        PowerUtils.move(this.direction, player, target.blockPosition());

                    } else if (target instanceof ItemEntity || target instanceof FallingBlockEntity || target instanceof ArmorStand ||
                               (target instanceof AbstractMinecart && !target.isVehicle())) {
                        PowerUtils.move(this.direction / 2.0, target, player.blockPosition());

                        // Split the difference
                    } else if (!(target instanceof ThrowableItemProjectile)) {
                        PowerUtils.move(this.direction / 2.0, target, player.blockPosition());

                        PowerUtils.move(this.direction / 2.0, player, target.blockPosition());
                    }
                }
            }

        });
        ctx.get().setPacketHandled(true);
    }
} public class UpdateEnhancedPacket {

    private final int enhance_time;
    private final int entityID;


    public UpdateEnhancedPacket(boolean enhanced, int entityID) {
        this.enhance_time = enhanced ? 100 : 0;
        this.entityID = entityID;
    }

    public UpdateEnhancedPacket(int enhance_time, int entityID) {
        this.enhance_time = enhance_time;
        this.entityID = entityID;
    }

    public static UpdateEnhancedPacket decode(FriendlyByteBuf buf) {
        return new UpdateEnhancedPacket(buf.readInt(), buf.readInt());
    }

    public void encode(FriendlyByteBuf buf) {
        buf.writeInt(this.enhance_time);
        buf.writeInt(this.entityID);
    }

    public void handle(Supplier<NetworkEvent.Context> ctx) {
        ctx.get().enqueueWork(() -> {
            if (ctx.get().getDirection() == NetworkDirection.PLAY_TO_SERVER) { // Update player of own status
                Player player = (Player) ctx.get().getSender().level.getEntity(this.entityID);
                if (player != null) {
                    player.getCapability(AllomancerCapability.PLAYER_CAP).ifPresent(data -> {
                        data.setEnhanced(this.enhance_time);
                        Network.sync(new UpdateEnhancedPacket(this.enhance_time, this.entityID), player);
                    });
                }
            } else {
                Player player = (Player) Minecraft.getInstance().level.getEntity(this.entityID);
                if (player != null) {
                    player.getCapability(AllomancerCapability.PLAYER_CAP).ifPresent(data -> data.setEnhanced(this.enhance_time));
                }
            }

        });
        ctx.get().setPacketHandled(true);
    }
} public class Network {

    private static final String VERSION = "1.1";
    public static final SimpleChannel INSTANCE = NetworkRegistry.newSimpleChannel(new ResourceLocation(Allomancy.MODID, "networking"), () -> VERSION, VERSION::equals,
                                                                                  VERSION::equals);

    private static int index = 0;

    private static int nextIndex() {
        return index++;
    }

    public static void registerPackets() {
        INSTANCE.registerMessage(nextIndex(), AllomancerDataPacket.class, AllomancerDataPacket::encode, AllomancerDataPacket::decode, AllomancerDataPacket::handle);
        INSTANCE.registerMessage(nextIndex(), UpdateBurnPacket.class, UpdateBurnPacket::encode, UpdateBurnPacket::decode, UpdateBurnPacket::handle);
        INSTANCE.registerMessage(nextIndex(), ChangeEmotionPacket.class, ChangeEmotionPacket::encode, ChangeEmotionPacket::decode, ChangeEmotionPacket::handle);
        INSTANCE.registerMessage(nextIndex(), TryPushPullEntity.class, TryPushPullEntity::encode, TryPushPullEntity::decode, TryPushPullEntity::handle);
        INSTANCE.registerMessage(nextIndex(), TryPushPullBlock.class, TryPushPullBlock::encode, TryPushPullBlock::decode, TryPushPullBlock::handle);
        INSTANCE.registerMessage(nextIndex(), UpdateEnhancedPacket.class, UpdateEnhancedPacket::encode, UpdateEnhancedPacket::decode, UpdateEnhancedPacket::handle);
    }

    public static void sendToServer(Object msg) {
        INSTANCE.sendToServer(msg);
    }

    public static void sendTo(Object msg, ServerPlayer player) {
        if (!(player instanceof FakePlayer)) {
            INSTANCE.sendTo(msg, player.connection.getConnection(), NetworkDirection.PLAY_TO_CLIENT);
        }
    }

    public static void sendTo(Object msg, PacketDistributor.PacketTarget target) {
        INSTANCE.send(target, msg);
    }

    public static void sync(Player player) {
        player.getCapability(AllomancerCapability.PLAYER_CAP).ifPresent(data -> sync(data, player));
    }

    public static void sync(IAllomancerData cap, Player player) {
        sync(new AllomancerDataPacket(cap, player), player);
    }

    public static void sync(Object msg, Player player) {
        sendTo(msg, PacketDistributor.TRACKING_ENTITY_AND_SELF.with(() -> player));
    }

}
import com.legobmw99.allomancy.api.enums.Metal;
import com.legobmw99.allomancy.modules.combat.CombatSetup;
import com.legobmw99.allomancy.modules.powers.PowerUtils;
import com.legobmw99.allomancy.modules.powers.PowersConfig;
import com.legobmw99.allomancy.modules.powers.client.gui.MetalOverlay;
import com.legobmw99.allomancy.modules.powers.client.gui.MetalSelectScreen;
import com.legobmw99.allomancy.modules.powers.client.particle.SoundParticleData;
import com.legobmw99.allomancy.modules.powers.client.util.ClientUtils;
import com.legobmw99.allomancy.modules.powers.client.util.MetalBlockBlob;
import com.legobmw99.allomancy.modules.powers.data.AllomancerCapability;
import com.legobmw99.allomancy.modules.powers.network.ChangeEmotionPacket;
import com.legobmw99.allomancy.modules.powers.network.TryPushPullBlock;
import com.legobmw99.allomancy.modules.powers.network.TryPushPullEntity;
import com.legobmw99.allomancy.modules.powers.network.UpdateEnhancedPacket;
import com.legobmw99.allomancy.network.Network;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.ChatScreen;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceKey;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.PathfinderMob;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.*;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.client.event.InputEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent.ElementType;
import net.minecraftforge.client.event.RenderLevelLastEvent;
import net.minecraftforge.client.event.sound.PlaySoundEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import org.lwjgl.glfw.GLFW;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
                    }

                    this.tickOffset = (this.tickOffset + 1) % 2;

                    if (this.tickOffset == 0) {
                        // Populate the metal lists
                        this.metal_blobs.clear();
                        this.metal_entities.clear();
                        if (data.isBurning(Metal.IRON) || data.isBurning(Metal.STEEL)) {
                            int max = PowersConfig.max_metal_detection.get();
                            BlockPos negative = player.blockPosition().offset(-max, -max, -max);
                            BlockPos positive = player.blockPosition().offset(max, max, max);

                            // Add metal entities to metal list
                            this.metal_entities.addAll(
                                    player.level.getEntitiesOfClass(Entity.class, new AABB(negative, positive), e -> PowerUtils.isEntityMetal(e) && !e.equals(player)));

                            // Add metal blobs to metal list
                            var blocks = BlockPos.betweenClosedStream(negative, positive);
                            blocks.filter(bp -> PowerUtils.isBlockStateMetal(player.level.getBlockState(bp))).forEach(bp -> {
                                var matches = this.metal_blobs.stream().filter(mbl -> mbl.isMatch(bp)).collect(Collectors.toSet());
                                switch (matches.size()) {
                                    case 0 -> // new blob
                                            this.metal_blobs.add(new MetalBlockBlob(bp));
                                    case 1 -> // add to existing blob
                                            matches.stream().findAny().get().add(bp);
                                    default -> { // this block serves as a bridge between (possibly many) existing blobs
                                        this.metal_blobs.removeAll(matches);
                                        MetalBlockBlob mbb = matches.stream().reduce(null, MetalBlockBlob::merge);
                                        mbb.add(bp);
                                        this.metal_blobs.add(mbb);
                                    }
                                }

                            });

                        }
                        // Populate our list of nearby allomancy users
                        this.nearby_allomancers.clear();
                        if (data.isBurning(Metal.BRONZE) && (data.isEnhanced() || !data.isBurning(Metal.COPPER))) {
                            // Add metal burners to a list
                            BlockPos negative = player.blockPosition().offset(-30, -30, -30);
                            BlockPos positive = player.blockPosition().offset(30, 30, 30);
                            var nearby_players = player.level.getEntitiesOfClass(Player.class, new AABB(negative, positive), entity -> entity != null && entity != player);


                            for (Player otherPlayer : nearby_players) {

                                boolean cont = otherPlayer.getCapability(AllomancerCapability.PLAYER_CAP).map(otherData -> {
                                    if (otherData.isBurning(Metal.COPPER) && (!data.isEnhanced() || otherData.isEnhanced())) {
                                        return false;
                                    }
                                    for (Metal mt : Metal.values()) {
                                        if (otherData.isBurning(mt)) {
                                            this.nearby_allomancers.add(otherPlayer);
                                            break;
                                        }
                                    }
                                    return true;
                                }).orElse(true);

                                if (!cont) {
                                    break;
                                }
                            }
                        }
                    }
                }
            });
        }
    }


    @OnlyIn(Dist.CLIENT)
    @SubscribeEvent
    public void onKeyInput(final InputEvent.KeyInputEvent event) {
        if (event.getAction() == GLFW.GLFW_PRESS) {
            acceptInput();
        }
    }

    @OnlyIn(Dist.CLIENT)
    @SubscribeEvent
    public void onMouseInput(final InputEvent.MouseInputEvent event) {
        if (event.getAction() == GLFW.GLFW_PRESS) {
            acceptInput();
        }
    }

    /**
     * Handles either mouse or button presses for the mod's keybinds
     */
    private void acceptInput() {

        boolean extras = PowersClientSetup.enable_more_keybinds && Arrays.stream(PowersClientSetup.powers).anyMatch(KeyMapping::isDown);

        if (PowersClientSetup.burn.isDown() || extras) {
            Player player = this.mc.player;
            if (this.mc.screen == null) {
                if (player == null || !this.mc.isWindowActive()) {
                    return;
                }

                player.getCapability(AllomancerCapability.PLAYER_CAP).ifPresent(data -> {
                    if (extras) { // try one of the extra keybinds
                        for (int i = 0; i < PowersClientSetup.powers.length; i++) {
                            if (PowersClientSetup.powers[i].isDown()) {
                                ClientUtils.toggleBurn(Metal.getMetal(i), data);
                            }
                        }

                    } else { // normal keypress

                        int num_powers = data.getPowerCount();

                        if (num_powers == 0) {
                            return;
                        } else if (num_powers == 1) {
                            ClientUtils.toggleBurn(data.getPowers()[0], data);
                        } else {
                            this.mc.setScreen(new MetalSelectScreen());