diff --git a/src/main/java/pl/pabilo8/immersiveintelligence/common/entity/ammo/EntityAmmoBase.java b/src/main/java/pl/pabilo8/immersiveintelligence/common/entity/ammo/EntityAmmoBase.java index 1f693a22e..f813e634a 100644 --- a/src/main/java/pl/pabilo8/immersiveintelligence/common/entity/ammo/EntityAmmoBase.java +++ b/src/main/java/pl/pabilo8/immersiveintelligence/common/entity/ammo/EntityAmmoBase.java @@ -135,7 +135,27 @@ public void setFromParameters(IAmmoType ammoType, AmmoCore core, CoreType this.components = components; this.height = this.width = Math.max(0.25f, (ammoType.getCaliber()/16f)); float fraction = height/2f; - this.setEntityBoundingBox(this.aabb = new AxisAlignedBB(-fraction, -fraction, -fraction, fraction, fraction, fraction)); + this.aabb = new AxisAlignedBB(-fraction, -fraction, -fraction, fraction, fraction, fraction); + this.setPosition(this.posX, this.posY, this.posZ); + } + + @Override + public void setPosition(double x, double y, double z) + { + this.posX = x; + this.posY = y; + this.posZ = z; + float fraction = this.width / 2.0F; + this.setEntityBoundingBox(new AxisAlignedBB(x - fraction, y - fraction, z - fraction, x + fraction, y + fraction, z + fraction)); + } + + @Override + public void resetPositionToBB() + { + AxisAlignedBB box = this.getEntityBoundingBox(); + this.posX = (box.minX + box.maxX) / 2.0D; + this.posY = (box.minY + box.maxY) / 2.0D; // We use center instead of minY + this.posZ = (box.minZ + box.maxZ) / 2.0D; } @Override diff --git a/src/main/java/pl/pabilo8/immersiveintelligence/common/entity/ammo/types/EntityAmmoGrenade.java b/src/main/java/pl/pabilo8/immersiveintelligence/common/entity/ammo/types/EntityAmmoGrenade.java index 8e4fa2c33..510b0eded 100644 --- a/src/main/java/pl/pabilo8/immersiveintelligence/common/entity/ammo/types/EntityAmmoGrenade.java +++ b/src/main/java/pl/pabilo8/immersiveintelligence/common/entity/ammo/types/EntityAmmoGrenade.java @@ -69,49 +69,134 @@ else if(spinTicks > 0) spin = MathHelper.wrapDegrees(spin+(spinDirection?-SPIN_DEGREES: SPIN_DEGREES)); } + private boolean hasRicochetedThisTick = false; + @Override protected void doProjectileMotion() { + this.hasRicochetedThisTick = false; + //Volumetric raytrace through the projectile's flight path - if(velocity <= 0) - { - double initX = motionX, initY = motionY, initZ = motionZ; - applyInMotionColision(this.world.getCollisionBoxes(this, aabb - .offset(posX, posY, posZ) - .expand(motionX, motionY, motionZ)) - ); - if(initX!=motionX||initY!=motionY||initZ!=motionZ) - spinTicks /= 2; - } - else - flightTracer.stepTrace(world, getPositionVector(), getNextPositionVector(), hit -> { - if(hit!=null) + flightTracer.stepTrace(world, getPositionVector(), getNextPositionVector(), hit -> { + if(hit!=null) + { + if(shouldDetonateAfterContact()) { - //Proximity fuses don't use penetration logic - if(shouldDetonateAfterContact()) - { - detonate(); - return true; - } + detonate(); + return true; + } - //But other fuses do - switch(hit.typeOfHit) - { - case BLOCK: - return handleBlockDamage(hit); - case ENTITY: - return handleEntityDamage(hit); - } + switch(hit.typeOfHit) + { + case BLOCK: + return handleBlockDamage(hit); + case ENTITY: + return handleEntityDamage(hit); } - return false; - }); + } + return false; + }); + + if (this.isDead) return; + // Apply AABB collision (handles slow collisions, rolling, prevents clipping, and catches edge ricochets) + applyInMotionColision(); //Finalize the motion posX += motionX; posY += motionY; posZ += motionZ; + this.setPosition(posX, posY, posZ); + } + + private void applyInMotionColision() + { + double initX = motionX; + double initY = motionY; + double initZ = motionZ; + + AxisAlignedBB offsetAABB = this.getEntityBoundingBox(); + + List boxes = new java.util.ArrayList<>(); + for (AxisAlignedBB box : this.world.getCollisionBoxes(this, offsetAABB.expand(motionX, motionY, motionZ))) { + if (owner != null && owner.getEntityBoundingBox() != null && box.intersects(owner.getEntityBoundingBox())) continue; + + // Ignore fragile blocks (like leaves and glass) in AABB sweep so we pass through them + boolean isFragile = false; + net.minecraft.util.math.BlockPos bp = new net.minecraft.util.math.BlockPos( + box.minX + (box.maxX - box.minX)/2.0, + box.minY + (box.maxY - box.minY)/2.0, + box.minZ + (box.maxZ - box.minZ)/2.0 + ); + net.minecraft.block.state.IBlockState state = world.getBlockState(bp); + if (state.getBlock() != net.minecraft.init.Blocks.AIR) { + pl.pabilo8.immersiveintelligence.api.ammo.penetration.IPenetrationHandler pen = pl.pabilo8.immersiveintelligence.api.ammo.PenetrationRegistry.getPenetrationHandler(state); + if (pen != null && pen.getPenetrationHardness().compareTo(pl.pabilo8.immersiveintelligence.api.ammo.enums.PenetrationHardness.FRAGILE) <= 0) { + isFragile = true; + } + } + + if (!isFragile) { + boxes.add(box); + } + } + + for(AxisAlignedBB axisalignedbb : boxes) + motionY = axisalignedbb.calculateYOffset(offsetAABB, motionY); + offsetAABB = offsetAABB.offset(0, motionY, 0); + + for(AxisAlignedBB axisalignedbb : boxes) + motionX = axisalignedbb.calculateXOffset(offsetAABB, motionX); + offsetAABB = offsetAABB.offset(motionX, 0, 0); + + for(AxisAlignedBB axisalignedbb : boxes) + motionZ = axisalignedbb.calculateZOffset(offsetAABB, motionZ); + + //check Y axis motion + if(this.onGround = (initY != motionY && initY < 0.0D)) + gravityMotionY = 0; + // Handle grazing/edge bounces that FactoryTracer missed + // Skip if onHitRicochet already triggered this tick to prevent double-inversion! + if (!this.hasRicochetedThisTick && (initX != motionX || initY != motionY || initZ != motionZ)) { + double impactY = Math.abs(initY - motionY); + + // If we ONLY hit the floor, and the impact is very weak (just gravity pulling us down while resting), do not bounce! + if (initX == motionX && initZ == motionZ && impactY > 0 && impactY <= 0.15) { + // Just resting on the ground, kill vertical base motion so friction can apply! + if (this.baseMotion.y > 0) { + Vec3d flattened = new Vec3d(this.baseMotion.x, 0, this.baseMotion.z); + if (flattened.lengthVector() > 0) { + this.baseMotion = flattened.normalize(); + } else { + this.baseMotion = Vec3d.ZERO; + } + } + } else { + double bounceX = initX != motionX ? -initX : initX; + double bounceY = initY != motionY ? -initY : initY; + double bounceZ = initZ != motionZ ? -initZ : initZ; + + Vec3d bouncedVelocity = new Vec3d(bounceX, bounceY, bounceZ).scale(0.45); + + this.velocity = (float) bouncedVelocity.lengthVector(); + if (this.velocity > 0.001) { + this.baseMotion = bouncedVelocity.normalize(); + } + this.gravityMotionY = 0; + + spinDirection = !spinDirection; + spinTicks += 180; + + playSound(net.minecraft.init.SoundEvents.BLOCK_METAL_HIT, 0.5f, 1f); + } + } + + // If the grenade has upward momentum after bouncing, it is NOT resting on the ground! + // This prevents updatePhysics from destroying 99% of its velocity on the next tick. + if (baseMotion.y > 0 && velocity > 0.05) { + this.onGround = false; + } } @Override @@ -141,46 +226,59 @@ protected boolean handleBlockDamage(RayTraceResult hit) @Override protected void onHitRicochet(RayTraceResult hit, IPenetrationHandler handler) { - //Get colision boxes - List boxes = new ArrayList<>(); - ForgeEventFactory.gatherCollisionBoxes(world, this, new AxisAlignedBB(hit.getBlockPos()).grow(width), boxes); - world.getBlockState(hit.getBlockPos()).addCollisionBoxToList(world, hit.getBlockPos(), - aabb.offset(hit.hitVec), boxes, this, false); + this.hasRicochetedThisTick = true; + + //If ricochets are disabled, end the bullet's lifecycle + if(!pl.pabilo8.immersiveintelligence.api.ammo.utils.IIAmmoUtils.ammoRicochets) + { + detonate(); + return; + } + + // Don't let it bounce infinitely without sideHit + if (hit.sideHit == null) + { + // Guess side hit based on actual motion + hit.sideHit = net.minecraft.util.EnumFacing.getFacingFromVector((float)motionX, (float)motionY, (float)motionZ).getOpposite(); + } - //Apply collisions - applyInMotionColision(boxes); + // We no longer manually teleport the grenade or zero out its motion! + // The AABB sweep (applyInMotionColision) will cleanly push the grenade exactly to the wall this tick, + // and the newly calculated baseMotion/velocity will take over on the next tick! - //Decay velocity - this.velocity *= 0.3f; + // Compute the true velocity vector before bounce + Vec3d trueVelocity = new Vec3d(baseMotion.x * velocity, baseMotion.y * velocity + gravityMotionY, baseMotion.z * velocity); + + penetrationDepth -= handler.getThickness()*2; + gravityMotionY = 0; spinDirection = !spinDirection; spinTicks += 180; - //Sync with client + // Reflect the true velocity off the surface normal + Vec3d surfaceNormal = new Vec3d(hit.sideHit.getDirectionVec()); + Vec3d bouncedVelocity = trueVelocity.subtract( + surfaceNormal.scale(2 * trueVelocity.dotProduct(surfaceNormal)) + ); + + // Apply decay + bouncedVelocity = bouncedVelocity.scale(0.45); + + // Set the new velocity and baseMotion + this.velocity = (float) bouncedVelocity.lengthVector(); + + if (this.velocity > 0.001) + this.baseMotion = bouncedVelocity.normalize(); + updateEntityForEvent(SyncEvents.ENTITY_COLLISION); - } - /** - * Applies collision with selected bounding boxes for a moving grenade - * - * @param boxes List of bounding boxes to collide with - */ - private void applyInMotionColision(List boxes) - { - double initY = motionY; - AxisAlignedBB offsetAABB = aabb.offset(posX, posY, posZ); - for(AxisAlignedBB axisalignedbb : boxes) - { - motionY = axisalignedbb.calculateYOffset(offsetAABB, motionY); - offsetAABB = offsetAABB.offset(0, motionY, 0); - motionX = axisalignedbb.calculateXOffset(offsetAABB, motionX); - offsetAABB = offsetAABB.offset(motionX, 0, 0); - motionZ = axisalignedbb.calculateZOffset(offsetAABB, motionZ); - offsetAABB = offsetAABB.offset(0, 0, motionZ); - } + //Clear the lists + ignoredEntities.clear(); + ignoredPositions.clear(); - //check Y axis motion - if(this.onGround = initY!=motionY) - gravityMotionY = 0; + //Play impact sound + net.minecraft.util.SoundEvent sound = handler.getSpecialSound(pl.pabilo8.immersiveintelligence.api.ammo.enums.HitEffect.RICOCHET); + if(sound!=null) + playSound(sound, 0.5f, 1f); } @Override diff --git a/src/main/java/pl/pabilo8/immersiveintelligence/common/util/raytracer/FactoryTracer.java b/src/main/java/pl/pabilo8/immersiveintelligence/common/util/raytracer/FactoryTracer.java index daacf0222..47bcbdbca 100644 --- a/src/main/java/pl/pabilo8/immersiveintelligence/common/util/raytracer/FactoryTracer.java +++ b/src/main/java/pl/pabilo8/immersiveintelligence/common/util/raytracer/FactoryTracer.java @@ -109,12 +109,14 @@ public RayTraceResult stepTrace(World world, Vec3d posStart, Vec3d posEnd, Predi boolean skipPos = false; //Step Vector3d pDiff = new Vector3d(posEnd.x-posStart.x, posEnd.y-posStart.y, posEnd.z-posStart.z); - pDiff.normalize(); - pDiff.scale(precision); + double totalDist = Math.sqrt(pDiff.x*pDiff.x + pDiff.y*pDiff.y + pDiff.z*pDiff.z); + if(totalDist > 0) + pDiff.normalize(); //Expand the bounding box AxisAlignedBB aabb; - int totalSteps = (int)Math.max(1, posStart.distanceTo(posEnd)/precision); + int totalSteps = (int)Math.max(1, totalDist/precision); + pDiff.scale(totalDist / totalSteps); //Cache entities List allEntities = allowEntities?listAllEntities(world, posStart, posEnd): null; @@ -180,10 +182,9 @@ else if(entity.getEntityBoundingBox().intersects(aabb)) continue; //Perform a precise raytrace on the block - RayTraceResult trace = state.collisionRayTrace(world, pos, - new Vec3d(aabb.minX, aabb.minY, aabb.minZ), - new Vec3d(aabb.maxX, aabb.maxY, aabb.maxZ) - ); + Vec3d stepStart = new Vec3d(pX - pDiff.x, pY - pDiff.y, pZ - pDiff.z); + Vec3d stepEnd = new Vec3d(pX, pY, pZ); + RayTraceResult trace = state.collisionRayTrace(world, pos, stepStart, stepEnd); //Avoid scanning the same position twice if(trace!=null&&trace.typeOfHit!=Type.MISS) skipPos = true;