diff --git a/src/main/java/electroblob/wizardry/client/WizardryClientEventHandler.java b/src/main/java/electroblob/wizardry/client/WizardryClientEventHandler.java index e9ea54fb..ce053d6c 100644 --- a/src/main/java/electroblob/wizardry/client/WizardryClientEventHandler.java +++ b/src/main/java/electroblob/wizardry/client/WizardryClientEventHandler.java @@ -208,7 +208,6 @@ public final class WizardryClientEventHandler { Minecraft mc = Minecraft.getMinecraft(); WizardData properties = WizardData.get(mc.player); - RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(mc.world, mc.player, 16, false); RenderManager renderManager = event.getRenderer().getRenderManager(); ItemStack wand = mc.player.getHeldItemMainhand(); @@ -218,45 +217,51 @@ public final class WizardryClientEventHandler { } // Target selection pointer - if(mc.player.isSneaking() && wand.getItem() instanceof ItemWand && rayTrace != null && !(event.getEntity() instanceof EntityArmorStand) - && rayTrace.entityHit == event.getEntity() && properties != null && properties.selectedMinion != null){ + if(mc.player.isSneaking() && wand.getItem() instanceof ItemWand && WizardryUtilities.isLiving(event.getEntity()) + && properties != null && properties.selectedMinion != null){ + + // -> Moved this in here so it isn't called every tick + RayTraceResult rayTrace = WizardryUtilities.standardEntityRayTrace(mc.world, mc.player, 16, false); + + if(rayTrace != null && rayTrace.entityHit == event.getEntity()){ - Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBuffer(); + Tessellator tessellator = Tessellator.getInstance(); + BufferBuilder buffer = tessellator.getBuffer(); - GlStateManager.pushMatrix(); + GlStateManager.pushMatrix(); - GlStateManager.disableCull(); - GlStateManager.disableLighting(); - OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); - // Disabling depth test allows it to be seen through everything. - GlStateManager.disableDepth(); - GlStateManager.color(1, 1, 1, 1); + GlStateManager.disableCull(); + GlStateManager.disableLighting(); + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240f, 240f); + // Disabling depth test allows it to be seen through everything. + GlStateManager.disableDepth(); + GlStateManager.color(1, 1, 1, 1); - GlStateManager.translate(event.getX(), event.getY() + event.getEntity().height + 0.5, event.getZ()); + GlStateManager.translate(event.getX(), event.getY() + event.getEntity().height + 0.5, event.getZ()); - // This counteracts the reverse rotation behaviour when in front f5 view. - // Fun fact: this is a bug with vanilla too! Look at a snowball in front f5 view, for example. - float yaw = mc.gameSettings.thirdPersonView == 2 ? renderManager.playerViewX : -renderManager.playerViewX; - GlStateManager.rotate(180 - renderManager.playerViewY, 0.0F, 1.0F, 0.0F); - GlStateManager.rotate(yaw, 1.0F, 0.0F, 0.0F); + // This counteracts the reverse rotation behaviour when in front f5 view. + // Fun fact: this is a bug with vanilla too! Look at a snowball in front f5 view, for example. + float yaw = mc.gameSettings.thirdPersonView == 2 ? renderManager.playerViewX : -renderManager.playerViewX; + GlStateManager.rotate(180 - renderManager.playerViewY, 0.0F, 1.0F, 0.0F); + GlStateManager.rotate(yaw, 1.0F, 0.0F, 0.0F); - buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); + buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); - mc.renderEngine.bindTexture(targetPointerTexture); + mc.renderEngine.bindTexture(targetPointerTexture); - buffer.pos(-0.2, 0.24, 0).tex(0, 0).endVertex(); - buffer.pos(0.2, 0.24, 0).tex(9f / 16f, 0).endVertex(); - buffer.pos(0.2, -0.24, 0).tex(9f / 16f, 11f / 16f).endVertex(); - buffer.pos(-0.2, -0.24, 0).tex(0, 11f / 16f).endVertex(); + buffer.pos(-0.2, 0.24, 0).tex(0, 0).endVertex(); + buffer.pos(0.2, 0.24, 0).tex(9f / 16f, 0).endVertex(); + buffer.pos(0.2, -0.24, 0).tex(9f / 16f, 11f / 16f).endVertex(); + buffer.pos(-0.2, -0.24, 0).tex(0, 11f / 16f).endVertex(); - tessellator.draw(); + tessellator.draw(); - GlStateManager.enableCull(); - GlStateManager.enableLighting(); - GlStateManager.enableDepth(); + GlStateManager.enableCull(); + GlStateManager.enableLighting(); + GlStateManager.enableDepth(); - GlStateManager.popMatrix(); + GlStateManager.popMatrix(); + } } // Summoned creature selection pointer diff --git a/src/main/java/electroblob/wizardry/spell/SpellRay.java b/src/main/java/electroblob/wizardry/spell/SpellRay.java index 79b2f310..41c65e52 100644 --- a/src/main/java/electroblob/wizardry/spell/SpellRay.java +++ b/src/main/java/electroblob/wizardry/spell/SpellRay.java @@ -70,6 +70,8 @@ public abstract class SpellRay extends Spell { protected boolean ignoreEntities = false; /** Whether liquids count as blocks when raytracing. Defaults to false. */ protected boolean hitLiquids = false; + /** The aim assist to use when raytracing. Defaults to 0. */ + protected float aimAssist = 0; public SpellRay(String name, Tier tier, Element element, SpellType type, int cost, int cooldown, boolean isContinuous, double baseRange, SoundEvent sound){ this(Wizardry.MODID, name, tier, element, type, cost, cooldown, isContinuous, baseRange, sound); @@ -147,6 +149,17 @@ public abstract class SpellRay extends Spell { return this; } + /** + * Sets the aim assist to use when raytracing. + * @param particleVelocity The aim assist to use when raytracing. See {@link WizardryUtilities#rayTrace(World, Vec3d, + * Vec3d, float, boolean, Class, java.util.function.Predicate)} for more details. + * @return The spell instance, allowing this method to be chained onto the constructor. + */ + public SpellRay aimAssist(float aimAssist){ + this.aimAssist = aimAssist; + return this; + } + @Override public boolean canBeCastByNPCs(){ return true; } // Finally everything in here is standardised and written in a form that's actually readable - it was long overdue! @@ -157,49 +170,7 @@ public abstract class SpellRay extends Spell { Vec3d origin = new Vec3d(caster.posX, caster.getEntityBoundingBox().minY + caster.getEyeHeight() - Y_OFFSET, caster.posZ); - double range = baseRange * modifiers.get(WizardryItems.range_upgrade); - - // The first method will hit the first block it touches, passing through entities as if they weren't there. - // The second method will hit the first thing it touches, whether that's a block or an entity. - // Note that when it hits a block, the exact position hit is returned in hitVec, whereas when it hits an - // entity, it just returns the entity's position in hitVec. - RayTraceResult rayTrace = ignoreEntities - ? WizardryUtilities.standardBlockRayTrace(world, caster, range, hitLiquids) - : WizardryUtilities.standardEntityRayTrace(world, caster, range, hitLiquids); - - boolean flag = false; - - if(rayTrace != null){ - // Doesn't matter which way round these are, they're mutually exclusive - if(rayTrace.typeOfHit == RayTraceResult.Type.ENTITY){ - - Entity target = rayTrace.entityHit; - // Do whatever the spell does when it hits an entity - flag = onEntityHit(world, target, caster, ticksInUse, modifiers); - // If the spell succeeded, clip the particles to the correct distance so they don't go through the entity - if(flag){ - // The most pragmatic solution is to use the target's centre point for reasons explained earlier - double dx = origin.x - target.posX; - double dy = origin.y - (target.getEntityBoundingBox().minY + target.height/2); - double dz = origin.z - target.posZ; - range = MathHelper.sqrt(dx*dx + dy*dy + dz*dz); - } - - }else if(rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ - // Do whatever the spell does when it hits an block - flag = onBlockHit(world, rayTrace.getBlockPos(), rayTrace.sideHit, caster, ticksInUse, modifiers); - // If the spell succeeded, clip the particles to the correct distance so they don't go through the block - if(flag) range = origin.distanceTo(rayTrace.hitVec); - } - } - - // If flag is false, either the spell missed or the relevant entity/block hit method returned false - if(!flag && !onMiss(world, caster, ticksInUse, modifiers)) return false; - - // Particle spawning - if(world.isRemote){ - spawnParticleRay(world, origin, look, range); - } + if(!shootSpell(world, origin, look, caster, ticksInUse, modifiers)) return false; if(!isContinuous) caster.swingArm(hand); // Bit of a dirty fix but I don't think it'll be a problem! if(sound != null) WizardryUtilities.playSoundAtPlayer(caster, sound, volume, pitch + pitchVariation * (world.rand.nextFloat() - 0.5f)); @@ -253,6 +224,46 @@ public abstract class SpellRay extends Spell { return true; } + /** Player and dispenser casting are almost identical so this takes care of the shared stuff. */ + private boolean shootSpell(World world, Vec3d origin, Vec3d direction, @Nullable EntityPlayer caster, int ticksInUse, SpellModifiers modifiers){ + + double range = baseRange * modifiers.get(WizardryItems.range_upgrade); + Vec3d endpoint = origin.add(direction.scale(range)); + + // The first method will hit the first block it touches, passing through entities as if they weren't there. + // The second method will hit the first thing it touches, whether that's a block or an entity. This method now + // returns the exact hit position for entities as well as blocks. + RayTraceResult rayTrace = WizardryUtilities.rayTrace(world, origin, endpoint, aimAssist, hitLiquids, Entity.class, e -> e == caster); + + boolean flag = false; + + if(rayTrace != null){ + // Doesn't matter which way round these are, they're mutually exclusive + if(rayTrace.typeOfHit == RayTraceResult.Type.ENTITY){ + // Do whatever the spell does when it hits an entity + flag = onEntityHit(world, rayTrace.entityHit, caster, ticksInUse, modifiers); + // If the spell succeeded, clip the particles to the correct distance so they don't go through the entity + if(flag) range = origin.distanceTo(rayTrace.hitVec); + + }else if(rayTrace.typeOfHit == RayTraceResult.Type.BLOCK){ + // Do whatever the spell does when it hits an block + flag = onBlockHit(world, rayTrace.getBlockPos(), rayTrace.sideHit, caster, ticksInUse, modifiers); + // If the spell succeeded, clip the particles to the correct distance so they don't go through the block + if(flag) range = origin.distanceTo(rayTrace.hitVec); + } + } + + // If flag is false, either the spell missed or the relevant entity/block hit method returned false + if(!flag && !onMiss(world, caster, ticksInUse, modifiers)) return false; + + // Particle spawning + if(world.isRemote){ + spawnParticleRay(world, origin, direction, range); + } + + return true; + } + // Private helper method, no-one will need to override it since it's pretty much the whole point of this class. private void spawnParticleRay(World world, Vec3d origin, Vec3d direction, double distance){ diff --git a/src/main/java/electroblob/wizardry/util/WizardryUtilities.java b/src/main/java/electroblob/wizardry/util/WizardryUtilities.java index d9578fd6..d334e2a2 100644 --- a/src/main/java/electroblob/wizardry/util/WizardryUtilities.java +++ b/src/main/java/electroblob/wizardry/util/WizardryUtilities.java @@ -4,13 +4,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.UUID; import java.util.function.Function; +import java.util.function.Predicate; import javax.annotation.Nullable; @@ -553,118 +553,145 @@ public final class WizardryUtilities { // =============================================================================================================== /** - * Does a block ray trace (NOT entities) from an entity's eyes (i.e. properly...) + * Helper method which performs a ray trace for blocks only from an entity's eye position in the direction + * they are looking, over a specified range, using {@link World#rayTraceBlocks(Vec3d, Vec3d, boolean)}. + * + * @param world The world in which to perform the ray trace. + * @param entity The entity from which to perform the ray trace. The ray trace will start from this entity's eye + * position and proceed in the direction the entity is looking. + * @param range The distance over which the ray trace will be performed. + * @param hitLiquids True to return hits on the surfaces of liquids, false to ignore liquid blocks as if they were + * not there. + * @return A {@link RayTraceResult} representing the object that was hit, which may be either a block or nothing. + * Returns {@code null} only if the origin and endpoint are within the same block. */ @Nullable public static RayTraceResult standardBlockRayTrace(World world, EntityLivingBase entity, double range, boolean hitLiquids){ - - Vec3d start = new Vec3d(entity.posX, entity.getEntityBoundingBox().minY + entity.getEyeHeight(), entity.posZ); - Vec3d look = entity.getLookVec(); - Vec3d end = start.addVector(look.x * range, look.y * range, look.z * range); - return world.rayTraceBlocks(start, end, hitLiquids); + // This method does not apply an offset like ray spells do, since it is not desirable in most other use cases. + Vec3d origin = new Vec3d(entity.posX, entity.getEntityBoundingBox().minY + entity.getEyeHeight(), entity.posZ); + Vec3d endpoint = origin.add(entity.getLookVec().scale(range)); + return world.rayTraceBlocks(origin, endpoint, hitLiquids); } /** - * Helper method which does a rayTrace for entities from an entity's eye level in the direction they are looking - * with a specified range, using the tracePath method. Tidies up the code a bit. Border size defaults to 1. + * Helper method which performs a ray trace for blocks and entities from an entity's eye position in the direction + * they are looking, over a specified range, using {@link WizardryUtilities#rayTrace(World, Vec3d, Vec3d, float, + * boolean, Class, Predicate)}. Aim assist is zero, the entity type is simply {@code Entity} (all entities), and the + * filter removes the given entity and allows all others. * - * @param world - * @param entity - * @param range - * @return + * @param world The world in which to perform the ray trace. + * @param entity The entity from which to perform the ray trace. The ray trace will start from this entity's eye + * position and proceed in the direction the entity is looking. This entity will be ignored when ray tracing. + * @param range The distance over which the ray trace will be performed. + * @param hitLiquids True to return hits on the surfaces of liquids, false to ignore liquid blocks as if they were + * not there. + * @return A {@link RayTraceResult} representing the object that was hit, which may be an entity, a block or + * nothing. Returns {@code null} only if the origin and endpoint are within the same block and no entity was hit. */ @Nullable - public static RayTraceResult standardEntityRayTrace(World world, EntityLivingBase entity, double range, boolean hitLiquids){ - return standardEntityRayTrace(world, entity, range, 1, hitLiquids); + public static RayTraceResult standardEntityRayTrace(World world, Entity entity, double range, boolean hitLiquids){ + // This method does not apply an offset like ray spells do, since it is not desirable in most other use cases. + Vec3d origin = new Vec3d(entity.posX, entity.getEntityBoundingBox().minY + entity.getEyeHeight(), entity.posZ); + Vec3d endpoint = origin.add(entity.getLookVec().scale(range)); + return WizardryUtilities.rayTrace(world, origin, endpoint, 0, hitLiquids, Entity.class, e -> e == entity); } /** - * Helper method which does a rayTrace for entities from a entity's eye level in the direction they are looking with - * a specified range and radius, using the tracePath method. Tidies up the code a bit. + * Performs a ray trace for blocks and entities, starting at the given origin and finishing at the given endpoint. + * As of wizardry 4.2, the ray tracing methods have been rewritten to be more user-friendly and implement proper + * aim assist. + *
+ * N.B. It is possible to ignore entities entirely by passing in a {@code Predicate} that is always false;
+ * however, in this specific case it is more efficient to use
+ * {@link World#rayTraceBlocks(Vec3d, Vec3d, boolean, boolean, boolean)} or one of its overloads.
*
- * @param world
- * @param entity
- * @param range
- * @param borderSize
- * @return
- */
- @Nullable
- public static RayTraceResult standardEntityRayTrace(World world, EntityLivingBase entity, double range, float borderSize, boolean hitLiquids){
- double dx = entity.getLookVec().x * range;
- double dy = entity.getLookVec().y * range;
- double dz = entity.getLookVec().z * range;
- HashSet