diff --git a/src/main/java/biomesoplenty/common/entities/EntityPixie.java b/src/main/java/biomesoplenty/common/entities/EntityPixie.java new file mode 100644 index 000000000..83ae23e49 --- /dev/null +++ b/src/main/java/biomesoplenty/common/entities/EntityPixie.java @@ -0,0 +1,357 @@ +/******************************************************************************* + * Copyright 2015, the Biomes O' Plenty Team + * + * This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International Public License. + * + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/. + ******************************************************************************/ + +package biomesoplenty.common.entities; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import biomesoplenty.api.item.BOPItems; +import net.minecraft.entity.EntityFlying; +import net.minecraft.entity.ai.EntityAIBase; +import net.minecraft.entity.ai.EntityMoveHelper; +import net.minecraft.entity.monster.IMob; +import net.minecraft.item.Item; +import net.minecraft.util.AxisAlignedBB; +import net.minecraft.util.BlockPos; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumParticleTypes; +import net.minecraft.util.MathHelper; +import net.minecraft.world.EnumSkyBlock; +import net.minecraft.world.World; + +public class EntityPixie extends EntityFlying implements IMob { + + public EntityPixie(World worldIn) { + super(worldIn); + this.setSize(0.7F, 0.7F); + + this.moveHelper = new EntityPixie.PixieMoveHelper(); + this.tasks.addTask(3, new EntityPixie.AIPixieRandomFly()); + } + + @Override + protected void applyEntityAttributes() + { + super.applyEntityAttributes(); + // TODO: get right value here this.getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(10.0D); + } + + @Override + protected Item getDropItem() + { + return BOPItems.pixie_dust; + } + + @Override + public void onLivingUpdate() + { + super.onLivingUpdate(); + if (this.worldObj.isRemote) + { + for (int i = 0; i < 7; i++) + { + if (this.rand.nextInt(2)==0) + { + // TODO: add pixie particle BiomesOPlenty.proxy.spawnParticle("pixietrail", this.posX + (this.rand.nextDouble()) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height - (double)this.yOffset, this.posZ + (this.rand.nextDouble()) * (double)this.width); + this.worldObj.spawnParticle(EnumParticleTypes.PORTAL, this.posX + (this.rand.nextDouble() - 0.5D) * (double)this.width, this.posY + this.rand.nextDouble() * (double)this.height - 0.25D, this.posZ + (this.rand.nextDouble() - 0.5D) * (double)this.width, (this.rand.nextDouble() - 0.5D) * 2.0D, -this.rand.nextDouble(), (this.rand.nextDouble() - 0.5D) * 2.0D, new int[0]); + } + } + } + } + + + // Checks to make sure the light is not too bright where the mob is spawning + // This is same code as for EntitySkeleton + protected boolean isValidLightLevel() + { + BlockPos blockpos = new BlockPos(this.posX, this.getEntityBoundingBox().minY, this.posZ); + + if (this.worldObj.getLightFor(EnumSkyBlock.SKY, blockpos) > this.rand.nextInt(32)) + { + // TODO: not sure what's going on here... + return false; + } + else + { + int light = this.worldObj.getLightFromNeighbors(blockpos); + + // if it's thundering, force getSkylightSubtracted to 10 before calculating getLightFromNeighbors, then restore it + if (this.worldObj.isThundering()) + { + int oldSkyLightSubtracted = this.worldObj.getSkylightSubtracted(); + this.worldObj.setSkylightSubtracted(10); + light = this.worldObj.getLightFromNeighbors(blockpos); + this.worldObj.setSkylightSubtracted(oldSkyLightSubtracted); + } + + return light <= this.rand.nextInt(8); + } + } + + @Override + public boolean getCanSpawnHere() + { + return this.isValidLightLevel() && super.getCanSpawnHere(); + } + + + @Override + protected String getLivingSound() + { + return "biomesoplenty:mob.pixie.say"; + } + @Override + protected String getHurtSound() + { + return "biomesoplenty:mob.pixie.hurt"; + } + @Override + protected String getDeathSound() + { + return "biomesoplenty:mob.pixie.hurt"; + } + + + // TODO - move PixieMoveTargetPos and AIPixieRandomFly outside and implement in a more generic way, to be reused for pixie and wasp + + + + // Helper class representing a point in space that the pixie is targeting for some reason + class PixieMoveTargetPos + { + private EntityPixie pixie = EntityPixie.this; + + public double posX; + public double posY; + public double posZ; + public double distX; + public double distY; + public double distZ; + public double dist; + public double aimX; + public double aimY; + public double aimZ; + + public PixieMoveTargetPos() + { + this(0, 0, 0); + } + + public PixieMoveTargetPos(double posX, double posY, double posZ) + { + this.setTarget(posX, posY, posZ); + } + + public void setTarget(double posX, double posY, double posZ) + { + this.posX = posX; + this.posY = posY; + this.posZ = posZ; + this.refresh(); + } + + public void refresh() + { + this.distX = this.posX - this.pixie.posX; + this.distY = this.posY - this.pixie.posY; + this.distZ = this.posZ - this.pixie.posZ; + + this.dist = (double)MathHelper.sqrt_double(this.distX * this.distX + this.distY * this.distY + this.distZ * this.distZ); + + // (aimX,aimY,aimZ) is a unit vector in the direction we want to go + if (this.dist == 0.0D) + { + this.aimX = 0.0D; + this.aimY = 0.0D; + this.aimZ = 0.0D; + } + else + { + this.aimX = this.distX / this.dist; + this.aimY = this.distY / this.dist; + this.aimZ = this.distZ / this.dist; + } + } + + public boolean isBoxBlocked(AxisAlignedBB box) + { + return !this.pixie.worldObj.getCollidingBoundingBoxes(this.pixie, box).isEmpty(); + } + + // check nothing will collide with the pixie in the direction of aim, for howFar units (or until the destination - whichever is closer) + public boolean isPathClear(double howFar) + { + howFar = Math.min(howFar, this.dist); + AxisAlignedBB box = this.pixie.getEntityBoundingBox(); + for (double i = 0.5D; i < howFar; ++i) + { + // check there's nothing in the way + if (this.isBoxBlocked(box.offset(this.aimX * i, this.aimY * i, this.aimZ * i))) + { + return false; + } + } + if (this.isBoxBlocked(box.offset(this.aimX * howFar, this.aimY * howFar, this.aimZ * howFar))) + { + return false; + } + return true; + } + + } + + class PixieMoveHelper extends EntityMoveHelper + { + // EntityMoveHelper has the boolean 'update' which is set to true when the target is changed, and set to false when a bearing is set + // So it means 'the target has changed but we're not yet heading for it' + // We'll re-use it here with a slightly different interpretation + // Here it will mean 'has a target and not yet arrived' + + private EntityPixie pixie = EntityPixie.this; + private int courseChangeCooldown = 0; + private double closeEnough = 0.3D; + private PixieMoveTargetPos targetPos = new PixieMoveTargetPos(); + + public PixieMoveHelper() + { + super(EntityPixie.this); + } + + @Override + public void setMoveTo(double x, double y, double z, double speedIn) + { + super.setMoveTo(x,y,z,speedIn); + this.targetPos.setTarget(x, y, z); + } + + @Override + public void onUpdateMoveHelper() + { + // if we have arrived at the previous target, or we have no target to aim for, do nothing + if (!this.update) {return;} + + if (this.courseChangeCooldown-- > 0) { + // limit the rate at which we change course + return; + } + this.courseChangeCooldown += this.pixie.getRNG().nextInt(2) + 2; + + // update the target position + this.targetPos.refresh(); + + // accelerate the pixie towards the target + double acceleration = 0.1D; + this.pixie.motionX += this.targetPos.aimX * acceleration; + this.pixie.motionY += this.targetPos.aimY * acceleration; + this.pixie.motionZ += this.targetPos.aimZ * acceleration; + + // rotate to point at target + this.pixie.renderYawOffset = this.pixie.rotationYaw = -((float)Math.atan2(this.targetPos.distX, this.targetPos.distZ)) * 180.0F / (float)Math.PI; + + // abandon this movement if we have reached the target or there is no longer a clear path to the target + if (!this.targetPos.isPathClear(5.0D)) + { + //System.out.println("Abandoning move target - way is blocked" ); + this.update = false; + } else if (this.targetPos.dist < this.closeEnough) { + //System.out.println("Arrived (close enough) dist:"+this.targetPos.dist); + this.update = false; + } + } + + } + + // AI class for implementing the random flying behaviour + class AIPixieRandomFly extends EntityAIBase + { + private EntityPixie pixie = EntityPixie.this; + private PixieMoveTargetPos targetPos = new PixieMoveTargetPos(); + + public AIPixieRandomFly() + { + this.setMutexBits(1); + } + + // should we choose a new random destination for the pixie to fly to? + // yes, if the pixie doesn't already have a destination + @Override + public boolean shouldExecute() + { + return !this.pixie.getMoveHelper().isUpdating(); + } + + @Override + public boolean continueExecuting() {return false;} + + // choose a a new random destination for the pixie to fly to + @Override + public void startExecuting() + { + Random rand = this.pixie.getRNG(); + // pick a random nearby point and see if we can fly to it + if (this.tryGoingRandomDirection(rand, 6.0D)) {return;} + // pick a random closer point to fly to instead + if (this.tryGoingRandomDirection(rand, 2.0D)) {return;} + // try going straight along axes (try all 6 directions in random order) + List directions = Arrays.asList(EnumFacing.values()); + Collections.shuffle(directions); + for (EnumFacing facing : directions) + { + if (this.tryGoingAlongAxis(rand, facing, 1.0D)) {return;} + } + } + + + // note y direction has a slight downward bias to stop them flying too high + public boolean tryGoingRandomDirection(Random rand, double maxDistance) + { + double dirX = ((rand.nextDouble() * 2.0D - 1.0D) * maxDistance); + double dirY = ((rand.nextDouble() * 2.0D - 1.1D) * maxDistance); + double dirZ = ((rand.nextDouble() * 2.0D - 1.0D) * maxDistance); + return this.tryGoing(dirX, dirY, dirZ); + } + + public boolean tryGoingAlongAxis(Random rand, EnumFacing facing, double maxDistance) + { + double dirX = 0.0D; + double dirY = 0.0D; + double dirZ = 0.0D; + switch (facing.getAxis()) + { + case X: + dirX = rand.nextDouble() * facing.getAxisDirection().getOffset() * maxDistance; + break; + case Y: + dirY = rand.nextDouble() * facing.getAxisDirection().getOffset() * maxDistance; + break; + case Z: default: + dirZ = rand.nextDouble() * facing.getAxisDirection().getOffset() * maxDistance; + break; + } + return this.tryGoing(dirX, dirY, dirZ); + } + + public boolean tryGoing(double dirX, double dirY, double dirZ) + { + //System.out.println("("+dirX+","+dirY+","+dirZ+")"); + this.targetPos.setTarget(this.pixie.posX + dirX, this.pixie.posY + dirY, this.pixie.posZ + dirZ); + //System.out.println("Testing random move target distance:"+this.targetPos.dist+" direction:("+this.targetPos.aimX+","+this.targetPos.aimY+","+this.targetPos.aimZ+")"); + boolean result = this.targetPos.isPathClear(5.0F); + if (result) + { + this.pixie.getMoveHelper().setMoveTo(this.targetPos.posX, this.targetPos.posY, this.targetPos.posZ, 1.0D); + } + return result; + } + } + + +} diff --git a/src/main/java/biomesoplenty/common/entities/ModelPixie.java b/src/main/java/biomesoplenty/common/entities/ModelPixie.java new file mode 100644 index 000000000..775797a70 --- /dev/null +++ b/src/main/java/biomesoplenty/common/entities/ModelPixie.java @@ -0,0 +1,66 @@ +package biomesoplenty.common.entities; + +import net.minecraft.client.model.ModelBase; +import net.minecraft.client.model.ModelRenderer; +import net.minecraft.entity.Entity; +import net.minecraft.util.MathHelper; + +public class ModelPixie extends ModelBase +{ + //fields + ModelRenderer Body; + ModelRenderer LeftWing; + ModelRenderer RightWing; + + public ModelPixie() + { + textureWidth = 64; + textureHeight = 32; + + Body = new ModelRenderer(this, 0, 0); + Body.addBox(0F, 0F, 0F, 4, 4, 4); + Body.setRotationPoint(-2F, 16F, -2F); + Body.setTextureSize(64, 32); + Body.mirror = true; + setRotation(Body, 0F, 0F, 0F); + LeftWing = new ModelRenderer(this, 32, 0); + LeftWing.addBox(0F, 0F, -1F, 0, 4, 7); + LeftWing.setRotationPoint(2F, 15F, 2F); + LeftWing.setTextureSize(64, 32); + LeftWing.mirror = true; + setRotation(LeftWing, 0F, 0F, 0F); + RightWing = new ModelRenderer(this, 50, 0); + RightWing.addBox(0F, 0F, -1F, 0, 4, 7); + RightWing.setRotationPoint(-2F, 15F, 2F); + RightWing.setTextureSize(64, 32); + RightWing.mirror = true; + setRotation(RightWing, 0F, 0F, 0F); + } + + @Override + public void render(Entity entity, float f, float f1, float f2, float f3, float f4, float f5) + { + super.render(entity, f, f1, f2, f3, f4, f5); + setRotationAngles(f, f1, f2, f3, f4, f5, entity); + Body.render(f5); + LeftWing.render(f5); + RightWing.render(f5); + } + + private void setRotation(ModelRenderer model, float x, float y, float z) + { + model.rotateAngleX = x; + model.rotateAngleY = y; + model.rotateAngleZ = z; + } + + @Override + public void setRotationAngles(float f, float f1, float f2, float f3, float f4, float f5, Entity entity) + { + super.setRotationAngles(f, f1, f2, f3, f4, f5, entity); + + RightWing.rotateAngleY = -(MathHelper.cos(f2 * 1.7F) * (float)Math.PI * 0.5F); + LeftWing.rotateAngleY = MathHelper.cos(f2 * 1.7F) * (float)Math.PI * 0.5F; + } + +} \ No newline at end of file diff --git a/src/main/java/biomesoplenty/common/entities/RenderPixie.java b/src/main/java/biomesoplenty/common/entities/RenderPixie.java new file mode 100644 index 000000000..6b1c62e00 --- /dev/null +++ b/src/main/java/biomesoplenty/common/entities/RenderPixie.java @@ -0,0 +1,54 @@ +package biomesoplenty.common.entities; + +import net.minecraft.client.renderer.entity.RenderLiving; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.entity.Entity; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; + +@SideOnly(Side.CLIENT) +public class RenderPixie extends RenderLiving +{ + + private static final ResourceLocation pixieTextureLocation = new ResourceLocation("biomesoplenty:textures/entity/pixie.png"); + + public RenderPixie(RenderManager renderManager) + { + super(renderManager, new ModelPixie(), 0.25F); + this.shadowSize = 0.0F; + } + + @Override + protected ResourceLocation getEntityTexture(Entity entity) + { + return pixieTextureLocation; + } + + // TODO: Not sure about all this - Adubbz check please + // Looks like the idea is to set some rendering functions, then call super.doRender, then go back to normal + // LayerEndermanEyes have the same kind of approach I think + @Override + public void doRender(Entity entity, double x, double y, double z, float entityYaw, float partialTicks) + { + GlStateManager.enableBlend(); + GlStateManager.disableAlpha(); + GlStateManager.blendFunc(1, 1); + GlStateManager.disableLighting(); + + char c0 = 61680; + int i = c0 % 65536; + int j = c0 / 65536; + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, (float)i / 1.0F, (float)j / 1.0F); + GlStateManager.enableLighting(); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + + super.doRender(entity, x, y, z, entityYaw, partialTicks); + + GlStateManager.disableBlend(); + GlStateManager.enableAlpha(); + } + +} \ No newline at end of file diff --git a/src/main/java/biomesoplenty/common/init/ModEntities.java b/src/main/java/biomesoplenty/common/init/ModEntities.java index c469d0b13..9c6697cf6 100644 --- a/src/main/java/biomesoplenty/common/init/ModEntities.java +++ b/src/main/java/biomesoplenty/common/init/ModEntities.java @@ -8,6 +8,7 @@ package biomesoplenty.common.init; +import biomesoplenty.common.entities.EntityPixie; import biomesoplenty.common.entities.EntityWasp; import biomesoplenty.common.entities.projectiles.EntityDart; import biomesoplenty.core.BiomesOPlenty; @@ -20,10 +21,10 @@ public class ModEntities { // TODO: how to set id? + // TODO: why can't we use the summon command on these? EntityRegistry.registerModEntity(EntityDart.class, "dart", 26, BiomesOPlenty.instance, 80, 3, true); - EntityRegistry.registerModEntity(EntityWasp.class, "wasp", 27, BiomesOPlenty.instance, 80, 3, true); - + EntityRegistry.registerModEntity(EntityPixie.class, "pixie", 28, BiomesOPlenty.instance, 80, 3, true); } } \ No newline at end of file diff --git a/src/main/java/biomesoplenty/core/ClientProxy.java b/src/main/java/biomesoplenty/core/ClientProxy.java index 03fd8ed92..73f7d64f2 100644 --- a/src/main/java/biomesoplenty/core/ClientProxy.java +++ b/src/main/java/biomesoplenty/core/ClientProxy.java @@ -22,7 +22,9 @@ import net.minecraftforge.client.model.ModelLoader; import net.minecraftforge.fml.client.registry.RenderingRegistry; import biomesoplenty.api.block.IBOPBlock; import biomesoplenty.common.config.MiscConfigurationHandler; +import biomesoplenty.common.entities.EntityPixie; import biomesoplenty.common.entities.EntityWasp; +import biomesoplenty.common.entities.RenderPixie; import biomesoplenty.common.entities.RenderWasp; import biomesoplenty.common.entities.projectiles.EntityDart; import biomesoplenty.common.entities.projectiles.RenderDart; @@ -43,6 +45,7 @@ public class ClientProxy extends CommonProxy //Entity rendering and other stuff will go here in future RenderingRegistry.registerEntityRenderingHandler(EntityDart.class, new RenderDart(minecraft.getRenderManager())); RenderingRegistry.registerEntityRenderingHandler(EntityWasp.class, new RenderWasp(minecraft.getRenderManager())); + RenderingRegistry.registerEntityRenderingHandler(EntityPixie.class, new RenderPixie(minecraft.getRenderManager())); } diff --git a/src/main/resources/assets/biomesoplenty/sounds.json b/src/main/resources/assets/biomesoplenty/sounds.json index a6e70f16f..311db4148 100644 --- a/src/main/resources/assets/biomesoplenty/sounds.json +++ b/src/main/resources/assets/biomesoplenty/sounds.json @@ -1,4 +1,16 @@ { + "mob.pixie.hurt": { + "category": "neutral", + "sounds": [ + "mob/pixie/hurt" + ] + }, + "mob.pixie.say": { + "category": "neutral", + "sounds": [ + "mob/pixie/say" + ] + }, "mob.wasp.hurt": { "category": "hostile", "sounds": [ diff --git a/src/main/resources/assets/biomesoplenty/sounds/mob/pixie/hurt.ogg b/src/main/resources/assets/biomesoplenty/sounds/mob/pixie/hurt.ogg new file mode 100644 index 000000000..13edfba02 Binary files /dev/null and b/src/main/resources/assets/biomesoplenty/sounds/mob/pixie/hurt.ogg differ diff --git a/src/main/resources/assets/biomesoplenty/sounds/mob/pixie/say.ogg b/src/main/resources/assets/biomesoplenty/sounds/mob/pixie/say.ogg new file mode 100644 index 000000000..068a050b2 Binary files /dev/null and b/src/main/resources/assets/biomesoplenty/sounds/mob/pixie/say.ogg differ diff --git a/src/main/resources/assets/biomesoplenty/textures/entity/pixie.png b/src/main/resources/assets/biomesoplenty/textures/entity/pixie.png new file mode 100644 index 000000000..9ccd05c4f Binary files /dev/null and b/src/main/resources/assets/biomesoplenty/textures/entity/pixie.png differ