From df2ea4d2ccb9c8635f824de14768ad7382ca27a1 Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 23 Oct 2019 04:53:34 +0100 Subject: [PATCH] Add TNT rules --- .../java/carpet/fakes/TntEntityInterface.java | 7 + .../carpet/helpers/OptimizedExplosion.java | 575 ++++++++++++++++++ .../java/carpet/mixins/ExplosionAccessor.java | 41 ++ .../java/carpet/mixins/ExplosionMixin.java | 23 + .../java/carpet/mixins/TntEntityMixin.java | 60 +- .../java/carpet/settings/CarpetSettings.java | 54 +- src/main/java/carpet/settings/ParsedRule.java | 5 + .../java/carpet/settings/RuleCategory.java | 1 + src/main/resources/carpet.mixins.json | 1 + 9 files changed, 758 insertions(+), 9 deletions(-) create mode 100644 src/main/java/carpet/fakes/TntEntityInterface.java create mode 100644 src/main/java/carpet/helpers/OptimizedExplosion.java create mode 100644 src/main/java/carpet/mixins/ExplosionAccessor.java diff --git a/src/main/java/carpet/fakes/TntEntityInterface.java b/src/main/java/carpet/fakes/TntEntityInterface.java new file mode 100644 index 00000000..5dcc2c8a --- /dev/null +++ b/src/main/java/carpet/fakes/TntEntityInterface.java @@ -0,0 +1,7 @@ +package carpet.fakes; + +public interface TntEntityInterface { + + int getMergedTNT(); + +} diff --git a/src/main/java/carpet/helpers/OptimizedExplosion.java b/src/main/java/carpet/helpers/OptimizedExplosion.java new file mode 100644 index 00000000..5753ce78 --- /dev/null +++ b/src/main/java/carpet/helpers/OptimizedExplosion.java @@ -0,0 +1,575 @@ +package carpet.helpers; +//Author: masa + +import java.math.RoundingMode; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.List; + +import carpet.mixins.ExplosionAccessor; +import carpet.settings.CarpetSettings; +import carpet.utils.Messenger; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.Material; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.TntEntity; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.fluid.FluidState; +import net.minecraft.item.ItemStack; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.world.explosion.Explosion; +import net.minecraft.world.loot.context.LootContext; +import net.minecraft.world.loot.context.LootContextParameters; +import org.apache.commons.lang3.tuple.MutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.minecraft.block.Block; +import net.minecraft.enchantment.ProtectionEnchantment; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; + +public class OptimizedExplosion +{ + private static List entitylist; + private static Vec3d vec3dmem; + private static long tickmem; + // For disabling the explosion particles and sound + public static int explosionSound = 0; + + // masa's optimizations + private static Object2DoubleOpenHashMap> densityCache = new Object2DoubleOpenHashMap<>(); + private static MutablePair pairMutable = new MutablePair<>(); + private static Object2ObjectOpenHashMap stateCache = new Object2ObjectOpenHashMap<>(); + private static Object2ObjectOpenHashMap fluidCache = new Object2ObjectOpenHashMap<>(); + private static BlockPos.Mutable posMutable = new BlockPos.Mutable(0, 0, 0); + private static ObjectOpenHashSet affectedBlockPositionsSet = new ObjectOpenHashSet<>(); + private static boolean firstRay; + private static boolean rayCalcDone; + private static ArrayList chances = new ArrayList<>(); + private static BlockPos blastChanceLocation; + + public static void doExplosionA(Explosion e) { + ExplosionAccessor eAccess = (ExplosionAccessor) e; + + blastCalc(e); + + if (!CarpetSettings.explosionNoBlockDamage) { + rayCalcDone = false; + firstRay = true; + getAffectedPositionsOnPlaneY(e, 0, 0, 15, 0, 15); // bottom + getAffectedPositionsOnPlaneY(e, 15, 0, 15, 0, 15); // top + getAffectedPositionsOnPlaneX(e, 0, 1, 14, 0, 15); // west + getAffectedPositionsOnPlaneX(e, 15, 1, 14, 0, 15); // east + getAffectedPositionsOnPlaneZ(e, 0, 1, 14, 1, 14); // north + getAffectedPositionsOnPlaneZ(e, 15, 1, 14, 1, 14); // south + stateCache.clear(); + fluidCache.clear(); + + e.getAffectedBlocks().addAll(affectedBlockPositionsSet); + affectedBlockPositionsSet.clear(); + } + + float f3 = eAccess.getPower() * 2.0F; + int k1 = MathHelper.floor(eAccess.getX() - (double) f3 - 1.0D); + int l1 = MathHelper.floor(eAccess.getX() + (double) f3 + 1.0D); + int i2 = MathHelper.floor(eAccess.getY() - (double) f3 - 1.0D); + int i1 = MathHelper.floor(eAccess.getY() + (double) f3 + 1.0D); + int j2 = MathHelper.floor(eAccess.getZ() - (double) f3 - 1.0D); + int j1 = MathHelper.floor(eAccess.getZ() + (double) f3 + 1.0D); + Vec3d vec3d = new Vec3d(eAccess.getX(), eAccess.getY(), eAccess.getZ()); + + if (vec3dmem == null || !vec3dmem.equals(vec3d) || tickmem != eAccess.getWorld().getTime()) { + vec3dmem = vec3d; + tickmem = eAccess.getWorld().getTime(); + entitylist = eAccess.getWorld().getEntities((Entity)null, + new Box((double) k1, (double) i2, (double) j2, (double) l1, (double) i1, (double) j1)); + explosionSound = 0; + } + + explosionSound++; + + for (int k2 = 0; k2 < entitylist.size(); ++k2) { + Entity entity = entitylist.get(k2); + + if (entity == eAccess.getEntity()) { + // entitylist.remove(k2); + removeFast(entitylist, k2); + k2--; + continue; + } + + if (entity instanceof TntEntity && + entity.x == eAccess.getEntity().x && + entity.y == eAccess.getEntity().y && + entity.z == eAccess.getEntity().z) { + continue; + } + + if (!entity.isImmuneToExplosion()) { + double d12 = MathHelper.sqrt(entity.squaredDistanceTo(eAccess.getX(), eAccess.getY(), eAccess.getZ())) / (double) f3; + + if (d12 <= 1.0D) { + double d5 = entity.x - eAccess.getX(); + double d7 = entity.y + (double) entity.getStandingEyeHeight() - eAccess.getY(); + double d9 = entity.z - eAccess.getZ(); + double d13 = (double) MathHelper.sqrt(d5 * d5 + d7 * d7 + d9 * d9); + + if (d13 != 0.0D) { + d5 = d5 / d13; + d7 = d7 / d13; + d9 = d9 / d13; + double density; + + pairMutable.setLeft(vec3d); + pairMutable.setRight(entity.getBoundingBox()); + density = densityCache.getOrDefault(pairMutable, Double.MAX_VALUE); + + if (density == Double.MAX_VALUE) + { + Pair pair = Pair.of(vec3d, entity.getBoundingBox()); + density = Explosion.getExposure(vec3d, entity); + densityCache.put(pair, density); + } + + double d10 = (1.0D - d12) * density; + entity.damage(e.getDamageSource(), + (float) ((int) ((d10 * d10 + d10) / 2.0D * 7.0D * (double) f3 + 1.0D))); + double d11 = d10; + + if (entity instanceof LivingEntity) { + d11 = ProtectionEnchantment.transformExplosionKnockback((LivingEntity) entity, d10); + } + + entity.setVelocity(entity.getVelocity().add(d5 * d11, d7 * d11, d9 * d11)); + + if (entity instanceof PlayerEntity) { + PlayerEntity PlayerEntity = (PlayerEntity) entity; + + if (!PlayerEntity.isSpectator() + && (!PlayerEntity.isCreative() || !PlayerEntity.abilities.flying)) { + e.getAffectedPlayers().put(PlayerEntity, new Vec3d(d5 * d10, d7 * d10, d9 * d10)); + } + } + } + } + } + } + + densityCache.clear(); + } + + public static void doExplosionB(Explosion e, boolean spawnParticles) + { + ExplosionAccessor eAccess = (ExplosionAccessor) e; + World world = eAccess.getWorld(); + double posX = eAccess.getX(); + double posY = eAccess.getY(); + double posZ = eAccess.getZ(); + + boolean damagesTerrain = eAccess.getBlockDestructionType() != Explosion.DestructionType.NONE; + + // explosionSound incremented till disabling the explosion particles and sound + if (explosionSound < 100 || explosionSound % 100 == 0) + { + world.playSound(null, posX, posY, posZ, SoundEvents.ENTITY_GENERIC_EXPLODE, SoundCategory.BLOCKS, 4.0F, + (1.0F + (world.random.nextFloat() - world.random.nextFloat()) * 0.2F) * 0.7F); + + if (eAccess.getPower() >= 2.0F && damagesTerrain) + { + world.addParticle(ParticleTypes.EXPLOSION_EMITTER, posX, posY, posZ, 1.0D, 0.0D, 0.0D); + } + else + { + world.addParticle(ParticleTypes.EXPLOSION, posX, posY, posZ, 1.0D, 0.0D, 0.0D); + } + } + + if (damagesTerrain) + { + for (BlockPos blockpos : e.getAffectedBlocks()) + { + BlockState BlockState = world.getBlockState(blockpos); + Block block = BlockState.getBlock(); + + if (spawnParticles) + { + double d0 = (double)((float)blockpos.getX() + world.random.nextFloat()); + double d1 = (double)((float)blockpos.getY() + world.random.nextFloat()); + double d2 = (double)((float)blockpos.getZ() + world.random.nextFloat()); + double d3 = d0 - posX; + double d4 = d1 - posY; + double d5 = d2 - posZ; + double d6 = (double)MathHelper.sqrt(d3 * d3 + d4 * d4 + d5 * d5); + d3 = d3 / d6; + d4 = d4 / d6; + d5 = d5 / d6; + double d7 = 0.5D / (d6 / (double) eAccess.getPower() + 0.1D); + d7 = d7 * (double)(world.random.nextFloat() * world.random.nextFloat() + 0.3F); + d3 = d3 * d7; + d4 = d4 * d7; + d5 = d5 * d7; + world.addParticle(ParticleTypes.POOF, + (d0 + posX) / 2.0D, (d1 + posY) / 2.0D, (d2 + posZ) / 2.0D, d3, d4, d5); + world.addParticle(ParticleTypes.SMOKE, d0, d1, d2, d3, d4, d5); + } + + if (BlockState.getMaterial() != Material.AIR) + { + if (block.shouldDropItemsOnExplosion(e)) + { + BlockEntity blockEntity = block.hasBlockEntity() ? world.getBlockEntity(blockpos) : null; + + LootContext.Builder lootBuilder = new LootContext.Builder((ServerWorld) eAccess.getWorld()) + .setRandom(eAccess.getWorld().random) + .put(LootContextParameters.POSITION, blockpos) + .put(LootContextParameters.TOOL, ItemStack.EMPTY) + .putNullable(LootContextParameters.BLOCK_ENTITY, blockEntity); + + if (eAccess.getBlockDestructionType() == Explosion.DestructionType.DESTROY) + lootBuilder.put(LootContextParameters.EXPLOSION_RADIUS, eAccess.getPower()); + + Block.dropStacks(BlockState, lootBuilder); + } + + world.setBlockState(blockpos, Blocks.AIR.getDefaultState(), 3); + block.onDestroyedByExplosion(world, blockpos, e); + } + } + } + + if (eAccess.isCreateFire()) + { + for (BlockPos blockpos1 : e.getAffectedBlocks()) + { + // Use the same Chunk reference because the positions are in the same xz-column + Chunk chunk = world.getChunk(blockpos1.getX() >> 4, blockpos1.getZ() >> 4); + + BlockPos down = blockpos1.down(); + if (chunk.getBlockState(blockpos1).getMaterial() == Material.AIR && + chunk.getBlockState(down).isFullOpaque(world, down) && + eAccess.getRandom().nextInt(3) == 0) + { + world.setBlockState(blockpos1, Blocks.FIRE.getDefaultState()); + } + } + } + } + + private static void removeFast(List lst, int index) { + if (index < lst.size() - 1) + lst.set(index, lst.get(lst.size() - 1)); + lst.remove(lst.size() - 1); + } + + private static void rayCalcs(Explosion e) { + ExplosionAccessor eAccess = (ExplosionAccessor) e; + boolean first = true; + + for (int j = 0; j < 16; ++j) { + for (int k = 0; k < 16; ++k) { + for (int l = 0; l < 16; ++l) { + if (j == 0 || j == 15 || k == 0 || k == 15 || l == 0 || l == 15) { + double d0 = (double) ((float) j / 15.0F * 2.0F - 1.0F); + double d1 = (double) ((float) k / 15.0F * 2.0F - 1.0F); + double d2 = (double) ((float) l / 15.0F * 2.0F - 1.0F); + double d3 = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2); + d0 = d0 / d3; + d1 = d1 / d3; + d2 = d2 / d3; + float rand = eAccess.getWorld().random.nextFloat(); + if (CarpetSettings.tntRandomRange >= 0) { + rand = (float) CarpetSettings.tntRandomRange; + } + float f = eAccess.getPower() * (0.7F + rand * 0.6F); + double d4 = eAccess.getX(); + double d6 = eAccess.getY(); + double d8 = eAccess.getZ(); + + for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { + BlockPos blockpos = new BlockPos(d4, d6, d8); + BlockState BlockState = eAccess.getWorld().getBlockState(blockpos); + FluidState fluidState = eAccess.getWorld().getFluidState(blockpos); + + if (BlockState.getMaterial() != Material.AIR) { + float f2 = Math.max(BlockState.getBlock().getBlastResistance(), fluidState.getBlastResistance()); + if (eAccess.getEntity() != null) + f2 = eAccess.getEntity().getEffectiveExplosionResistance(e, eAccess.getWorld(), blockpos, BlockState, fluidState, f2); + f -= (f2 + 0.3F) * 0.3F; + } + + if (f > 0.0F && (eAccess.getEntity() == null || + eAccess.getEntity().canExplosionDestroyBlock(e, eAccess.getWorld(), blockpos, BlockState, f))) + { + affectedBlockPositionsSet.add(blockpos); + } + else if (first) { + return; + } + + first = false; + + d4 += d0 * 0.30000001192092896D; + d6 += d1 * 0.30000001192092896D; + d8 += d2 * 0.30000001192092896D; + } + } + } + } + } + } + + private static void getAffectedPositionsOnPlaneX(Explosion e, int x, int yStart, int yEnd, int zStart, int zEnd) + { + if (rayCalcDone == false) + { + final double xRel = (double) x / 15.0D * 2.0D - 1.0D; + + for (int z = zStart; z <= zEnd; ++z) + { + double zRel = (double) z / 15.0D * 2.0D - 1.0D; + + for (int y = yStart; y <= yEnd; ++y) + { + double yRel = (double) y / 15.0D * 2.0D - 1.0D; + + if (checkAffectedPosition(e, xRel, yRel, zRel)) + { + return; + } + } + } + } + } + + private static void getAffectedPositionsOnPlaneY(Explosion e, int y, int xStart, int xEnd, int zStart, int zEnd) + { + if (rayCalcDone == false) + { + final double yRel = (double) y / 15.0D * 2.0D - 1.0D; + + for (int z = zStart; z <= zEnd; ++z) + { + double zRel = (double) z / 15.0D * 2.0D - 1.0D; + + for (int x = xStart; x <= xEnd; ++x) + { + double xRel = (double) x / 15.0D * 2.0D - 1.0D; + + if (checkAffectedPosition(e, xRel, yRel, zRel)) + { + return; + } + } + } + } + } + + private static void getAffectedPositionsOnPlaneZ(Explosion e, int z, int xStart, int xEnd, int yStart, int yEnd) + { + if (rayCalcDone == false) + { + final double zRel = (double) z / 15.0D * 2.0D - 1.0D; + + for (int x = xStart; x <= xEnd; ++x) + { + double xRel = (double) x / 15.0D * 2.0D - 1.0D; + + for (int y = yStart; y <= yEnd; ++y) + { + double yRel = (double) y / 15.0D * 2.0D - 1.0D; + + if (checkAffectedPosition(e, xRel, yRel, zRel)) + { + return; + } + } + } + } + } + + private static boolean checkAffectedPosition(Explosion e, double xRel, double yRel, double zRel) + { + ExplosionAccessor eAccess = (ExplosionAccessor) e; + double len = Math.sqrt(xRel * xRel + yRel * yRel + zRel * zRel); + double xInc = (xRel / len) * 0.3; + double yInc = (yRel / len) * 0.3; + double zInc = (zRel / len) * 0.3; + float rand = eAccess.getWorld().random.nextFloat(); + float sizeRand = (CarpetSettings.tntRandomRange >= 0 ? (float) CarpetSettings.tntRandomRange : rand); + float size = eAccess.getPower() * (0.7F + sizeRand * 0.6F); + double posX = eAccess.getX(); + double posY = eAccess.getY(); + double posZ = eAccess.getZ(); + + for (float f1 = 0.3F; size > 0.0F; size -= 0.22500001F) + { + posMutable.set(posX, posY, posZ); + + // Don't query already cached positions again from the world + BlockState state = stateCache.get(posMutable); + FluidState fluid = fluidCache.get(posMutable); + BlockPos posImmutable = null; + + if (state == null) + { + posImmutable = posMutable.toImmutable(); + state = eAccess.getWorld().getBlockState(posImmutable); + stateCache.put(posImmutable, state); + fluid = eAccess.getWorld().getFluidState(posImmutable); + fluidCache.put(posImmutable, fluid); + } + + if (state.getMaterial() != Material.AIR) + { + float resistance = Math.max(state.getBlock().getBlastResistance(), fluid.getBlastResistance()); + + if (eAccess.getEntity() != null) + { + resistance = eAccess.getEntity().getEffectiveExplosionResistance(e, eAccess.getWorld(), posMutable, state, fluid, resistance); + } + + size -= (resistance + 0.3F) * 0.3F; + } + + if (size > 0.0F && (eAccess.getEntity() == null || eAccess.getEntity().canExplosionDestroyBlock(e, eAccess.getWorld(), posMutable, state, size))) + { + affectedBlockPositionsSet.add(posImmutable != null ? posImmutable : posMutable.toImmutable()); + } + else if (firstRay) + { + rayCalcDone = true; + return true; + } + + firstRay = false; + + posX += xInc; + posY += yInc; + posZ += zInc; + } + + return false; + } + + public static void setBlastChanceLocation(BlockPos p){ + blastChanceLocation = p; + } + + private static void blastCalc(Explosion e){ + ExplosionAccessor eAccess = (ExplosionAccessor) e; + if(blastChanceLocation == null || blastChanceLocation.getSquaredDistance(eAccess.getX(), eAccess.getY(), eAccess.getZ(), false) > 200) return; + chances.clear(); + for (int j = 0; j < 16; ++j) { + for (int k = 0; k < 16; ++k) { + for (int l = 0; l < 16; ++l) { + if (j == 0 || j == 15 || k == 0 || k == 15 || l == 0 || l == 15) { + double d0 = (double) ((float) j / 15.0F * 2.0F - 1.0F); + double d1 = (double) ((float) k / 15.0F * 2.0F - 1.0F); + double d2 = (double) ((float) l / 15.0F * 2.0F - 1.0F); + double d3 = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2); + d0 = d0 / d3; + d1 = d1 / d3; + d2 = d2 / d3; + float f = eAccess.getPower() * (0.7F + 0.6F); + double d4 = eAccess.getX(); + double d6 = eAccess.getY(); + double d8 = eAccess.getZ(); + boolean found = false; + + for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { + BlockPos blockpos = new BlockPos(d4, d6, d8); + BlockState BlockState = eAccess.getWorld().getBlockState(blockpos); + FluidState fluidState = eAccess.getWorld().getFluidState(blockpos); + + if (BlockState.getMaterial() != Material.AIR) { + float f2 = Math.max(BlockState.getBlock().getBlastResistance(), fluidState.getBlastResistance()); + if (eAccess.getEntity() != null) + f2 = eAccess.getEntity().getEffectiveExplosionResistance(e, eAccess.getWorld(), blockpos, BlockState, fluidState, f2); + f -= (f2 + 0.3F) * 0.3F; + } + + if (f > 0.0F && (eAccess.getEntity() == null || + eAccess.getEntity().canExplosionDestroyBlock(e, eAccess.getWorld(), blockpos, BlockState, f))) { + if(!found && blockpos.equals(blastChanceLocation)){ + chances.add(f); + found = true; + } + } + + d4 += d0 * 0.30000001192092896D; + d6 += d1 * 0.30000001192092896D; + d8 += d2 * 0.30000001192092896D; + } + } + } + } + } + + showTNTblastChance(e); + } + + private static void showTNTblastChance(Explosion e){ + ExplosionAccessor eAccess = (ExplosionAccessor) e; + double randMax = 0.6F * eAccess.getPower(); + double total = 0; + boolean fullyBlownUp = false; + boolean first = true; + int rays = 0; + for(float f3 : chances){ + rays++; + double calc = f3 - randMax; + if(calc > 0) fullyBlownUp = true; + double chancePerRay = (Math.abs(calc) / randMax); + if(!fullyBlownUp){ + if(first){ + first = false; + total = chancePerRay; + }else { + total = total * chancePerRay; + } + } + } + if(fullyBlownUp) total = 0; + double chance = 1 - total; + NumberFormat nf = NumberFormat.getNumberInstance(); + nf.setRoundingMode (RoundingMode.DOWN); + nf.setMaximumFractionDigits(2); + for(PlayerEntity player : eAccess.getWorld().getPlayers()){ + Messenger.m(player,"w Pop: ", + "c " + nf.format(chance) + " ", + "^w Chance for the block to be destroyed by the blast: " + chance, + "?" + chance, + "w Remain: ", + String.format("c %.2f ", total), + "^w Chance the block survives the blast: " + total, + "?" + total, + "w Rays: ", + String.format("c %d ", rays), + "^w TNT blast rays going through the block", + "?" + rays, + "w Size: ", + String.format("c %.1f ", eAccess.getPower()), + "^w TNT blast size", + "?" + eAccess.getPower(), + "w @: ", + String.format("c [%.1f %.1f %.1f] ", eAccess.getX(), eAccess.getY(), eAccess.getZ()), + "^w TNT blast location X:" + eAccess.getX() + " Y:" + eAccess.getY() + " Z:" + eAccess.getZ(), + "?" + eAccess.getX() + " " + eAccess.getY() + " " + eAccess.getZ() + ); + } + } +} \ No newline at end of file diff --git a/src/main/java/carpet/mixins/ExplosionAccessor.java b/src/main/java/carpet/mixins/ExplosionAccessor.java new file mode 100644 index 00000000..1a864ec6 --- /dev/null +++ b/src/main/java/carpet/mixins/ExplosionAccessor.java @@ -0,0 +1,41 @@ +package carpet.mixins; + +import net.minecraft.entity.Entity; +import net.minecraft.world.World; +import net.minecraft.world.explosion.Explosion; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Random; + +@Mixin(Explosion.class) +public interface ExplosionAccessor { + + @Accessor + boolean isCreateFire(); + + @Accessor + Explosion.DestructionType getBlockDestructionType(); + + @Accessor + World getWorld(); + + @Accessor + Random getRandom(); + + @Accessor + double getX(); + + @Accessor + double getY(); + + @Accessor + double getZ(); + + @Accessor + float getPower(); + + @Accessor + Entity getEntity(); + +} diff --git a/src/main/java/carpet/mixins/ExplosionMixin.java b/src/main/java/carpet/mixins/ExplosionMixin.java index e3a59349..182a82a2 100644 --- a/src/main/java/carpet/mixins/ExplosionMixin.java +++ b/src/main/java/carpet/mixins/ExplosionMixin.java @@ -1,5 +1,6 @@ package carpet.mixins; +import carpet.helpers.OptimizedExplosion; import carpet.settings.CarpetSettings; import net.minecraft.util.math.BlockPos; import net.minecraft.world.explosion.Explosion; @@ -18,6 +19,28 @@ public abstract class ExplosionMixin @Shadow @Final private List affectedBlocks; + + @Inject(method = "collectBlocksAndDamageEntities", at = @At("HEAD"), + cancellable = true) + private void onExplosionA(CallbackInfo ci) + { + if (CarpetSettings.optimizedTNT) + { + OptimizedExplosion.doExplosionA((Explosion) (Object) this); + ci.cancel(); + } + } + + @Inject(method = "affectWorld", at = @At("HEAD"), + cancellable = true) + private void onExplosionB(boolean spawnParticles, CallbackInfo ci) + { + if (CarpetSettings.optimizedTNT) + { + OptimizedExplosion.doExplosionB((Explosion) (Object) this, spawnParticles); + ci.cancel(); + } + } @Inject(method = "collectBlocksAndDamageEntities", at = @At(value = "INVOKE", shift = At.Shift.AFTER, target = "Ljava/util/List;addAll(Ljava/util/Collection;)Z")) diff --git a/src/main/java/carpet/mixins/TntEntityMixin.java b/src/main/java/carpet/mixins/TntEntityMixin.java index fb120b2b..26abd230 100644 --- a/src/main/java/carpet/mixins/TntEntityMixin.java +++ b/src/main/java/carpet/mixins/TntEntityMixin.java @@ -1,5 +1,6 @@ package carpet.mixins; +import carpet.fakes.TntEntityInterface; import carpet.settings.CarpetSettings; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; @@ -7,7 +8,9 @@ import net.minecraft.entity.LivingEntity; import net.minecraft.entity.TntEntity; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; +import net.minecraft.world.explosion.Explosion; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -15,15 +18,28 @@ import carpet.logging.LoggerRegistry; import carpet.logging.logHelpers.TNTLogHelper; @Mixin(TntEntity.class) -public abstract class TntEntityMixin extends Entity +public abstract class TntEntityMixin extends Entity implements TntEntityInterface { + @Shadow private int fuseTimer; + private TNTLogHelper logHelper; + private boolean mergeBool = false; + private int mergedTNT = 1; public TntEntityMixin(EntityType entityType_1, World world_1) { super(entityType_1, world_1); } + + @Inject(method = "(Lnet/minecraft/world/World;DDDLnet/minecraft/entity/LivingEntity;)V", + at = @At("RETURN")) + private void modifyTNTAngle(World world, double x, double y, double z, LivingEntity entity, CallbackInfo ci) + { + if (CarpetSettings.hardcodeTNTangle != -1) + setVelocity(-Math.sin(CarpetSettings.hardcodeTNTangle) * 0.02, 0.2, -Math.cos(CarpetSettings.hardcodeTNTangle) * 0.02); + } + @Inject(method = "(Lnet/minecraft/entity/EntityType;Lnet/minecraft/world/World;)V", at = @At("RETURN")) private void initTNTLoggerPrime(EntityType entityType_1, World world_1, CallbackInfo ci) { @@ -57,5 +73,47 @@ public abstract class TntEntityMixin extends Entity { if (LoggerRegistry.__tnt && logHelper != null) logHelper.onExploded(x, y, z); + + if (mergedTNT > 1) + for (int i = 0; i < mergedTNT - 1; i++) + this.world.createExplosion(this, this.x, this.y + (double)(this.getHeight() / 16.0F), this.z, 4.0F, Explosion.DestructionType.BREAK); + } + + @Inject(method = "tick", at = @At(value = "INVOKE", + target = "Lnet/minecraft/entity/TntEntity;setVelocity(Lnet/minecraft/util/math/Vec3d;)V", + ordinal = 2)) + private void tryMergeTnt(CallbackInfo ci) + { + // Merge code for combining tnt into a single entity if they happen to exist in the same spot, same fuse, no motion CARPET-XCOM + if(CarpetSettings.mergeTNT){ + Vec3d velocity = getVelocity(); + if(!world.isClient && mergeBool && velocity.x == 0 && velocity.y == 0 && velocity.z == 0){ + mergeBool = false; + for(Entity entity : world.getEntities(this, this.getBoundingBox())){ + if(entity instanceof TntEntity && !entity.removed){ + TntEntity entityTNTPrimed = (TntEntity)entity; + Vec3d tntVelocity = entityTNTPrimed.getVelocity(); + if(tntVelocity.x == 0 && tntVelocity.y == 0 && tntVelocity.z == 0 + && this.x == entityTNTPrimed.x && this.z == entityTNTPrimed.z && this.y == entityTNTPrimed.y + && this.fuseTimer == entityTNTPrimed.getFuseTimer()){ + mergedTNT += ((TntEntityInterface) entityTNTPrimed).getMergedTNT(); + entityTNTPrimed.remove(); + } + } + } + } + } + } + + @Inject(method = "tick", at = @At(value = "FIELD", + target = "Lnet/minecraft/entity/TntEntity;fuseTimer:I", + ordinal = 0)) + private void setMergeable(CallbackInfo ci) + { + // Merge code, merge only tnt that have had a chance to move CARPET-XCOM + Vec3d velocity = getVelocity(); + if(!world.isClient && (velocity.y != 0 || velocity.x != 0 || velocity.z != 0)){ + mergeBool = true; + } } } \ No newline at end of file diff --git a/src/main/java/carpet/settings/CarpetSettings.java b/src/main/java/carpet/settings/CarpetSettings.java index abb48eb8..30a4e8e7 100644 --- a/src/main/java/carpet/settings/CarpetSettings.java +++ b/src/main/java/carpet/settings/CarpetSettings.java @@ -17,13 +17,7 @@ import net.minecraft.world.dimension.DimensionType; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import static carpet.settings.RuleCategory.BUGFIX; -import static carpet.settings.RuleCategory.COMMAND; -import static carpet.settings.RuleCategory.CREATIVE; -import static carpet.settings.RuleCategory.EXPERIMENTAL; -import static carpet.settings.RuleCategory.FEATURE; -import static carpet.settings.RuleCategory.OPTIMIZATION; -import static carpet.settings.RuleCategory.SURVIVAL; +import static carpet.settings.RuleCategory.*; public class CarpetSettings { @@ -89,9 +83,53 @@ public class CarpetSettings @Rule( desc = "Explosions won't destroy blocks", category = CREATIVE ) public static boolean explosionNoBlockDamage = false; - @Rule( desc = "Removes random TNT momentum when primed", category = CREATIVE ) + @Rule( desc = "Removes random TNT momentum when primed", category = {CREATIVE, TNT} ) public static boolean tntPrimerMomentumRemoved = false; + @Rule( desc = "TNT causes less lag when exploding in the same spot and in liquids", category = TNT) + public static boolean optimizedTNT = false; + + @Rule( desc = "Sets the tnt random explosion range to a fixed value", category = TNT, options = "-1", + validate = TNTRandomRangeValidator.class, extra = "Set to -1 for default behavior") + public static double tntRandomRange = -1; + + private static class TNTRandomRangeValidator extends Validator { + @Override + public Double validate(ServerCommandSource source, ParsedRule currentRule, Double newValue, String string) { + if (newValue == -1) + return newValue; + if (!optimizedTNT) + return null; + return newValue >= 0 ? newValue : null; + } + + @Override + public String description() { + if (!optimizedTNT) + return "Requires optimizedTNT to be enabled"; + return "Cannot be negative, except for -1"; + } + } + + @Rule( desc = "Sets the horizontal random angle on TNT for debugging of TNT contraptions", category = TNT, options = "-1", + validate = TNTAngleValidator.class, extra = "Set to -1 for default behavior") + public static double hardcodeTNTangle; + + private static class TNTAngleValidator extends Validator { + @Override + public Double validate(ServerCommandSource source, ParsedRule currentRule, Double newValue, String string) { + return (newValue >= 0 && newValue < Math.PI * 2) || newValue == -1 ? newValue : null; + } + + @Override + public String description() { + return "Must be between 0 and 2pi, or -1"; + } + } + + @Rule( desc = "Merges stationary primed TNT entities", category = TNT ) + public static boolean mergeTNT = false; + @Rule( desc = "Lag optimizations for redstone dust", extra = "by Theosib", diff --git a/src/main/java/carpet/settings/ParsedRule.java b/src/main/java/carpet/settings/ParsedRule.java index e840f6b3..8a6b2708 100644 --- a/src/main/java/carpet/settings/ParsedRule.java +++ b/src/main/java/carpet/settings/ParsedRule.java @@ -89,6 +89,10 @@ public final class ParsedRule implements Comparable { { return set(source, (T) (Object) Integer.parseInt(value), value); } + else if (type == double.class) + { + return set(source, (T) (Object) Double.parseDouble(value), value); + } else if (type.isEnum()) { String ucValue = value.toUpperCase(Locale.ROOT); @@ -111,6 +115,7 @@ public final class ParsedRule implements Comparable { if (value == null) { Messenger.m(source, "r Wrong value for " + name + ": " + stringValue); + Messenger.m(source, "r " + validator.description()); return null; } } diff --git a/src/main/java/carpet/settings/RuleCategory.java b/src/main/java/carpet/settings/RuleCategory.java index c5da5add..12a783d0 100644 --- a/src/main/java/carpet/settings/RuleCategory.java +++ b/src/main/java/carpet/settings/RuleCategory.java @@ -9,5 +9,6 @@ public class RuleCategory public static final String OPTIMIZATION = "optimization"; public static final String FEATURE = "feature"; public static final String COMMAND = "command"; + public static final String TNT = "tnt"; } diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index 901905a0..9a558b75 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -103,6 +103,7 @@ "ItemEntityMixin", "TntBlockMixin", "ExplosionMixin", + "ExplosionAccessor", "TntEntityMixin", "ProjectileEntityMixin",