Minecraft Modding: Enabling Your Custom Fluid To Cause Drowning

Background


Although Forge has provided a nice custom fluid system, there are several areas where it can be lacking in comparison with the built-in liquids, especially water. For example, water can push entities, it can extinguish fire, it can drown entities, and so forth.

The difficulty is that much of this code is not in the custom block class but rather in the Entity or EntityLivingBase classes and is hard-coded to refer to Material.WATER. So if you use your own material (and this is usually recommended as it provides ability to make custom MapColor as well as set other properties) none of those effects will happen.

So this tutorial gives you an way to make the drowning effect of water happen to entities, including players, who are submerged in your custom fluid.

This tutorial assumes you've already made a custom fluid block according to Jabelar's Custom Fluids Tutorial.

The Approach To Affect Air Supply


There are two steps required to implement the air supply system:

  1. Create a LivingUpdateEvent handling method that adjusts the air supply if an entity is in your fluid.
  2. Create a RenderGameOverlayEvent handling method to display the air supply if a player is in your fluid.
Step 1. Create A LivingUpdateEvent Handling Method That Adjusts The Air Supply

When I originally tried to code this, I thought it would be simple -- that I could simply handle the LivingUpdateEvent and set the entity air based on whether it was in the fluid's material. However, this didn't work because the vanilla living update code resets the air to 300 every time an entity is not in water. So I could set it but it would get immediately reset.

I then thought maybe I would come up with a separate air capability for entities and track that. That would work for the most part but would contain a possible bug if your custom fluid was ever connected to water and you moved in between them (although with a bit of work possibly that could be managed)

So I had to come up with a more comprehensive but honestly tedious way of solving the problem -- replace all the EntityLivingBase code with my own modified version that treats my fluids like water. 

Technically this is actually pretty easy -- simply copy the code into the LivingUpdateEvent and then cancel the event to prevent the vanilla code from running. However, it was trickier than that because many important fields and methods do not have public scope so by moving the code to my class it required some work to get it to compile. In particular I had to:
  • Replace this with the event.getEntityLiving()
  • Expand some methods that were not visible by copying their code in-line.
  • Replace some direct field accesses with getter methods where available.
  • Comment out or delete fields that were unused (Minecraft actually has a fair bit of orphan code that is now unused).
  • Use Java reflection for the remaining fields that were still visible. In Minecraft, because the modding is done on an de-obfuscated enviroment that has to be re-applied to the obfuscated build, you need to use the ReflectionHelper class and make sure you provide both the unobfuscated and obfuscated names of the fields and methods you are trying to access by reflection. I used the MCP Bot to look up the "SRG names" which are the obfuscated ones needed.
  • Replace the portion that checks for Material.WATER to also check for my custom fluid material(s).
Obviously the living update code is some of the most complex in the game so there is a lot of it. But the cool thing is I've done the work so you can start by simply copying this into a class that you have registered as event handler on the EVENT_BUS (see Jabelar's Event Handler Tutorial if you are not familiar with event handling).

Key Point: Basically just copy the part below into your registered event handling class and replace the ModMaterials.SLIME with your own material instance that you used for (each of your) custom fluids. 

public static Field activeItemStackUseCount = ReflectionHelper.findField(EntityLivingBase.class, "activeItemStackUseCount", "field_184628_bn");
public static Field handInventory = ReflectionHelper.findField(EntityLivingBase.class, "handInventory", "field_184630_bs");
public static Field armorArray = ReflectionHelper.findField(EntityLivingBase.class, "armorArray", "field_184631_bt");
public static Field ticksElytraFlying = ReflectionHelper.findField(EntityLivingBase.class, "ticksElytraFlying", "field_184629_bo");
public static Field rideCooldown = ReflectionHelper.findField(Entity.class, "rideCooldown", "field_184245_j");
public static Field portalCounter = ReflectionHelper.findField(Entity.class, "portalCounter", "field_82153_h");
public static Field inPortal = ReflectionHelper.findField(Entity.class, "inPortal", "field_71087_bX");
public static Field fire = ReflectionHelper.findField(Entity.class, "fire", "field_190534_ay");
public static Field prevBlockpos = ReflectionHelper.findField(EntityLivingBase.class, "prevBlockpos", "field_184620_bC");
public static Field firstUpdate = ReflectionHelper.findField(Entity.class, "firstUpdate", "field_70148_d");
public static Field attackingPlayer = ReflectionHelper.findField(EntityLivingBase.class, "attackingPlayer", "field_70717_bb");
public static Field recentlyHit = ReflectionHelper.findField(EntityLivingBase.class, "recentlyHit", "field_70718_bc");

public static Method setFlag = ReflectionHelper.findMethod(Entity.class, "setFlag", "func_70052_a", Integer.TYPE, Boolean.TYPE); 
public static Method getFlag = ReflectionHelper.findMethod(Entity.class, "getFlag", "func_70083_f", Integer.TYPE); 
public static Method decrementTimeUntilPortal = ReflectionHelper.findMethod(Entity.class, "decrementTimeUntilPortal", "func_184173_H", new Class[] {});
public static Method updatePotionEffects = ReflectionHelper.findMethod(EntityLivingBase.class, "updatePotionEffects", "func_70679_bo", new Class[] {});
public static Method onDeathUpdate = ReflectionHelper.findMethod(EntityLivingBase.class, "onDeathUpdate", "func_70609_aI", new Class[] {});

    @SuppressWarnings("unchecked")
@SubscribeEvent(priority=EventPriority.NORMAL, receiveCanceled=true)
    public void onEvent(LivingUpdateEvent event) throws IllegalArgumentException, IllegalAccessException
    {
    event.setCanceled(true);
   
    EntityLivingBase theEntity = event.getEntityLiving();
     
        // super.onUpdate() expanded
        if (!theEntity.world.isRemote)
        {
            try {
setFlag.invoke(theEntity, 6, theEntity.isGlowing());
} catch (InvocationTargetException e) {
e.printStackTrace();
}
        }

        // theEntity.onEntityUpdate();
        // onEntityUpdate() expanded
        theEntity.prevSwingProgress = theEntity.swingProgress;
        // super.onEntityUpdate();
        // super onEntityUpdage() expanded
        theEntity.world.profiler.startSection("entityBaseTick");

        if (theEntity.isRiding() && theEntity.getRidingEntity().isDead)
        {
            theEntity.dismountRidingEntity();
        }

        if (rideCooldown.getInt(theEntity) > 0)
        {
        rideCooldown.setInt(theEntity, rideCooldown.getInt(theEntity) - 1);
        }

        theEntity.prevDistanceWalkedModified = theEntity.distanceWalkedModified;
        theEntity.prevPosX = theEntity.posX;
        theEntity.prevPosY = theEntity.posY;
        theEntity.prevPosZ = theEntity.posZ;
        theEntity.prevRotationPitch = theEntity.rotationPitch;
        theEntity.prevRotationYaw = theEntity.rotationYaw;

        if (!theEntity.world.isRemote && theEntity.world instanceof WorldServer)
        {
            theEntity.world.profiler.startSection("portal");

            if (inPortal.getBoolean(theEntity))
            {
                MinecraftServer minecraftserver = theEntity.world.getMinecraftServer();

                if (minecraftserver.getAllowNether())
                {
                    if (!theEntity.isRiding())
                    {
                        int i = theEntity.getMaxInPortalTime();

                        portalCounter.setInt(theEntity, portalCounter.getInt(theEntity) + 1);
                        if (portalCounter.getInt(theEntity) >= i)
                        {
                            portalCounter.set(theEntity, i);
                            theEntity.timeUntilPortal = theEntity.getPortalCooldown();
                            int j;

                            if (theEntity.world.provider.getDimensionType().getId() == -1)
                            {
                                j = 0;
                            }
                            else
                            {
                                j = -1;
                            }

                            theEntity.changeDimension(j);
                        }
                    }

                    inPortal.set(theEntity, false);
                }
            }
            else
            {
                if (portalCounter.getInt(theEntity) > 0)
                {
                    portalCounter.setInt(theEntity, portalCounter.getModifiers() - 4);
                }

                if (portalCounter.getInt(theEntity) < 0)
                {
                    portalCounter.setInt(theEntity, 0);
                }
            }

            try {
decrementTimeUntilPortal.invoke(theEntity, new Object[] {});
} catch (InvocationTargetException e) {
e.printStackTrace();
}
            theEntity.world.profiler.endSection();
        }

        theEntity.spawnRunningParticles();
        theEntity.handleWaterMovement();

        if (theEntity.world.isRemote)
        {
            theEntity.extinguish();
        }
        else if (fire.getInt(theEntity) > 0)
        {
            if (theEntity.isImmuneToFire())
            {
                fire.setInt(theEntity, fire.getInt(theEntity) - 4);

                if (fire.getInt(theEntity) < 0)
                {
                    theEntity.extinguish();
                }
            }
            else
            {
                if (fire.getInt(theEntity) % 20 == 0)
                {
                    theEntity.attackEntityFrom(DamageSource.ON_FIRE, 1.0F);
                }

                theEntity.setFire(fire.getInt(theEntity));
            }
        }

        if (theEntity.isInLava())
        {
        // setOnFireFromLava expanded
            // theEntity.setOnFireFromLava();
            if (!theEntity.isImmuneToFire())
            {
                theEntity.attackEntityFrom(DamageSource.LAVA, 4.0F);
                theEntity.setFire(15);
            }

            theEntity.fallDistance *= 0.5F;
        }

        if (theEntity.posY < -64.0D)
        {
        // onDeathUpdate expanded
            theEntity.attackEntityFrom(DamageSource.OUT_OF_WORLD, 4.0F);
        }

        if (!theEntity.world.isRemote)
        {
            try {
setFlag.invoke(theEntity, 0, fire.getInt(theEntity) > 0);
} catch (InvocationTargetException e) {
e.printStackTrace();
}
        }

        firstUpdate.setBoolean(theEntity, false);
        theEntity.world.profiler.endSection();
        theEntity.world.profiler.startSection("livingEntityBaseTick");
        boolean flag = theEntity instanceof EntityPlayer;

        if (theEntity.isEntityAlive())
        {
            if (theEntity.isEntityInsideOpaqueBlock())
            {
                theEntity.attackEntityFrom(DamageSource.IN_WALL, 1.0F);
            }
            else if (flag && !theEntity.world.getWorldBorder().contains(theEntity.getEntityBoundingBox()))
            {
                double d0 = theEntity.world.getWorldBorder().getClosestDistance(theEntity) + theEntity.world.getWorldBorder().getDamageBuffer();

                if (d0 < 0.0D)
                {
                    double d1 = theEntity.world.getWorldBorder().getDamageAmount();

                    if (d1 > 0.0D)
                    {
                        theEntity.attackEntityFrom(DamageSource.IN_WALL, Math.max(1, MathHelper.floor(-d0 * d1)));
                    }
                }
            }
        }

        if (theEntity.isImmuneToFire() || theEntity.world.isRemote)
        {
            theEntity.extinguish();
        }

        boolean flag1 = flag && ((EntityPlayer)theEntity).capabilities.disableDamage;

        if (theEntity.isEntityAlive())
        {
        /*
        * Modified this so that custom fluids can suffocate
        */
            if (!theEntity.isInsideOfMaterial(Material.WATER) && !theEntity.isInsideOfMaterial(ModMaterials.SLIME))
            {
                theEntity.setAir(300);
            }
            else
            {
                if (!theEntity.canBreatheUnderwater() && !theEntity.isPotionActive(MobEffects.WATER_BREATHING) && !flag1)
                {
                // DEBUG
                System.out.println("Entity "+theEntity.getName()+" is drowning in fluid");
               
                // decreaseAirSupply() expanded
                    theEntity.setAir(EnchantmentHelper.getRespirationModifier(theEntity) > 0 && theEntity.getRNG().nextInt(EnchantmentHelper.getRespirationModifier(theEntity) + 1) > 0 ? theEntity.getAir() : theEntity.getAir() - 1);

                    if (theEntity.getAir() == -20)
                    {
                        theEntity.setAir(0);

                        for (int i = 0; i < 8; ++i)
                        {
                            float f2 = theEntity.getRNG().nextFloat() - theEntity.getRNG().nextFloat();
                            float f = theEntity.getRNG().nextFloat() - theEntity.getRNG().nextFloat();
                            float f1 = theEntity.getRNG().nextFloat() - theEntity.getRNG().nextFloat();
                            theEntity.world.spawnParticle(EnumParticleTypes.WATER_BUBBLE, theEntity.posX + f2, theEntity.posY + f, theEntity.posZ + f1, theEntity.motionX, theEntity.motionY, theEntity.motionZ);
                        }

                        theEntity.attackEntityFrom(DamageSource.DROWN, 2.0F);
                    }
                }

                if (!theEntity.world.isRemote && theEntity.isRiding() && theEntity.getRidingEntity() != null && theEntity.getRidingEntity().shouldDismountInWater(theEntity))
                {
                    theEntity.dismountRidingEntity();
                }
            }

            if (!theEntity.world.isRemote)
            {
                BlockPos blockpos = new BlockPos(theEntity);

                if (!Objects.equal(prevBlockpos.get(theEntity), blockpos))
                {
                    prevBlockpos.set(theEntity, blockpos);
                    // theEntity.frostWalk(blockpos);
                    // frostWalk() expanded
                    int i = EnchantmentHelper.getMaxEnchantmentLevel(Enchantments.FROST_WALKER, theEntity);

                    if (i > 0)
                    {
                        EnchantmentFrostWalker.freezeNearby(theEntity, theEntity.world, blockpos, i);
                    }
                }
            }
        }

        if (theEntity.isEntityAlive() && theEntity.isWet())
        {
            theEntity.extinguish();
        }

        theEntity.prevCameraPitch = theEntity.cameraPitch;

        if (theEntity.hurtTime > 0)
        {
            --theEntity.hurtTime;
        }

        if (theEntity.hurtResistantTime > 0 && !(theEntity instanceof EntityPlayerMP))
        {
            --theEntity.hurtResistantTime;
        }

        if (theEntity.getHealth() <= 0.0F)
        {
            try {
onDeathUpdate.invoke(theEntity, new Object[] {});
} catch (InvocationTargetException e) {
e.printStackTrace();
}
        }

        if (recentlyHit.getInt(theEntity) > 0)
        {
            recentlyHit.setInt(theEntity, recentlyHit.getInt(theEntity) - 1);;
        }
        else
        {
            attackingPlayer.set(theEntity, null);
        }

        if (theEntity.getLastAttackedEntity() != null && !theEntity.getLastAttackedEntity().isEntityAlive())
        {
            attackingPlayer.set(theEntity, null);
        }

        if (theEntity.getRevengeTarget() != null)
        {
            if (!theEntity.getRevengeTarget().isEntityAlive())
            {
                theEntity.setRevengeTarget((EntityLivingBase)null);
            }
            else if (theEntity.ticksExisted - theEntity.getRevengeTimer() > 100)
            {
                theEntity.setRevengeTarget((EntityLivingBase)null);
            }
        }

        try {
updatePotionEffects.invoke(theEntity, new Object[] {});
} catch (InvocationTargetException e1) {
e1.printStackTrace();
}
        // theEntity.prevMovedDistance = theEntity.movedDistance;
        theEntity.prevRenderYawOffset = theEntity.renderYawOffset;
        theEntity.prevRotationYawHead = theEntity.rotationYawHead;
        theEntity.prevRotationYaw = theEntity.rotationYaw;
        theEntity.prevRotationPitch = theEntity.rotationPitch;
        theEntity.world.profiler.endSection();

        // updateActiveHand() method expanded
        if (theEntity.isHandActive())
        {
            ItemStack itemstack = theEntity.getHeldItem(theEntity.getActiveHand());

            if (itemstack == theEntity.getActiveItemStack())
            {
                if (!theEntity.getActiveItemStack().isEmpty())
                {

                    try {
activeItemStackUseCount.setInt(theEntity, net.minecraftforge.event.ForgeEventFactory.onItemUseTick(theEntity, theEntity.getActiveItemStack(), activeItemStackUseCount.getInt(theEntity)));
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
                    try {
if (activeItemStackUseCount.getInt(theEntity) > 0)
   theEntity.getActiveItemStack().getItem().onUsingTick(theEntity.getActiveItemStack(), theEntity, activeItemStackUseCount.getInt(theEntity));
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
                }

                if (theEntity.getItemInUseCount() <= 25 && theEntity.getItemInUseCount() % 4 == 0)
                {
                    // theEntity.updateItemUse(theEntity.getActiveItemStack(), 5);
                    // updateItemUse() expanded
                    ItemStack stack = theEntity.getActiveItemStack();
                    int eatingParticleCount = 5;
                    if (!stack.isEmpty() && theEntity.isHandActive())
                    {
                        if (stack.getItemUseAction() == EnumAction.DRINK)
                        {
                            theEntity.playSound(SoundEvents.ENTITY_GENERIC_DRINK, 0.5F, theEntity.world.rand.nextFloat() * 0.1F + 0.9F);
                        }

                        if (stack.getItemUseAction() == EnumAction.EAT)
                        {
                            for (int i = 0; i < eatingParticleCount; ++i)
                            {
                                Vec3d vec3d = new Vec3d((theEntity.world.rand.nextFloat() - 0.5D) * 0.1D, Math.random() * 0.1D + 0.1D, 0.0D);
                                vec3d = vec3d.rotatePitch(-theEntity.rotationPitch * 0.017453292F);
                                vec3d = vec3d.rotateYaw(-theEntity.rotationYaw * 0.017453292F);
                                double d0 = (-theEntity.world.rand.nextFloat()) * 0.6D - 0.3D;
                                Vec3d vec3d1 = new Vec3d((theEntity.world.rand.nextFloat() - 0.5D) * 0.3D, d0, 0.6D);
                                vec3d1 = vec3d1.rotatePitch(-theEntity.rotationPitch * 0.017453292F);
                                vec3d1 = vec3d1.rotateYaw(-theEntity.rotationYaw * 0.017453292F);
                                vec3d1 = vec3d1.addVector(theEntity.posX, theEntity.posY + theEntity.getEyeHeight(), theEntity.posZ);

                                if (stack.getHasSubtypes())
                                {
                                    theEntity.world.spawnParticle(EnumParticleTypes.ITEM_CRACK, vec3d1.x, vec3d1.y, vec3d1.z, vec3d.x, vec3d.y + 0.05D, vec3d.z, Item.getIdFromItem(stack.getItem()), stack.getMetadata());
                                }
                                else
                                {
                                    theEntity.world.spawnParticle(EnumParticleTypes.ITEM_CRACK, vec3d1.x, vec3d1.y, vec3d1.z, vec3d.x, vec3d.y + 0.05D, vec3d.z, Item.getIdFromItem(stack.getItem()));
                                }
                            }

                            theEntity.playSound(SoundEvents.ENTITY_GENERIC_EAT, 0.5F + 0.5F * theEntity.world.rand.nextInt(2), (theEntity.world.rand.nextFloat() - theEntity.world.rand.nextFloat()) * 0.2F + 1.0F);
                        }
                    }                 
                }
                try {
activeItemStackUseCount.setInt(theEntity, activeItemStackUseCount.getInt(theEntity) - 1);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
                try {
if (activeItemStackUseCount.getInt(theEntity) - 1 <= 0 && !theEntity.world.isRemote)
{
   // theEntity.onItemUseFinish();
// onITemUseFinish() expanded
   if (!theEntity.getActiveItemStack().isEmpty() && theEntity.isHandActive())
   {
    {
    // theEntity.updateItemUse(theEntity.getActiveItemStack(), 16);
    // updateItemUse expanded
           ItemStack stack = theEntity.getActiveItemStack();
           int eatingParticleCount = 16;
           if (!stack.isEmpty() && theEntity.isHandActive())
           {
               if (stack.getItemUseAction() == EnumAction.DRINK)
               {
                   theEntity.playSound(SoundEvents.ENTITY_GENERIC_DRINK, 0.5F, theEntity.world.rand.nextFloat() * 0.1F + 0.9F);
               }

               if (stack.getItemUseAction() == EnumAction.EAT)
               {
                   for (int i = 0; i < eatingParticleCount; ++i)
                   {
                       Vec3d vec3d = new Vec3d((theEntity.world.rand.nextFloat() - 0.5D) * 0.1D, Math.random() * 0.1D + 0.1D, 0.0D);
                       vec3d = vec3d.rotatePitch(-theEntity.rotationPitch * 0.017453292F);
                       vec3d = vec3d.rotateYaw(-theEntity.rotationYaw * 0.017453292F);
                       double d0 = (-theEntity.world.rand.nextFloat()) * 0.6D - 0.3D;
                       Vec3d vec3d1 = new Vec3d((theEntity.world.rand.nextFloat() - 0.5D) * 0.3D, d0, 0.6D);
                       vec3d1 = vec3d1.rotatePitch(-theEntity.rotationPitch * 0.017453292F);
                       vec3d1 = vec3d1.rotateYaw(-theEntity.rotationYaw * 0.017453292F);
                       vec3d1 = vec3d1.addVector(theEntity.posX, theEntity.posY + theEntity.getEyeHeight(), theEntity.posZ);

                       if (stack.getHasSubtypes())
                       {
                           theEntity.world.spawnParticle(EnumParticleTypes.ITEM_CRACK, vec3d1.x, vec3d1.y, vec3d1.z, vec3d.x, vec3d.y + 0.05D, vec3d.z, Item.getIdFromItem(stack.getItem()), stack.getMetadata());
                       }
                       else
                       {
                           theEntity.world.spawnParticle(EnumParticleTypes.ITEM_CRACK, vec3d1.x, vec3d1.y, vec3d1.z, vec3d.x, vec3d.y + 0.05D, vec3d.z, Item.getIdFromItem(stack.getItem()));
                       }
                   }

                   theEntity.playSound(SoundEvents.ENTITY_GENERIC_EAT, 0.5F + 0.5F * theEntity.world.rand.nextInt(2), (theEntity.world.rand.nextFloat() - theEntity.world.rand.nextFloat()) * 0.2F + 1.0F);
               }
           }                 

    }
    ItemStack itemstack2 = theEntity.getActiveItemStack().onItemUseFinish(theEntity.world, theEntity);
       itemstack2 = net.minecraftforge.event.ForgeEventFactory.onItemUseFinish(theEntity, theEntity.getActiveItemStack(), theEntity.getItemInUseCount(), itemstack2);
       theEntity.setHeldItem(theEntity.getActiveHand(), itemstack2);
       theEntity.resetActiveHand();
   }

}
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
            }
            else
            {
                theEntity.resetActiveHand();
            }
        }


        if (!theEntity.world.isRemote)
        {
            int i = theEntity.getArrowCountInEntity();

            if (i > 0)
            {
                if (theEntity.arrowHitTimer <= 0)
                {
                    theEntity.arrowHitTimer = 20 * (30 - i);
                }

                --theEntity.arrowHitTimer;

                if (theEntity.arrowHitTimer <= 0)
                {
                    theEntity.setArrowCountInEntity(i - 1);
                }
            }

            for (EntityEquipmentSlot entityequipmentslot : EntityEquipmentSlot.values())
            {
                ItemStack itemstack = ItemStack.EMPTY;

                switch (entityequipmentslot.getSlotType())
                {
                    case HAND:
try {
itemstack = ((NonNullList)handInventory.get(theEntity)).get(entityequipmentslot.getIndex());
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
                        break;
                    case ARMOR:
try {
itemstack = ((NonNullList)armorArray.get(theEntity)).get(entityequipmentslot.getIndex());
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
                        break;
                    default:
                        continue;
                }

                ItemStack itemstack1 = theEntity.getItemStackFromSlot(entityequipmentslot);

                if (!ItemStack.areItemStacksEqual(itemstack, itemstack1))
                {
                    if (!ItemStack.areItemStacksEqualUsingNBTShareTag(itemstack1, itemstack))
                    ((WorldServer)theEntity.world).getEntityTracker().sendToTracking(theEntity, new SPacketEntityEquipment(theEntity.getEntityId(), entityequipmentslot, itemstack1));
                    net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.entity.living.LivingEquipmentChangeEvent(theEntity, entityequipmentslot, itemstack, itemstack1));

                    if (!itemstack.isEmpty())
                    {
                        theEntity.getAttributeMap().removeAttributeModifiers(itemstack.getAttributeModifiers(entityequipmentslot));
                    }

                    if (!itemstack1.isEmpty())
                    {
                        theEntity.getAttributeMap().applyAttributeModifiers(itemstack1.getAttributeModifiers(entityequipmentslot));
                    }

                    switch (entityequipmentslot.getSlotType())
                    {
                        case HAND:
                            ((NonNullList)handInventory.get(theEntity)).set(entityequipmentslot.getIndex(), itemstack1.isEmpty() ? ItemStack.EMPTY : itemstack1.copy());
                            break;
                        case ARMOR:
                            ((NonNullList)armorArray.get(theEntity)).set(entityequipmentslot.getIndex(), itemstack1.isEmpty() ? ItemStack.EMPTY : itemstack1.copy());
                    }
                }
            }

            if (theEntity.ticksExisted % 20 == 0)
            {
                theEntity.getCombatTracker().reset();
            }

            if (!theEntity.isGlowing())
            {
                try {
if (((boolean)getFlag.invoke(theEntity, 6)) != theEntity.isPotionActive(MobEffects.GLOWING))
{
   setFlag.invoke(theEntity, 6, theEntity.isPotionActive(MobEffects.GLOWING));
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}
            }
        }

        theEntity.onLivingUpdate();
        double d0 = theEntity.posX - theEntity.prevPosX;
        double d1 = theEntity.posZ - theEntity.prevPosZ;
        float f3 = (float)(d0 * d0 + d1 * d1);
        float f4 = theEntity.renderYawOffset;
        float f5 = 0.0F;
        if (f3 > 0.0025000002F)
        {
            f5 = (float)Math.sqrt(f3) * 3.0F;
            float f1 = (float)MathHelper.atan2(d1, d0) * (180F / (float)Math.PI) - 90.0F;
            float f2 = MathHelper.abs(MathHelper.wrapDegrees(theEntity.rotationYaw) - f1);

            if (95.0F < f2 && f2 < 265.0F)
            {
                f4 = f1 - 180.0F;
            }
            else
            {
                f4 = f1;
            }
        }

        if (theEntity.swingProgress > 0.0F)
        {
            f4 = theEntity.rotationYaw;
        }

        if (!theEntity.onGround)
        {
        }

        // theEntity.onGroundSpeedFactor += (f - theEntity.onGroundSpeedFactor) * 0.3F;
        theEntity.world.profiler.startSection("headTurn");
        // f5 = theEntity.updateDistance(f4, f5);
        // updateDistance expanded
        float p_110146_1_ = f4;
        float p_110146_2_ = f5;
        theEntity.renderYawOffset += MathHelper.wrapDegrees(p_110146_1_ - theEntity.renderYawOffset) * 0.3F;
        float f1 = MathHelper.wrapDegrees(theEntity.rotationYaw - theEntity.renderYawOffset);
        boolean flagx = f1 < -90.0F || f1 >= 90.0F;

        if (f1 < -75.0F)
        {
            f1 = -75.0F;
        }

        if (f1 >= 75.0F)
        {
            f1 = 75.0F;
        }

        theEntity.renderYawOffset = theEntity.rotationYaw - f1;

        if (f1 * f1 > 2500.0F)
        {
            theEntity.renderYawOffset += f1 * 0.2F;
        }

        if (flagx)
        {
            p_110146_2_ *= -1.0F;
        }

        f5 = p_110146_2_;

        theEntity.world.profiler.endSection();
        theEntity.world.profiler.startSection("rangeChecks");

        while (theEntity.rotationYaw - theEntity.prevRotationYaw < -180.0F)
        {
            theEntity.prevRotationYaw -= 360.0F;
        }

        while (theEntity.rotationYaw - theEntity.prevRotationYaw >= 180.0F)
        {
            theEntity.prevRotationYaw += 360.0F;
        }

        while (theEntity.renderYawOffset - theEntity.prevRenderYawOffset < -180.0F)
        {
            theEntity.prevRenderYawOffset -= 360.0F;
        }

        while (theEntity.renderYawOffset - theEntity.prevRenderYawOffset >= 180.0F)
        {
            theEntity.prevRenderYawOffset += 360.0F;
        }

        while (theEntity.rotationPitch - theEntity.prevRotationPitch < -180.0F)
        {
            theEntity.prevRotationPitch -= 360.0F;
        }

        while (theEntity.rotationPitch - theEntity.prevRotationPitch >= 180.0F)
        {
            theEntity.prevRotationPitch += 360.0F;
        }

        while (theEntity.rotationYawHead - theEntity.prevRotationYawHead < -180.0F)
        {
            theEntity.prevRotationYawHead -= 360.0F;
        }

        while (theEntity.rotationYawHead - theEntity.prevRotationYawHead >= 180.0F)
        {
            theEntity.prevRotationYawHead += 360.0F;
        }

        theEntity.world.profiler.endSection();
        // theEntity.movedDistance += f5;

        if (theEntity.isElytraFlying())
        {
        try {
ticksElytraFlying.setInt(theEntity, ticksElytraFlying.getInt(theEntity) + 1);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
        }
        else
        {
            try {
ticksElytraFlying.setInt(theEntity, 0);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
        }
    }


Step #2. Create A RenderGameOverlayEvent Handler To Display The Air Supply

The vanilla air supply is drawn by the GuiIngame class. But like other things related to liquids, it is hard-coded to look for Material.WATER. The good news is there is a Forge event, RenderGameOverlayEvent that is specifically designed to allow you to modify the GuiIngame stuff. 

You basically have two options -- you can cancel the event to prevent the vanilla HUD and entirely replace it with your own, or you can add to it. Since the air supply bar is something that isn't normally on the screen and would never already be on when we need it (you can't be in water and your custom fluid at the same time) I chose to not cancel it and simply add the bar when needed.

To do this was very easy. I just copied the vanilla code from GuiIngame and modified it to check for the material instances I used for my fluids instead of checking for water.

The result was as follows:
   
    @SideOnly(Side.CLIENT)
    @SubscribeEvent(priority=EventPriority.HIGHEST, receiveCanceled=true)
    public void onEvent(RenderGameOverlayEvent event)
    {
    Minecraft mc = Minecraft.getMinecraft();
    GuiIngame ingameGUI = mc.ingameGUI;
        ScaledResolution scaledRes = event.getResolution();
     
       if (mc.getRenderViewEntity() instanceof EntityPlayer)
        {
            EntityPlayer entityplayer = (EntityPlayer)mc.getRenderViewEntity();

            int airIndicatorX = scaledRes.getScaledWidth() / 2 + 91;
            int airIndicatorBottom = scaledRes.getScaledHeight() - 39;
            int airIndicatorTop = airIndicatorBottom - 10;

            if (entityplayer.isInsideOfMaterial(Material.WATER) || entityplayer.isInsideOfMaterial(ModMaterials.SLIME))
            {
                int airAmount = mc.player.getAir();
                int airLostPercent = MathHelper.ceil((airAmount - 2) * 10.0D / 300.0D);
                int airLeftPercent = MathHelper.ceil(airAmount * 10.0D / 300.0D) - airLostPercent;

                for (int airUnitIndex = 0; airUnitIndex < airLostPercent + airLeftPercent; ++airUnitIndex)
                {
                    if (airUnitIndex < airLostPercent)
                    {
                        ingameGUI.drawTexturedModalRect(airIndicatorX - airUnitIndex * 8 - 9, airIndicatorTop, 16, 18, 9, 9);
                    }
                    else
                    {
                        ingameGUI.drawTexturedModalRect(airIndicatorX - airUnitIndex * 8 - 9, airIndicatorTop, 25, 18, 9, 9);
                    }
                }
            }
        }
    }        

It should be fairly easy to understand. If the player is in the fluid material it is just using the instance of ingameGUI from the client Minecraft instance and drawing a bunch of little textured rectangles depending on how much air is left.

Testing Your Mod


So you should be able to test this in game by using a bucket of your fluid, digging a hole deep enough to stand in, then use the bucket to place the fluid to flow into the hole. Then make sure you are in survival mode (you can't drown in creative) and submerge yourself entirely in the fluid. You should see the air supply appear and start to go down until you start taking damage and eventually die by drowning. 

Conclusion


Replacing the entity behavior can be tough if there isn't a Forge event already intended for that purpose. However, the good news in this case is I did most of the work for you. For most part you can copy my stuff directly, but I always encourage people to go through what they copy and try to understand it. 

Happy modding!

3 comments:

  1. I had some trouble with the overlay. The texture did not show and the air icons showed when in creative and inside the fluid.
    I had to add the following from vanilla code in the RenderGameOverlayEvent code

    mc.getTextureManager().bindTexture(ICONS);

    and then enclose the
    if (mc.getRenderViewEntity() instanceof EntityPlayer)

    inside this if
    if (mc.playerController.shouldDrawHUD())

    ReplyDelete
  2. Oh, and also add
    GlStateManager.enableBlend();
    before
    mc.getTextureManager().bindTexture(ICONS);

    ReplyDelete
  3. A setup wizard guides you through the idm full crack installation.








    ReplyDelete