(function DuckEntity() {
    ModAPI.meta.title("Duck Mod 🦆");
    ModAPI.meta.version("v1");
    ModAPI.meta.description("adds ducks to the game");
    ModAPI.meta.credits("By ZXMushroom63");
    function waitForRenderManager() {
        return new Promise((res, rej)=>{
            function check() {
                if (ModAPI.mc.renderManager) {
                    res();
                } else {
                    setTimeout(check, 1/20);
                }
            }
            check();
        });
    }
    function registerEntity() {
        ModAPI.hooks.methods.jl_String_format = ModAPI.hooks.methods.nlev_HString_format; //temporary thing to fix an issue in eaglercraft
        // Utils
        function AITask(name, length) {
            return ModAPI.reflect.getClassById("net.minecraft.entity.ai." + name).constructors.find(x => x.length === length);
        }
        const ResourceLocation = ModAPI.reflect.getClassByName("ResourceLocation").constructors.find(x => x.length === 1);
        const EntityPlayer = ModAPI.reflect.getClassByName("EntityPlayer");
        const GlStateManager = Object.fromEntries(Object.values(ModAPI.reflect.getClassByName("GlStateManager").staticMethods).map(x => [x.methodNameShort, x.method]));
        const SharedMonsterAttributes = ModAPI.reflect.getClassByName("SharedMonsterAttributes").staticVariables;

        // START CUSTOM ENTITY
        var entityClass = ModAPI.reflect.getClassById("net.minecraft.entity.passive.EntityAnimal");
        var entitySuper = ModAPI.reflect.getSuper(entityClass, (x) => x.length === 2);
        var nme_EntityDuck = function nme_EntityDuck($worldIn) {
            entitySuper(this, $worldIn);
            this.wrapped ||= ModAPI.util.wrap(this).getCorrective();
            this.wrapped.setSize(0.4, 0.7);
            this.wrapped.tasks.addTask(0, AITask("EntityAISwimming", 1)(this));
            this.wrapped.tasks.addTask(1, AITask("EntityAIPanic", 2)(this, 1.9));
            this.wrapped.tasks.addTask(2, AITask("EntityAIMate", 2)(this, 1.0));
            this.wrapped.tasks.addTask(3, AITask("EntityAITempt", 4)(this, 1.5, ModAPI.items.bread.getRef(), 0)); //won't cause a problem as the bread is obtained when the entity is constructed.
            this.wrapped.tasks.addTask(4, AITask("EntityAIFollowParent", 2)(this, 1.2));
            this.wrapped.tasks.addTask(5, AITask("EntityAIWander", 2)(this, 1.1));
            this.wrapped.tasks.addTask(6, AITask("EntityAIWatchClosest", 3)(this, ModAPI.util.asClass(EntityPlayer.class), 6));
            this.wrapped.tasks.addTask(7, AITask("EntityAILookIdle", 1)(this));
        }
        ModAPI.reflect.prototypeStack(entityClass, nme_EntityDuck);
        nme_EntityDuck.prototype.$getEyeHeight = function () {
            this.wrapped ||= ModAPI.util.wrap(this).getCorrective();
            return this.wrapped.height;
        }

        const originalApplyEntityAttributes = nme_EntityDuck.prototype.$applyEntityAttributes;
        nme_EntityDuck.prototype.$applyEntityAttributes = function () {
            this.wrapped ||= ModAPI.util.wrap(this).getCorrective();
            originalApplyEntityAttributes.apply(this, []);
            this.wrapped.getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(5);
            this.wrapped.getEntityAttribute(SharedMonsterAttributes.movementSpeed).setBaseValue(0.25);
        }

        const originalLivingUpdate = nme_EntityDuck.prototype.$onLivingUpdate;
        nme_EntityDuck.prototype.$onLivingUpdate = function () {
            this.wrapped ||= ModAPI.util.wrap(this).getCorrective();
            originalLivingUpdate.apply(this, []);
            if (this.wrapped.isInWater()) {
                this.wrapped.motionY *= 0.5;
                this.wrapped.getEntityAttribute(SharedMonsterAttributes.movementSpeed).setBaseValue(1.4);
            } else {
                this.wrapped.getEntityAttribute(SharedMonsterAttributes.movementSpeed).setBaseValue(0.25);
            }
        }

        nme_EntityDuck.prototype.$getLivingSound = function () {
            return ModAPI.util.str("mob.duck.quack");
        }
        nme_EntityDuck.prototype.$getHurtSound = function () {
            return ModAPI.util.str("mob.duck.quack");
        }
        nme_EntityDuck.prototype.$getDeathSound = function () {
            return ModAPI.util.str("mob.duck.quack");
        }
        nme_EntityDuck.prototype.$playStepSound = function () {
            this.wrapped ||= ModAPI.util.wrap(this).getCorrective();
            this.wrapped.playSound(ModAPI.util.str("mob.duck.step"), 0.2, 1);
        }
        nme_EntityDuck.prototype.$getDropItem = function () {
            return ModAPI.items.feather.getRef();
        }
        nme_EntityDuck.prototype.$createChild = function (otherParent) {
            this.wrapped ||= ModAPI.util.wrap(this).getCorrective();
            return new nme_EntityDuck(this.wrapped.worldObj?.getRef() ?? null);
        }
        nme_EntityDuck.prototype.$isBreedingItem = function (itemstack) {
            return itemstack !== null && itemstack.$getItem() === ModAPI.items.bread.getRef();
        }
        // END CUSTOM ENTITY


        // START CUSTOM MODEL
        var modelChickenClass = ModAPI.reflect.getClassById("net.minecraft.client.model.ModelChicken");
        var modelChickenSuper = ModAPI.reflect.getSuper(modelChickenClass); //while super isn't used when extending this class, java implies the call.
        var nmcm_ModelDuck = function nmcm_ModelDuck() {
            modelChickenSuper(this);
        }
        ModAPI.reflect.prototypeStack(modelChickenClass, nmcm_ModelDuck);
        // END CUSTOM MODEL


        // START CUSTOM RENDERER
        var renderClass = ModAPI.reflect.getClassById("net.minecraft.client.renderer.entity.RenderLiving");
        var renderSuper = ModAPI.reflect.getSuper(renderClass, (x) => x.length === 4);
        const duckTextures = ResourceLocation(ModAPI.util.str("textures/entity/duck.png"));
        var nmcre_RenderDuck = function nmcre_RenderDuck(renderManager, modelBaseIn, shadowSizeIn) {
            renderSuper(this, renderManager, modelBaseIn, shadowSizeIn);
        }
        ModAPI.reflect.prototypeStack(renderClass, nmcre_RenderDuck);
        nmcre_RenderDuck.prototype.$getEntityTexture = function (entity) {
            return duckTextures;
        }
        nmcre_RenderDuck.prototype.$handleRotationFloat = function (entity, partialTicks) {
            entity = ModAPI.util.wrap(entity);
            if ((!entity.onGround) && (!entity.isInWater())) {
                return 2; //falling
            } else {
                return 0;
            }
        }

        const ID = ModAPI.keygen.entity("duck");
        ModAPI.reflect.getClassById("net.minecraft.entity.EntityList").staticMethods.addMapping0.method(
            ModAPI.util.asClass(nme_EntityDuck),
            {
                $createEntity: function ($worldIn) {
                    return new nme_EntityDuck($worldIn);
                }
            },
            ModAPI.util.str("Duck"),
            ID,
            0x5e3e2d, //egg base
            0x269166 //egg spots
        );

        const SpawnPlacementType = ModAPI.reflect.getClassById("net.minecraft.entity.EntityLiving$SpawnPlacementType").staticVariables;
        const ENTITY_PLACEMENTS = ModAPI.util.wrap(
            ModAPI.reflect.getClassById("net.minecraft.entity.EntitySpawnPlacementRegistry")
                .staticVariables.ENTITY_PLACEMENTS
        );
        ENTITY_PLACEMENTS.put(ModAPI.util.asClass(nme_EntityDuck), SpawnPlacementType.ON_GROUND);
        ModAPI.addEventListener('bootstrap', ()=>{
            const SpawnListEntry = ModAPI.reflect
                .getClassById("net.minecraft.world.biome.BiomeGenBase$SpawnListEntry")
                .constructors.find(x => x.length === 4);
            const BiomeGenSwamp = ModAPI.util.wrap(
                ModAPI.reflect.getClassById("net.minecraft.world.biome.BiomeGenBase")
                    .staticVariables.swampland
            );
            const BiomeGenRiver = ModAPI.util.wrap(
                ModAPI.reflect.getClassById("net.minecraft.world.biome.BiomeGenBase")
                    .staticVariables.river
            );
            const BiomeGenBeach = ModAPI.util.wrap(
                ModAPI.reflect.getClassById("net.minecraft.world.biome.BiomeGenBase")
                    .staticVariables.beach
            );
            const duckSpawnSwamp = SpawnListEntry(ModAPI.util.asClass(nme_EntityDuck), 22, 3, 5);
            const duckSpawnRiverBed = SpawnListEntry(ModAPI.util.asClass(nme_EntityDuck), 10, 5, 9);
            const duckSpawnBeach = SpawnListEntry(ModAPI.util.asClass(nme_EntityDuck), 24, 2, 3);
            BiomeGenSwamp.spawnableCreatureList.add(duckSpawnSwamp);
            BiomeGenRiver.spawnableCreatureList.add(duckSpawnRiverBed);
            BiomeGenBeach.spawnableCreatureList.add(duckSpawnBeach);
        });
        

        ModAPI.addEventListener("lib:asyncsink", async () => {
            AsyncSink.L10N.set("entity.Duck.name", "Duck");
        });


        return {
            EntityDuck: nme_EntityDuck,
            ModelDuck: nmcm_ModelDuck,
            RenderDuck: nmcre_RenderDuck,
            duckTextures: duckTextures
        }
    }

    ModAPI.dedicatedServer.appendCode(registerEntity);
    var data = registerEntity();

    ModAPI.addEventListener("lib:asyncsink", async () => {
        AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/textures/entity/duck.png", await (await fetch(
            "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAAgBAMAAABQs2O3AAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAACdQTFRFAAAALJBrKYdk1pcLp3UI4JEzL5pyaFY4AAAANa6BW0AjYUQlVTwhXohvWAAAAA10Uk5TAP///////////////y0EQa0AAADJSURBVHicY2RgVPj/gIGBwZhBkGEPkGb9zYACGBmYEv4tADKKj/CCFYT+3oCmgE3g1weQAQwQIzAUJLIyMACF9MA8kBEMASgqGIXYGl8ArbiPEEJToMbg8AdIz8OpIJGBFSTwCqcCIQYGfgb8VjDYoFqBChiBeDWYFYpbwSkwywy3ArDvGVyoZMVuVwJWrApDV7AaiQO05YwJuoJTSBwzbFbsQeK4UGzFIwaGf0wMDCE4rQDGGuN/BgZTnFZAFbiRZgUDATCqAAIA8Z45IRCQkrIAAAAASUVORK5CYII="
        )).arrayBuffer());
        AsyncSink.hideFile("resourcepacks/AsyncSinkLib/assets/minecraft/textures/entity/duck.png.mcmeta");

        await waitForRenderManager();

        AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/sounds/mob/duck/quack.ogg", await (await fetch(
            "data:audio/ogg;base64,[encoded data, 7824 characters]"
        )).arrayBuffer());
        AsyncSink.Audio.register("mob.duck.quack", AsyncSink.Audio.Category.ANIMALS, [
            {
                path: "sounds/mob/duck/quack.ogg",
                pitch: 1,
                volume: 1,
                streaming: false //use for large audio files
            }
        ]);
        AsyncSink.setFile("resourcepacks/AsyncSinkLib/assets/minecraft/sounds/mob/duck/step.ogg", await (await fetch(
            "data:audio/ogg;base64,[encoded data, 7472 characters]"
        )).arrayBuffer());
        AsyncSink.Audio.register("mob.duck.step", AsyncSink.Audio.Category.ANIMALS, [
            {
                path: "sounds/mob/duck/step.ogg",
                pitch: 1,
                volume: 1,
                streaming: false //use for large audio files
            }
        ]);

        ModAPI.mc.renderManager.entityRenderMap.put(ModAPI.util.asClass(data.EntityDuck), new data.RenderDuck(ModAPI.mc.renderManager.getRef(), new data.ModelDuck(), 0.3));
        ModAPI.promisify(ModAPI.mc.renderEngine.bindTexture)(data.duckTextures).then(() => {
            console.log("Loaded duck texture into cache.");
        });
    });
})();