From b2f1e7924340812e4b6b73edb83d24b3dbe7db58 Mon Sep 17 00:00:00 2001 From: GulpieEXE Date: Sat, 25 Oct 2025 23:38:36 -0400 Subject: [PATCH 1/7] Update MinerMobs.java For respawning config and helper methods in respawn logic --- .../modules/mobs/miner/MinerMobs.java | 190 ++++++++++-------- 1 file changed, 109 insertions(+), 81 deletions(-) diff --git a/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/MinerMobs.java b/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/MinerMobs.java index bff52d0e..96bedde6 100644 --- a/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/MinerMobs.java +++ b/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/MinerMobs.java @@ -19,6 +19,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; import net.minecraftforge.common.ForgeMod; import net.minecraftforge.event.entity.EntityAttributeModificationEvent; import net.minecraftforge.event.entity.EntityJoinLevelEvent; @@ -29,94 +30,121 @@ @LoadFeature(module = Modules.Ids.MOBS, description = "Mobs can mine blocks to reach the target. Uses offhand item to mine. Only mobs in the entity type tag enhancedai:mobs/can_mine can spawn with the ability to mine and blocks in the tag enhancedai:miner_blacklist cannot be mined. This feature also adds the block reach attribute to all entities.") public class MinerMobs extends Feature { - public static final TagKey> CAN_BE_MINER = TagKey.create(Registries.ENTITY_TYPE, EnhancedAI.location("mobs/can_mine")); - public static final TagKey BLOCK_BLACKLIST = TagKey.create(Registries.BLOCK, EnhancedAI.location("miner_blacklist")); + public static final TagKey> CAN_BE_MINER = TagKey.create(Registries.ENTITY_TYPE, EnhancedAI.location("mobs/can_mine")); + public static final TagKey BLOCK_BLACKLIST = TagKey.create(Registries.BLOCK, EnhancedAI.location("miner_blacklist")); - @Config(min = 0d, max = 1d, description = "Chance for a mob in the entity type tag enhancedai:can_be_miner to spawn with the miner ability") - public static Double minerChance = 0.07d; - @Config(description = "NONE: No item is required. ANY_TOOL: Any item that can break blocks in the off-hand is required. CORRECT_TOOL_FOR_REQUIRED: The mob is able to mine any blocks that don't require a tool, and require a tool for blocks that require it (e.g. can always mine dirt but can't mine stone). CORRECT_TOOL_FOR_ANY_BLOCK: The mob can only mine blocks if the tool is the right one for the block (e.g. can mine dirt only with a shovel).") - public static ToolRequirement toolRequirement = ToolRequirement.CORRECT_TOOL_FOR_REQUIRED; - @Config(min = -512, max = 1024, description = "Mobs can mine from the bottom of the world to this Y level.") - public static Integer maxY = 320; - @Config(min = 0, max = 128, description = "The maximum distance from the target at which the Mobs can mine. Set to 0 to always mine.") - public static Integer maxTargetDistance = 0; - @Config(min = 0d, max = 128d, description = "Multiplier for the time a mob takes to break blocks. E.g. with this set to 2, mobs will take twice the time to mine a block.") - public static Double timeToBreakMultiplier = 1.25d; - @Config(description = "Dimensions where mobs can mine.") - public static List dimensionWhitelist = List.of("minecraft:overworld", "minecraft:the_nether", "minecraft:the_end"); - @Config(description = "If true, the block tag `enhancedai:miner_mobs/blacklist` will be treated as a whitelist instead of blacklist") - public static Boolean blockBlacklistAsWhitelist = false; - @Config(description = "Mobs with Miner AI will not be able to break tile entities") - public static Boolean blacklistTileEntities = true; - @Config(description = "Mobs with Miner AI will spawn with a Stone Pickaxe that never drops.") - public static Boolean equipStonePick = true; + @Config(min = 0d, max = 1d, description = "Chance for a mob in the entity type tag enhancedai:can_be_miner to spawn with the miner ability") + public static Double minerChance = 0.07d; + @Config(description = "NONE: No item is required. ANY_TOOL: Any item that can break blocks in the off-hand is required. CORRECT_TOOL_FOR_REQUIRED: The mob is able to mine any blocks that don't require a tool, and require a tool for blocks that require it (e.g. can always mine dirt but can't mine stone). CORRECT_TOOL_FOR_ANY_BLOCK: The mob can only mine blocks if the tool is the right one for the block (e.g. can mine dirt only with a shovel).") + public static ToolRequirement toolRequirement = ToolRequirement.CORRECT_TOOL_FOR_REQUIRED; + @Config(min = -512, max = 1024, description = "Mobs can mine from the bottom of the world to this Y level.") + public static Integer maxY = 320; + @Config(min = 0, max = 128, description = "The maximum distance from the target at which the Mobs can mine. Set to 0 to always mine.") + public static Integer maxTargetDistance = 0; + @Config(min = 0d, max = 128d, description = "Multiplier for the time a mob takes to break blocks. E.g. with this set to 2, mobs will take twice the time to mine a block.") + public static Double timeToBreakMultiplier = 1.25d; + @Config(description = "Dimensions where mobs can mine.") + public static List dimensionWhitelist = List.of("minecraft:overworld", "minecraft:the_nether", "minecraft:the_end"); + @Config(description = "If true, the block tag `enhancedai:miner_mobs/blacklist` will be treated as a whitelist instead of blacklist") + public static Boolean blockBlacklistAsWhitelist = false; + @Config(description = "Mobs with Miner AI will not be able to break tile entities") + public static Boolean blacklistTileEntities = true; + @Config(description = "Mobs with Miner AI will spawn with a Stone Pickaxe that never drops.") + public static Boolean equipStonePick = true; + @Config(min = 0, max = 1200, description = "Time in ticks for a mined block to respawn. Set to 0 for no respawn. (20 ticks = 1 second)") + public static Integer blockRespawnTime = 0; + @Config(description = "If true, block respawn time will scale based on the block's hardness.") + public static Boolean scaleRespawnByHardness = false; + @Config(min = 0, max = 1200, description = "Base respawn time in ticks for a block (used if scaling by hardness is enabled).") + public static Integer baseRespawnTime = 200; + @Config(min = 0d, max = 100d, description = "Multiplier applied to the block's hardness when calculating respawn time (used if scaling by hardness is enabled).") + public static Double hardnessRespawnMultiplier = 100d; - public static EAIData MINER; - public static EAIDataEnum TOOL_REQUIREMENT; - public static EAIData MAX_Y; - public static EAIData MAX_TARGET_DISTANCE; - public static EAIData TIME_TO_BREAK_MULTIPLIER; - public static EAIDataList DIMENSION_WHITELIST; + public static EAIData MINER; + public static EAIDataEnum TOOL_REQUIREMENT; + public static EAIData MAX_Y; + public static EAIData MAX_TARGET_DISTANCE; + public static EAIData TIME_TO_BREAK_MULTIPLIER; + public static EAIDataList DIMENSION_WHITELIST; + public static EAIData BLOCK_RESPAWN_TIME; + public static EAIData SCALE_RESPAWN_BY_HARDNESS; + public static EAIData BASE_RESPAWN_TIME; + public static EAIData HARDNESS_RESPAWN_MULTIPLIER; - public void init(Module module, boolean enabledByDefault, boolean canBeDisabled) { - super.init(module, enabledByDefault, canBeDisabled); - MINER = EAIData.ofBool(this.createDataKey("miner"), (mob, miner) -> { - GoalHelper.removeGoal(mob.goalSelector, MineTowardsTargetGoal.class); - if (miner) - mob.goalSelector.addGoal(1, new MineTowardsTargetGoal(mob)); - }); - TOOL_REQUIREMENT = EAIDataEnum.of(this.createDataKey("tool_requirement"), ToolRequirement.class); - MAX_Y = EAIData.ofInt(this.createDataKey("max_y")); - MAX_TARGET_DISTANCE = EAIData.ofInt(this.createDataKey("max_target_distance")); - TIME_TO_BREAK_MULTIPLIER = EAIData.ofDouble(this.createDataKey("time_to_break_multiplier")); - DIMENSION_WHITELIST = EAIDataList.of(this.createDataKey("dimension_whitelist"), String.class); - } + public void init(Module module, boolean enabledByDefault, boolean canBeDisabled) { + super.init(module, enabledByDefault, canBeDisabled); + MINER = EAIData.ofBool(this.createDataKey("miner"), (mob, miner) -> { + GoalHelper.removeGoal(mob.goalSelector, MineTowardsTargetGoal.class); + if (miner) + mob.goalSelector.addGoal(1, new MineTowardsTargetGoal(mob)); + }); + TOOL_REQUIREMENT = EAIDataEnum.of(this.createDataKey("tool_requirement"), ToolRequirement.class); + MAX_Y = EAIData.ofInt(this.createDataKey("max_y")); + MAX_TARGET_DISTANCE = EAIData.ofInt(this.createDataKey("max_target_distance")); + TIME_TO_BREAK_MULTIPLIER = EAIData.ofDouble(this.createDataKey("time_to_break_multiplier")); + DIMENSION_WHITELIST = EAIDataList.of(this.createDataKey("dimension_whitelist"), String.class); + BLOCK_RESPAWN_TIME = EAIData.ofInt(this.createDataKey("block_respawn_time")); + SCALE_RESPAWN_BY_HARDNESS = EAIData.ofBool(this.createDataKey("scale_respawn_by_hardness")); + BASE_RESPAWN_TIME = EAIData.ofInt(this.createDataKey("base_respawn_time")); + HARDNESS_RESPAWN_MULTIPLIER = EAIData.ofDouble(this.createDataKey("hardness_respawn_multiplier")); + net.minecraftforge.common.MinecraftForge.EVENT_BUS.register(MineTowardsTargetGoal.class); + } - public static void addAttribute(EntityAttributeModificationEvent event) { - for (EntityType entityType : event.getTypes()) { - if (event.has(entityType, ForgeMod.BLOCK_REACH.get())) - continue; + public static void addAttribute(EntityAttributeModificationEvent event) { + for (EntityType entityType : event.getTypes()) { + if (event.has(entityType, ForgeMod.BLOCK_REACH.get())) + continue; - event.add(entityType, ForgeMod.BLOCK_REACH.get()); - } - } + event.add(entityType, ForgeMod.BLOCK_REACH.get()); + } + } - //Lowest priority so other mods can set persistent data - @SubscribeEvent(priority = EventPriority.LOWEST) - public void onSpawn(EntityJoinLevelEvent event) { - if (!this.isEnabled() - || event.getLevel().isClientSide - || !(event.getEntity() instanceof Mob mob) - || !mob.getType().is(CAN_BE_MINER)) - return; + //Lowest priority so other mods can set persistent data + @SubscribeEvent(priority = EventPriority.LOWEST) + public void onSpawn(EntityJoinLevelEvent event) { + if (!this.isEnabled() + || event.getLevel().isClientSide + || !(event.getEntity() instanceof Mob mob) + || !mob.getType().is(CAN_BE_MINER)) + return; - boolean isMiner = mob.getRandom().nextDouble() < minerChance; - MINER.applyIfAbsent(mob, isMiner); - if (isMiner && equipStonePick && mob.getOffhandItem().isEmpty()) - { - mob.setItemSlot(EquipmentSlot.OFFHAND, new ItemStack(Items.STONE_PICKAXE)); - mob.setDropChance(EquipmentSlot.OFFHAND, -1f); - } - TOOL_REQUIREMENT.applyIfAbsent(mob, toolRequirement); - MAX_Y.applyIfAbsent(mob, maxY); - MAX_TARGET_DISTANCE.applyIfAbsent(mob, maxTargetDistance); - TIME_TO_BREAK_MULTIPLIER.applyIfAbsent(mob, timeToBreakMultiplier); - DIMENSION_WHITELIST.applyIfAbsent(mob, dimensionWhitelist); - } + boolean isMiner = mob.getRandom().nextDouble() < minerChance; + MINER.applyIfAbsent(mob, isMiner); + if (isMiner && equipStonePick && mob.getOffhandItem().isEmpty()) + { + mob.setItemSlot(EquipmentSlot.OFFHAND, new ItemStack(Items.STONE_PICKAXE)); + mob.setDropChance(EquipmentSlot.OFFHAND, -1f); + } + TOOL_REQUIREMENT.applyIfAbsent(mob, toolRequirement); + MAX_Y.applyIfAbsent(mob, maxY); + MAX_TARGET_DISTANCE.applyIfAbsent(mob, maxTargetDistance); + TIME_TO_BREAK_MULTIPLIER.applyIfAbsent(mob, timeToBreakMultiplier); + DIMENSION_WHITELIST.applyIfAbsent(mob, dimensionWhitelist); + BLOCK_RESPAWN_TIME.applyIfAbsent(mob, blockRespawnTime); + SCALE_RESPAWN_BY_HARDNESS.applyIfAbsent(mob, scaleRespawnByHardness); + BASE_RESPAWN_TIME.applyIfAbsent(mob, baseRespawnTime); + HARDNESS_RESPAWN_MULTIPLIER.applyIfAbsent(mob, hardnessRespawnMultiplier); - public static boolean isValidDimension(Mob mob) { - List dimensionWhitelist = DIMENSION_WHITELIST.get(mob); - for (String dimension : dimensionWhitelist) { - if (dimension.equals(mob.level().dimension().location().toString())) - return true; - } - return false; - } + } - public enum ToolRequirement { - NONE, - ANY_TOOL, - CORRECT_TOOL_FOR_REQUIRED, - CORRECT_TOOL_FOR_ANY_BLOCK - } + public static boolean isValidDimension(Mob mob) { + List dimensionWhitelist = DIMENSION_WHITELIST.get(mob); + for (String dimension : dimensionWhitelist) { + if (dimension.equals(mob.level().dimension().location().toString())) + return true; + } + return false; + } + + public enum ToolRequirement { + NONE, + ANY_TOOL, + CORRECT_TOOL_FOR_REQUIRED, + CORRECT_TOOL_FOR_ANY_BLOCK + } + public static boolean shouldSaveBlockNBT(BlockState state) { + if (state.hasBlockEntity()) return true; + // Could add a tag whitelist or any other dynamic condition + return false; + } } From 02c93dc9b87ee771be4737ef4823a9be3ef3e532 Mon Sep 17 00:00:00 2001 From: GulpieEXE Date: Sat, 25 Oct 2025 23:40:31 -0400 Subject: [PATCH 2/7] Update MineTowardsTargetGoal.java Added functionality to tick() for block respawning if enabled --- .../mobs/miner/MineTowardsTargetGoal.java | 503 ++++++++++-------- 1 file changed, 278 insertions(+), 225 deletions(-) diff --git a/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/MineTowardsTargetGoal.java b/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/MineTowardsTargetGoal.java index 64c72fad..993d04ad 100644 --- a/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/MineTowardsTargetGoal.java +++ b/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/MineTowardsTargetGoal.java @@ -2,6 +2,7 @@ import insane96mcp.enhancedai.modules.mobs.MeleeAttacking; import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundSource; import net.minecraft.util.Mth; @@ -29,239 +30,291 @@ import net.minecraft.world.phys.Vec3; import net.minecraftforge.common.ForgeMod; import net.minecraftforge.event.ForgeEventFactory; +import insane96mcp.enhancedai.EnhancedAI; +import insane96mcp.enhancedai.modules.mobs.miner.persistence.BlockRespawnData; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.List; +import static insane96mcp.enhancedai.modules.mobs.miner.MinerMobs.blacklistTileEntities; + public class MineTowardsTargetGoal extends Goal { + private final Mob miner; + private LivingEntity target; + private final double reachDistance; + private final List targetBlocks = new ArrayList<>(); + private int tickToBreak = 0; + private int breakingTick = 0; + private BlockState blockState = null; + private int prevBreakProgress = 0; + + private Vec3 lastPosition = null; + private int lastPositionTickstamp = 0; + + private Path path = null; + + public MineTowardsTargetGoal(Mob miner) { + this.miner = miner; + this.reachDistance = miner.getAttribute(ForgeMod.BLOCK_REACH.get()) == null ? 4.5 : miner.getAttributeValue(ForgeMod.BLOCK_REACH.get()); + this.setFlags(EnumSet.of(Flag.LOOK, Flag.MOVE)); + } + + + + public boolean canUse() { + if (!MinerMobs.isValidDimension(this.miner) + || !this.miner.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) + || (MinerMobs.TOOL_REQUIREMENT.get(this.miner) == MinerMobs.ToolRequirement.ANY_TOOL && !(this.miner.getOffhandItem().getItem() instanceof DiggerItem)) + || this.miner.getTarget() == null) + return false; + float maxTargetDistance = MinerMobs.MAX_TARGET_DISTANCE.get(this.miner); + maxTargetDistance *= maxTargetDistance; + return this.isStuck() + && (!MeleeAttacking.isWithinMeleeAttackRange(this.miner, this.miner.getTarget()) || !this.miner.getSensing().hasLineOfSight(this.miner.getTarget())) + && (this.miner.distanceToSqr(miner.getTarget()) < maxTargetDistance || maxTargetDistance == 0); + } + + + public boolean canContinueToUse() { + if (this.targetBlocks.isEmpty()) + return false; + if (this.blockState != null && !this.canBreakBlock()) + return false; + if (this.target == null || !this.target.isAlive()) + return false; + + return this.targetBlocks.get(0).distSqr(this.miner.blockPosition()) < this.reachDistance * this.reachDistance + && this.miner.getNavigation().isDone() + && !this.miner.level().getBlockState(this.targetBlocks.get(0)).isAir() + && this.path != null && (this.path.getDistToTarget() > 1.5d || !this.miner.hasLineOfSight(this.target)); + } + + + public void start() { + this.target = this.miner.getTarget(); + if (this.target == null) + return; + fillTargetBlocks(); + if (!this.targetBlocks.isEmpty()) { + initBlockBreak(); + } + } + + + public void stop() { + this.target = null; + if (!this.targetBlocks.isEmpty()) { + this.miner.level().destroyBlockProgress(this.miner.getId(), targetBlocks.get(0), -1); + this.targetBlocks.clear(); + } + this.tickToBreak = 0; + this.breakingTick = 0; + this.blockState = null; + this.prevBreakProgress = 0; + this.lastPosition = null; + this.path = null; + this.miner.setAggressive(false); + } + + + public void tick() { + if (this.targetBlocks.isEmpty()) + return; + if (this.blockState != null && !this.canBreakBlock()) + return; + this.miner.setAggressive(true); + BlockPos pos = this.targetBlocks.get(0); + this.breakingTick++; + this.miner.getLookControl().setLookAt(pos.getX() + 0.5d, pos.getY() + 0.5d, pos.getZ() + 0.5d); + + // progress visuals + int progress = (int) ((this.breakingTick / (float) this.tickToBreak) * 10); + if (this.prevBreakProgress != progress) { + this.prevBreakProgress = progress; + this.miner.level().destroyBlockProgress(this.miner.getId(), pos, progress); + } + + if (this.breakingTick % 6 == 0) + this.miner.swing(InteractionHand.MAIN_HAND); + + if (this.breakingTick % 4 == 0) { + SoundType soundType = this.blockState.getSoundType(this.miner.level(), pos, this.miner); + this.miner.level().playSound(null, pos, soundType.getHitSound(), SoundSource.BLOCKS, + (soundType.getVolume() + 1.0F) / 8.0F, soundType.getPitch() * 0.5F); + } + + // --- Respawn-aware block breaking --- + if (this.breakingTick >= this.tickToBreak && this.miner.level() instanceof ServerLevel level) { + if (!ForgeEventFactory.onEntityDestroyBlock(this.miner, pos, this.blockState)) return; + + int respawnTime = MinerMobs.BLOCK_RESPAWN_TIME.get(this.miner); + boolean scaleByHardness = MinerMobs.SCALE_RESPAWN_BY_HARDNESS.get(this.miner); + + if (scaleByHardness) { + double hardness = Math.max(0, this.blockState.getDestroySpeed(level, pos)); + int baseTime = MinerMobs.BASE_RESPAWN_TIME.get(this.miner); + double multiplier = MinerMobs.HARDNESS_RESPAWN_MULTIPLIER.get(this.miner); + respawnTime = (int) Math.ceil(baseTime + hardness * multiplier); + } + + boolean willRespawn = respawnTime > 0; + + CompoundTag blockNbt = null; + if (willRespawn && MinerMobs.shouldSaveBlockNBT(this.blockState)) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity != null) + blockNbt = blockEntity.saveWithFullMetadata(); + } + + if (willRespawn) { + // Remove block without drops, record for respawn + level.removeBlockEntity(pos); + level.setBlock(pos, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState(), 3); + + long respawnAt = level.getGameTime() + respawnTime; + BlockRespawnData data = BlockRespawnData.get(level); + data.set(pos, respawnAt, this.blockState, blockNbt); + + EnhancedAI.LOGGER.debug("Scheduled respawn for block {} at {} after {} ticks", + this.blockState.getBlock().getName().getString(), pos, respawnTime); + } + else { + // Normal destruction with drops + if (this.miner.getItemBySlot(EquipmentSlot.OFFHAND).isCorrectToolForDrops(this.blockState)) { + BlockEntity blockEntity = this.blockState.hasBlockEntity() ? level.getBlockEntity(pos) : null; + LootParams.Builder lootparams = (new LootParams.Builder(level)) + .withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos)) + .withParameter(LootContextParams.TOOL, this.miner.getOffhandItem()) + .withOptionalParameter(LootContextParams.BLOCK_ENTITY, blockEntity) + .withOptionalParameter(LootContextParams.THIS_ENTITY, this.miner); + this.blockState.spawnAfterBreak(level, pos, this.miner.getOffhandItem(), false); + this.blockState.getDrops(lootparams).forEach(stack -> + level.addFreshEntity(new ItemEntity(level, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, stack))); + level.removeBlock(pos, false); + } + } + + this.miner.level().destroyBlockProgress(this.miner.getId(), pos, -1); + this.targetBlocks.remove(0); + if (!this.targetBlocks.isEmpty()) + initBlockBreak(); + else if (this.miner.distanceToSqr(this.target) > 2d && !this.miner.getSensing().hasLineOfSight(this.target)) + start(); + } + } + + private void initBlockBreak() { + this.blockState = this.miner.level().getBlockState(this.targetBlocks.get(0)); + this.tickToBreak = computeTickToBreak(); + this.breakingTick = 0; + this.path = this.miner.getNavigation().createPath(this.target, 1); + } + + private void fillTargetBlocks() { + int mobHeight = Mth.ceil(this.miner.getBbHeight()); + for (int i = 0; i < mobHeight; i++) { + BlockHitResult rayTrace = this.miner.level().clip(new ClipContext(this.miner.position().add(0, i + 0.5d, 0), this.target.getEyePosition(1f).add(0, i, 0), ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this.miner)); + if (rayTrace.getType() == HitResult.Type.MISS + || this.targetBlocks.contains(rayTrace.getBlockPos()) + || rayTrace.getBlockPos().getY() > MinerMobs.MAX_Y.get(this.miner)) + continue; + + double distance = this.miner.distanceToSqr(rayTrace.getLocation()); + if (distance > this.reachDistance * this.reachDistance) + continue; + + BlockState state = this.miner.level().getBlockState(rayTrace.getBlockPos()); - private final Mob miner; - private LivingEntity target; - private final double reachDistance; - private final List targetBlocks = new ArrayList<>(); - private int tickToBreak = 0; - private int breakingTick = 0; - private BlockState blockState = null; - private int prevBreakProgress = 0; - - private Vec3 lastPosition = null; - private int lastPositionTickstamp = 0; - - private Path path = null; - - public MineTowardsTargetGoal(Mob miner){ - this.miner = miner; - this.reachDistance = miner.getAttribute(ForgeMod.BLOCK_REACH.get()) == null ? 4.5 : miner.getAttributeValue(ForgeMod.BLOCK_REACH.get()); - this.setFlags(EnumSet.of(Flag.LOOK, Flag.MOVE)); - } - - public boolean canUse() { - if (!MinerMobs.isValidDimension(this.miner) - || !this.miner.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) - || (MinerMobs.TOOL_REQUIREMENT.get(this.miner) == MinerMobs.ToolRequirement.ANY_TOOL && !(this.miner.getOffhandItem().getItem() instanceof DiggerItem)) - || this.miner.getTarget() == null) - return false; - float maxTargetDistance = MinerMobs.MAX_TARGET_DISTANCE.get(this.miner); - maxTargetDistance *= maxTargetDistance; - return this.isStuck() - && (!MeleeAttacking.isWithinMeleeAttackRange(this.miner, this.miner.getTarget()) || !this.miner.getSensing().hasLineOfSight(this.miner.getTarget())) - && (this.miner.distanceToSqr(miner.getTarget()) < maxTargetDistance || maxTargetDistance == 0); - } - - public boolean canContinueToUse() { - if (this.targetBlocks.isEmpty()) - return false; - if (this.blockState != null && !this.canBreakBlock()) - return false; - - if (this.target == null || !this.target.isAlive()) - return false; - - return this.targetBlocks.get(0).distSqr(this.miner.blockPosition()) < this.reachDistance * this.reachDistance - && this.miner.getNavigation().isDone() - && !this.miner.level().getBlockState(this.targetBlocks.get(0)).isAir() - && this.path != null && (this.path.getDistToTarget() > 1.5d || !this.miner.hasLineOfSight(this.target)); - } - - public void start() { - this.target = this.miner.getTarget(); - if (this.target == null) - return; - fillTargetBlocks(); - if (!this.targetBlocks.isEmpty()) { - initBlockBreak(); - } - } - - public void stop() { - this.target = null; - if (!this.targetBlocks.isEmpty()) { - this.miner.level().destroyBlockProgress(this.miner.getId(), targetBlocks.get(0), -1); - this.targetBlocks.clear(); - } - this.tickToBreak = 0; - this.breakingTick = 0; - this.blockState = null; - this.prevBreakProgress = 0; - this.lastPosition = null; - this.path = null; - this.miner.setAggressive(false); - } - - public void tick() { - if (this.targetBlocks.isEmpty()) - return; - if (this.blockState != null && !this.canBreakBlock()) - return; - this.miner.setAggressive(true); - BlockPos pos = this.targetBlocks.get(0); - this.breakingTick++; - this.miner.getLookControl().setLookAt(pos.getX() + 0.5d, pos.getY() + 0.5d, pos.getZ() + 0.5d); - if (this.prevBreakProgress != (int) ((this.breakingTick / (float) this.tickToBreak) * 10)) { - this.prevBreakProgress = (int) ((this.breakingTick / (float) this.tickToBreak) * 10); - this.miner.level().destroyBlockProgress(this.miner.getId(), pos, this.prevBreakProgress); - } - if (this.breakingTick % 6 == 0) { - this.miner.swing(InteractionHand.MAIN_HAND); - } - if (this.breakingTick % 4 == 0) { - SoundType soundType = this.blockState.getSoundType(this.miner.level(), pos, this.miner); - this.miner.level().playSound(null, pos, soundType.getHitSound(), SoundSource.BLOCKS, (soundType.getVolume() + 1.0F) / 8.0F, soundType.getPitch() * 0.5F); - } - if (this.breakingTick >= this.tickToBreak && this.miner.level() instanceof ServerLevel level) { - if (ForgeEventFactory.onEntityDestroyBlock(this.miner, this.targetBlocks.get(0), this.blockState) && this.miner.level().destroyBlock(pos, false, this.miner) && (!this.blockState.requiresCorrectToolForDrops() || this.miner.getItemBySlot(EquipmentSlot.OFFHAND).isCorrectToolForDrops(this.blockState))) { - BlockEntity blockentity = this.blockState.hasBlockEntity() ? this.miner.level().getBlockEntity(pos) : null; - LootParams.Builder lootparams$builder = (new LootParams.Builder(level)).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos)).withParameter(LootContextParams.TOOL, this.miner.getOffhandItem()).withOptionalParameter(LootContextParams.BLOCK_ENTITY, blockentity).withOptionalParameter(LootContextParams.THIS_ENTITY, this.miner); - this.blockState.spawnAfterBreak(level, pos, this.miner.getOffhandItem(), false); - this.blockState.getDrops(lootparams$builder).forEach((itemStack) -> level.addFreshEntity(new ItemEntity(level, pos.getX() + 0.5f, pos.getY() + 0.5f, pos.getZ() + 0.5f, itemStack))); - } - this.miner.level().destroyBlockProgress(this.miner.getId(), pos, -1); - this.targetBlocks.remove(0); - if (!this.targetBlocks.isEmpty()) - initBlockBreak(); - else if (this.miner.distanceToSqr(this.target) > 2d && !this.miner.getSensing().hasLineOfSight(this.target)) - start(); - } - } - - private void initBlockBreak() { - this.blockState = this.miner.level().getBlockState(this.targetBlocks.get(0)); - this.tickToBreak = computeTickToBreak(); - this.breakingTick = 0; - this.path = this.miner.getNavigation().createPath(this.target, 1); - } - - private void fillTargetBlocks() { - int mobHeight = Mth.ceil(this.miner.getBbHeight()); - for (int i = 0; i < mobHeight; i++) { - BlockHitResult rayTraceResult = this.miner.level().clip(new ClipContext(this.miner.position().add(0, i + 0.5d, 0), this.target.getEyePosition(1f).add(0, i, 0), ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this.miner)); - if (rayTraceResult.getType() == HitResult.Type.MISS - || this.targetBlocks.contains(rayTraceResult.getBlockPos()) - || rayTraceResult.getBlockPos().getY() > MinerMobs.MAX_Y.get(this.miner)) + if (state.getDestroySpeed(this.miner.level(), rayTrace.getBlockPos()) == -1 + || (state.hasBlockEntity() && blacklistTileEntities)) continue; - double distance = this.miner.distanceToSqr(rayTraceResult.getLocation()); - if (distance > this.reachDistance * this.reachDistance) - continue; - - BlockState state = this.miner.level().getBlockState(rayTraceResult.getBlockPos()); - - if (state.getDestroySpeed(this.miner.level(), rayTraceResult.getBlockPos()) == -1 - || (state.hasBlockEntity() && MinerMobs.blacklistTileEntities)) - continue; - - boolean listed = state.is(MinerMobs.BLOCK_BLACKLIST); - if (listed != MinerMobs.blockBlacklistAsWhitelist) - continue; - - this.targetBlocks.add(rayTraceResult.getBlockPos()); - } - Collections.reverse(this.targetBlocks); - } - - public boolean requiresUpdateEveryTick() { - return true; - } - - /** - * Returns true if the miner has been stuck in the same spot (radius 1.5 blocks) for more than 3 seconds - */ - public boolean isStuck() { - if (this.miner.getTarget() == null) - return false; - - if (MeleeAttacking.isWithinMeleeAttackRange(this.miner, this.miner.getTarget()) && this.miner.getSensing().hasLineOfSight(this.miner.getTarget())) - return false; - if (this.lastPosition == null || this.miner.distanceToSqr(this.lastPosition) > 2.25d) { - this.lastPosition = this.miner.position(); - this.lastPositionTickstamp = this.miner.tickCount; - } - return this.miner.getNavigation().isDone() || this.miner.tickCount - this.lastPositionTickstamp >= 60; - } - - // Copy-paste of vanilla code - private int computeTickToBreak() { - int canHarvestBlock = this.canHarvestBlock() ? 30 : 100; - double diggingSpeed = this.getDigSpeed() / this.blockState.getDestroySpeed(this.miner.level(), this.targetBlocks.get(0)) / canHarvestBlock; - return Mth.ceil((1f / diggingSpeed) * MinerMobs.TIME_TO_BREAK_MULTIPLIER.get(this.miner)); - } - - private float getDigSpeed() { - float digSpeed = this.miner.getOffhandItem().getDestroySpeed(this.blockState); - if (digSpeed > 1.0F) { - int efficiencyLevel = EnchantmentHelper.getBlockEfficiency(this.miner); - ItemStack itemstack = this.miner.getOffhandItem(); - if (efficiencyLevel > 0 && !itemstack.isEmpty()) { - digSpeed += (float)(efficiencyLevel * efficiencyLevel + 1); - } - } - - if (MobEffectUtil.hasDigSpeed(this.miner)) { - digSpeed *= 1.0F + (float)(MobEffectUtil.getDigSpeedAmplification(this.miner) + 1) * 0.2F; - } - - if (this.miner.hasEffect(MobEffects.DIG_SLOWDOWN)) { - //noinspection ConstantConditions - float miningFatigueAmplifier = switch (this.miner.getEffect(MobEffects.DIG_SLOWDOWN).getAmplifier()) { - case 0 -> 0.3F; - case 1 -> 0.09F; - case 2 -> 0.0027F; - default -> 8.1E-4F; - }; - - digSpeed *= miningFatigueAmplifier; - } - - if (this.miner.isEyeInFluidType(ForgeMod.WATER_TYPE.get()) && !EnchantmentHelper.hasAquaAffinity(this.miner)) - digSpeed /= 5.0F; - - return digSpeed; - } - - private boolean canBreakBlock() { - MinerMobs.ToolRequirement toolRequirement = MinerMobs.TOOL_REQUIREMENT.get(this.miner); - if (toolRequirement == MinerMobs.ToolRequirement.NONE || toolRequirement == MinerMobs.ToolRequirement.ANY_TOOL) - return true; - if ((toolRequirement == MinerMobs.ToolRequirement.CORRECT_TOOL_FOR_REQUIRED) && !this.blockState.requiresCorrectToolForDrops()) - return true; - - ItemStack stack = this.miner.getOffhandItem(); - if (stack.isEmpty()) - return false; - - return stack.isCorrectToolForDrops(this.blockState); - } - - private boolean canHarvestBlock() { - if (!this.blockState.requiresCorrectToolForDrops()) - return true; - - ItemStack stack = this.miner.getOffhandItem(); - if (stack.isEmpty()) - return false; - - return stack.isCorrectToolForDrops(this.blockState); - } + boolean listed = state.is(MinerMobs.BLOCK_BLACKLIST); + if (listed != MinerMobs.blockBlacklistAsWhitelist) + continue; + + this.targetBlocks.add(rayTrace.getBlockPos()); + } + Collections.reverse(this.targetBlocks); + } + + private int computeTickToBreak() { + int canHarvestBlock = this.canHarvestBlock() ? 30 : 100; + double diggingSpeed = this.getDigSpeed() / this.blockState.getDestroySpeed(this.miner.level(), this.targetBlocks.get(0)) / canHarvestBlock; + return Mth.ceil((1f / diggingSpeed) * MinerMobs.TIME_TO_BREAK_MULTIPLIER.get(this.miner)); + } + + private float getDigSpeed() { + float digSpeed = this.miner.getOffhandItem().getDestroySpeed(this.blockState); + if (digSpeed > 1.0F) { + int efficiency = EnchantmentHelper.getBlockEfficiency(this.miner); + if (efficiency > 0) digSpeed += (float) (efficiency * efficiency + 1); + } + if (MobEffectUtil.hasDigSpeed(this.miner)) + digSpeed *= 1.0F + (MobEffectUtil.getDigSpeedAmplification(this.miner) + 1) * 0.2F; + if (this.miner.hasEffect(MobEffects.DIG_SLOWDOWN)) { + float f = switch (this.miner.getEffect(MobEffects.DIG_SLOWDOWN).getAmplifier()) { + case 0 -> 0.3F; + case 1 -> 0.09F; + case 2 -> 0.0027F; + default -> 8.1E-4F; + }; + digSpeed *= f; + } + if (this.miner.isEyeInFluidType(ForgeMod.WATER_TYPE.get()) && !EnchantmentHelper.hasAquaAffinity(this.miner)) + digSpeed /= 5.0F; + return digSpeed; + } + + private boolean canBreakBlock() { + MinerMobs.ToolRequirement toolReq = MinerMobs.TOOL_REQUIREMENT.get(this.miner); + if (toolReq == MinerMobs.ToolRequirement.NONE || toolReq == MinerMobs.ToolRequirement.ANY_TOOL) + return true; + if (toolReq == MinerMobs.ToolRequirement.CORRECT_TOOL_FOR_REQUIRED && !this.blockState.requiresCorrectToolForDrops()) + return true; + ItemStack stack = this.miner.getOffhandItem(); + return !stack.isEmpty() && stack.isCorrectToolForDrops(this.blockState); + } + + private boolean canHarvestBlock() { + if (!this.blockState.requiresCorrectToolForDrops()) + return true; + ItemStack stack = this.miner.getOffhandItem(); + return !stack.isEmpty() && stack.isCorrectToolForDrops(this.blockState); + } + + public boolean requiresUpdateEveryTick() { return true; } + + private boolean isStuck() { + if (this.miner.getTarget() == null) + return false; + if (MeleeAttacking.isWithinMeleeAttackRange(this.miner, this.miner.getTarget()) + && this.miner.getSensing().hasLineOfSight(this.miner.getTarget())) + return false; + if (this.lastPosition == null || this.miner.distanceToSqr(this.lastPosition) > 2.25d) { + this.lastPosition = this.miner.position(); + this.lastPositionTickstamp = this.miner.tickCount; + } + return this.miner.getNavigation().isDone() || this.miner.tickCount - this.lastPositionTickstamp >= 60; + } + + private void freeSpaceForRespawn(ServerLevel level, BlockPos pos) { + BlockState existing = level.getBlockState(pos); + + // If block is already air, nothing to do + if (existing.isAir()) return; + + // Drop the block as an item + existing.spawnAfterBreak(level, pos, ItemStack.EMPTY, true); + level.removeBlock(pos, false); + + // Move any entities standing on the block slightly upward + level.getEntities(null, existing.getShape(level, pos).bounds().move(pos.getX(), pos.getY(), pos.getZ())) + .forEach(e -> e.setPos(e.getX(), e.getY() + 1.0, e.getZ())); + } + + + } From 0da4f003b6be8cf71fd2125c3d4dfd3638755e75 Mon Sep 17 00:00:00 2001 From: GulpieEXE Date: Sat, 25 Oct 2025 23:42:02 -0400 Subject: [PATCH 3/7] Add files via upload --- .../miner/persistence/BlockRespawnData.java | 101 ++++++++++++++++++ .../persistence/BlockRespawnHandler.java | 101 ++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 src/main/java/insane96mcp/enhancedai/modules/mobs/miner/persistence/BlockRespawnData.java create mode 100644 src/main/java/insane96mcp/enhancedai/modules/mobs/miner/persistence/BlockRespawnHandler.java diff --git a/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/persistence/BlockRespawnData.java b/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/persistence/BlockRespawnData.java new file mode 100644 index 00000000..255c5071 --- /dev/null +++ b/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/persistence/BlockRespawnData.java @@ -0,0 +1,101 @@ +package insane96mcp.enhancedai.modules.mobs.miner.persistence; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.*; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.saveddata.SavedData; +import net.minecraft.core.HolderGetter; +import net.minecraft.nbt.NbtUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class BlockRespawnData extends SavedData { + + private final Map respawnEntries = new HashMap<>(); + + public static class RespawnEntry { + public final long time; + public final BlockState state; + public final CompoundTag nbt; + + public RespawnEntry(long time, BlockState state, CompoundTag nbt) { + this.time = time; + this.state = state; + this.nbt = nbt != null ? nbt.copy() : null; + } + } + + public BlockRespawnData() {} + + // Get or create the data instance for a level + public static BlockRespawnData get(ServerLevel level) { + return level.getDataStorage().computeIfAbsent( + nbt -> load(nbt, level), + BlockRespawnData::new, + "enhancedai_block_respawns" + ); + } + + // Load from NBT (requires ServerLevel for registry access) + public static BlockRespawnData load(CompoundTag nbt, ServerLevel level) { + BlockRespawnData data = new BlockRespawnData(); + ListTag list = nbt.getList("Respawns", Tag.TAG_COMPOUND); + + HolderGetter blockRegistry = level.holderLookup(Registries.BLOCK); + + for (Tag t : list) { + if (!(t instanceof CompoundTag tag)) continue; + + BlockPos pos = NbtUtils.readBlockPos(tag.getCompound("Pos")); + long time = tag.getLong("Time"); + BlockState state = NbtUtils.readBlockState(blockRegistry, tag.getCompound("State")); + CompoundTag beNbt = tag.contains("BlockEntity") ? tag.getCompound("BlockEntity").copy() : null; + + data.respawnEntries.put(pos, new RespawnEntry(time, state, beNbt)); + } + + return data; + } + + + + public CompoundTag save(CompoundTag nbt) { + ListTag list = new ListTag(); + + for (Map.Entry entry : respawnEntries.entrySet()) { + CompoundTag tag = new CompoundTag(); + tag.put("Pos", NbtUtils.writeBlockPos(entry.getKey())); + tag.putLong("Time", entry.getValue().time); + tag.put("State", NbtUtils.writeBlockState(entry.getValue().state)); + if (entry.getValue().nbt != null) tag.put("BlockEntity", entry.getValue().nbt.copy()); + list.add(tag); + } + + nbt.put("Respawns", list); + return nbt; + } + + // Add or update a respawn entry + public void set(BlockPos pos, long time, BlockState state, CompoundTag beNbt) { + respawnEntries.put(pos, new RespawnEntry(time, state, beNbt)); + this.setDirty(); + } + + // Remove a respawn entry + public void remove(BlockPos pos) { + respawnEntries.remove(pos); + this.setDirty(); + } + + // Access all entries + public Map getEntries() { + return respawnEntries; + } +} diff --git a/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/persistence/BlockRespawnHandler.java b/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/persistence/BlockRespawnHandler.java new file mode 100644 index 00000000..9cb1275d --- /dev/null +++ b/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/persistence/BlockRespawnHandler.java @@ -0,0 +1,101 @@ +package insane96mcp.enhancedai.modules.mobs.miner.persistence; + +import insane96mcp.enhancedai.EnhancedAI; +import net.minecraft.core.BlockPos; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.AABB; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +import java.util.Iterator; +import java.util.Map; + +@Mod.EventBusSubscriber(modid = EnhancedAI.MOD_ID) +public class BlockRespawnHandler { + + @SubscribeEvent + public static void onServerTick(TickEvent.ServerTickEvent event) { + if (event.phase != TickEvent.Phase.END) return; + + MinecraftServer server = event.getServer(); + if (server == null) return; + + for (ServerLevel level : server.getAllLevels()) { + BlockRespawnData data = BlockRespawnData.get(level); + long time = level.getGameTime(); + boolean changed = false; + + Iterator> it = data.getEntries().entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + BlockPos pos = entry.getKey(); + BlockRespawnData.RespawnEntry info = entry.getValue(); + + if (time >= info.time) { + try { + BlockState existing = level.getBlockState(pos); + if (!existing.isAir()) { + BlockEntity existingBe = level.getBlockEntity(pos); + + // --- Properly drop blocks obstructing respawn (like mining with a pickaxe) --- + LootParams.Builder lootBuilder = new LootParams.Builder(level) + .withParameter(LootContextParams.ORIGIN, pos.getCenter()) + .withOptionalParameter(LootContextParams.BLOCK_ENTITY, existingBe) + .withOptionalParameter(LootContextParams.TOOL, ItemStack.EMPTY); + + for (ItemStack drop : existing.getDrops(lootBuilder)) { + level.addFreshEntity(new ItemEntity(level, + pos.getX() + 0.5d, + pos.getY() + 0.5d, + pos.getZ() + 0.5d, + drop)); + } + + existing.spawnAfterBreak(level, pos, ItemStack.EMPTY, false); + level.removeBlock(pos, false); + } + + // --- Push any entities up to avoid suffocation --- + AABB box = new AABB(pos); + for (Entity e : level.getEntities(null, box)) { + e.setPos(e.getX(), e.getY() + 1.0, e.getZ()); + } + + // --- Restore the saved block --- + level.setBlock(pos, info.state, 3); + + // --- Restore NBT if tile entity --- + if (info.nbt != null) { + BlockEntity be = level.getBlockEntity(pos); + if (be != null) { + be.load(info.nbt); + be.setChanged(); + } else { + EnhancedAI.LOGGER.warn("BlockEntity missing at {} when respawning; NBT skipped.", pos); + } + } + + EnhancedAI.LOGGER.debug("Respawned block {} at {}", info.state.getBlock().getName().getString(), pos); + + } catch (Exception e) { + EnhancedAI.LOGGER.warn("Failed to respawn block at {}: {}", pos, e.getMessage()); + } + + it.remove(); + changed = true; + } + } + + if (changed) data.setDirty(); + } + } +} From 3cd45b531fc5ea7bcd956942ac77b31e0df4dd10 Mon Sep 17 00:00:00 2001 From: GulpieEXE Date: Sat, 8 Nov 2025 17:41:45 -0500 Subject: [PATCH 4/7] Update MineTowardsTargetGoal.java --- .../mobs/miner/MineTowardsTargetGoal.java | 549 +++++++++--------- 1 file changed, 273 insertions(+), 276 deletions(-) diff --git a/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/MineTowardsTargetGoal.java b/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/MineTowardsTargetGoal.java index 993d04ad..a89fe0aa 100644 --- a/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/MineTowardsTargetGoal.java +++ b/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/MineTowardsTargetGoal.java @@ -41,280 +41,277 @@ import static insane96mcp.enhancedai.modules.mobs.miner.MinerMobs.blacklistTileEntities; public class MineTowardsTargetGoal extends Goal { - private final Mob miner; - private LivingEntity target; - private final double reachDistance; - private final List targetBlocks = new ArrayList<>(); - private int tickToBreak = 0; - private int breakingTick = 0; - private BlockState blockState = null; - private int prevBreakProgress = 0; - - private Vec3 lastPosition = null; - private int lastPositionTickstamp = 0; - - private Path path = null; - - public MineTowardsTargetGoal(Mob miner) { - this.miner = miner; - this.reachDistance = miner.getAttribute(ForgeMod.BLOCK_REACH.get()) == null ? 4.5 : miner.getAttributeValue(ForgeMod.BLOCK_REACH.get()); - this.setFlags(EnumSet.of(Flag.LOOK, Flag.MOVE)); - } - - - - public boolean canUse() { - if (!MinerMobs.isValidDimension(this.miner) - || !this.miner.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) - || (MinerMobs.TOOL_REQUIREMENT.get(this.miner) == MinerMobs.ToolRequirement.ANY_TOOL && !(this.miner.getOffhandItem().getItem() instanceof DiggerItem)) - || this.miner.getTarget() == null) - return false; - float maxTargetDistance = MinerMobs.MAX_TARGET_DISTANCE.get(this.miner); - maxTargetDistance *= maxTargetDistance; - return this.isStuck() - && (!MeleeAttacking.isWithinMeleeAttackRange(this.miner, this.miner.getTarget()) || !this.miner.getSensing().hasLineOfSight(this.miner.getTarget())) - && (this.miner.distanceToSqr(miner.getTarget()) < maxTargetDistance || maxTargetDistance == 0); - } - - - public boolean canContinueToUse() { - if (this.targetBlocks.isEmpty()) - return false; - if (this.blockState != null && !this.canBreakBlock()) - return false; - if (this.target == null || !this.target.isAlive()) - return false; - - return this.targetBlocks.get(0).distSqr(this.miner.blockPosition()) < this.reachDistance * this.reachDistance - && this.miner.getNavigation().isDone() - && !this.miner.level().getBlockState(this.targetBlocks.get(0)).isAir() - && this.path != null && (this.path.getDistToTarget() > 1.5d || !this.miner.hasLineOfSight(this.target)); - } - - - public void start() { - this.target = this.miner.getTarget(); - if (this.target == null) - return; - fillTargetBlocks(); - if (!this.targetBlocks.isEmpty()) { - initBlockBreak(); - } - } - - - public void stop() { - this.target = null; - if (!this.targetBlocks.isEmpty()) { - this.miner.level().destroyBlockProgress(this.miner.getId(), targetBlocks.get(0), -1); - this.targetBlocks.clear(); - } - this.tickToBreak = 0; - this.breakingTick = 0; - this.blockState = null; - this.prevBreakProgress = 0; - this.lastPosition = null; - this.path = null; - this.miner.setAggressive(false); - } - - - public void tick() { - if (this.targetBlocks.isEmpty()) - return; - if (this.blockState != null && !this.canBreakBlock()) - return; - this.miner.setAggressive(true); - BlockPos pos = this.targetBlocks.get(0); - this.breakingTick++; - this.miner.getLookControl().setLookAt(pos.getX() + 0.5d, pos.getY() + 0.5d, pos.getZ() + 0.5d); - - // progress visuals - int progress = (int) ((this.breakingTick / (float) this.tickToBreak) * 10); - if (this.prevBreakProgress != progress) { - this.prevBreakProgress = progress; - this.miner.level().destroyBlockProgress(this.miner.getId(), pos, progress); - } - - if (this.breakingTick % 6 == 0) - this.miner.swing(InteractionHand.MAIN_HAND); - - if (this.breakingTick % 4 == 0) { - SoundType soundType = this.blockState.getSoundType(this.miner.level(), pos, this.miner); - this.miner.level().playSound(null, pos, soundType.getHitSound(), SoundSource.BLOCKS, - (soundType.getVolume() + 1.0F) / 8.0F, soundType.getPitch() * 0.5F); - } - - // --- Respawn-aware block breaking --- - if (this.breakingTick >= this.tickToBreak && this.miner.level() instanceof ServerLevel level) { - if (!ForgeEventFactory.onEntityDestroyBlock(this.miner, pos, this.blockState)) return; - - int respawnTime = MinerMobs.BLOCK_RESPAWN_TIME.get(this.miner); - boolean scaleByHardness = MinerMobs.SCALE_RESPAWN_BY_HARDNESS.get(this.miner); - - if (scaleByHardness) { - double hardness = Math.max(0, this.blockState.getDestroySpeed(level, pos)); - int baseTime = MinerMobs.BASE_RESPAWN_TIME.get(this.miner); - double multiplier = MinerMobs.HARDNESS_RESPAWN_MULTIPLIER.get(this.miner); - respawnTime = (int) Math.ceil(baseTime + hardness * multiplier); - } - - boolean willRespawn = respawnTime > 0; - - CompoundTag blockNbt = null; - if (willRespawn && MinerMobs.shouldSaveBlockNBT(this.blockState)) { - BlockEntity blockEntity = level.getBlockEntity(pos); - if (blockEntity != null) - blockNbt = blockEntity.saveWithFullMetadata(); - } - - if (willRespawn) { - // Remove block without drops, record for respawn - level.removeBlockEntity(pos); - level.setBlock(pos, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState(), 3); - - long respawnAt = level.getGameTime() + respawnTime; - BlockRespawnData data = BlockRespawnData.get(level); - data.set(pos, respawnAt, this.blockState, blockNbt); - - EnhancedAI.LOGGER.debug("Scheduled respawn for block {} at {} after {} ticks", - this.blockState.getBlock().getName().getString(), pos, respawnTime); - } - else { - // Normal destruction with drops - if (this.miner.getItemBySlot(EquipmentSlot.OFFHAND).isCorrectToolForDrops(this.blockState)) { - BlockEntity blockEntity = this.blockState.hasBlockEntity() ? level.getBlockEntity(pos) : null; - LootParams.Builder lootparams = (new LootParams.Builder(level)) - .withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos)) - .withParameter(LootContextParams.TOOL, this.miner.getOffhandItem()) - .withOptionalParameter(LootContextParams.BLOCK_ENTITY, blockEntity) - .withOptionalParameter(LootContextParams.THIS_ENTITY, this.miner); - this.blockState.spawnAfterBreak(level, pos, this.miner.getOffhandItem(), false); - this.blockState.getDrops(lootparams).forEach(stack -> - level.addFreshEntity(new ItemEntity(level, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, stack))); - level.removeBlock(pos, false); - } - } - - this.miner.level().destroyBlockProgress(this.miner.getId(), pos, -1); - this.targetBlocks.remove(0); - if (!this.targetBlocks.isEmpty()) - initBlockBreak(); - else if (this.miner.distanceToSqr(this.target) > 2d && !this.miner.getSensing().hasLineOfSight(this.target)) - start(); - } - } - - private void initBlockBreak() { - this.blockState = this.miner.level().getBlockState(this.targetBlocks.get(0)); - this.tickToBreak = computeTickToBreak(); - this.breakingTick = 0; - this.path = this.miner.getNavigation().createPath(this.target, 1); - } - - private void fillTargetBlocks() { - int mobHeight = Mth.ceil(this.miner.getBbHeight()); - for (int i = 0; i < mobHeight; i++) { - BlockHitResult rayTrace = this.miner.level().clip(new ClipContext(this.miner.position().add(0, i + 0.5d, 0), this.target.getEyePosition(1f).add(0, i, 0), ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this.miner)); - if (rayTrace.getType() == HitResult.Type.MISS - || this.targetBlocks.contains(rayTrace.getBlockPos()) - || rayTrace.getBlockPos().getY() > MinerMobs.MAX_Y.get(this.miner)) - continue; - - double distance = this.miner.distanceToSqr(rayTrace.getLocation()); - if (distance > this.reachDistance * this.reachDistance) - continue; - - BlockState state = this.miner.level().getBlockState(rayTrace.getBlockPos()); - - if (state.getDestroySpeed(this.miner.level(), rayTrace.getBlockPos()) == -1 - || (state.hasBlockEntity() && blacklistTileEntities)) - continue; - - boolean listed = state.is(MinerMobs.BLOCK_BLACKLIST); - if (listed != MinerMobs.blockBlacklistAsWhitelist) - continue; - - this.targetBlocks.add(rayTrace.getBlockPos()); - } - Collections.reverse(this.targetBlocks); - } - - private int computeTickToBreak() { - int canHarvestBlock = this.canHarvestBlock() ? 30 : 100; - double diggingSpeed = this.getDigSpeed() / this.blockState.getDestroySpeed(this.miner.level(), this.targetBlocks.get(0)) / canHarvestBlock; - return Mth.ceil((1f / diggingSpeed) * MinerMobs.TIME_TO_BREAK_MULTIPLIER.get(this.miner)); - } - - private float getDigSpeed() { - float digSpeed = this.miner.getOffhandItem().getDestroySpeed(this.blockState); - if (digSpeed > 1.0F) { - int efficiency = EnchantmentHelper.getBlockEfficiency(this.miner); - if (efficiency > 0) digSpeed += (float) (efficiency * efficiency + 1); - } - if (MobEffectUtil.hasDigSpeed(this.miner)) - digSpeed *= 1.0F + (MobEffectUtil.getDigSpeedAmplification(this.miner) + 1) * 0.2F; - if (this.miner.hasEffect(MobEffects.DIG_SLOWDOWN)) { - float f = switch (this.miner.getEffect(MobEffects.DIG_SLOWDOWN).getAmplifier()) { - case 0 -> 0.3F; - case 1 -> 0.09F; - case 2 -> 0.0027F; - default -> 8.1E-4F; - }; - digSpeed *= f; - } - if (this.miner.isEyeInFluidType(ForgeMod.WATER_TYPE.get()) && !EnchantmentHelper.hasAquaAffinity(this.miner)) - digSpeed /= 5.0F; - return digSpeed; - } - - private boolean canBreakBlock() { - MinerMobs.ToolRequirement toolReq = MinerMobs.TOOL_REQUIREMENT.get(this.miner); - if (toolReq == MinerMobs.ToolRequirement.NONE || toolReq == MinerMobs.ToolRequirement.ANY_TOOL) - return true; - if (toolReq == MinerMobs.ToolRequirement.CORRECT_TOOL_FOR_REQUIRED && !this.blockState.requiresCorrectToolForDrops()) - return true; - ItemStack stack = this.miner.getOffhandItem(); - return !stack.isEmpty() && stack.isCorrectToolForDrops(this.blockState); - } - - private boolean canHarvestBlock() { - if (!this.blockState.requiresCorrectToolForDrops()) - return true; - ItemStack stack = this.miner.getOffhandItem(); - return !stack.isEmpty() && stack.isCorrectToolForDrops(this.blockState); - } - - public boolean requiresUpdateEveryTick() { return true; } - - private boolean isStuck() { - if (this.miner.getTarget() == null) - return false; - if (MeleeAttacking.isWithinMeleeAttackRange(this.miner, this.miner.getTarget()) - && this.miner.getSensing().hasLineOfSight(this.miner.getTarget())) - return false; - if (this.lastPosition == null || this.miner.distanceToSqr(this.lastPosition) > 2.25d) { - this.lastPosition = this.miner.position(); - this.lastPositionTickstamp = this.miner.tickCount; - } - return this.miner.getNavigation().isDone() || this.miner.tickCount - this.lastPositionTickstamp >= 60; - } - - private void freeSpaceForRespawn(ServerLevel level, BlockPos pos) { - BlockState existing = level.getBlockState(pos); - - // If block is already air, nothing to do - if (existing.isAir()) return; - - // Drop the block as an item - existing.spawnAfterBreak(level, pos, ItemStack.EMPTY, true); - level.removeBlock(pos, false); - - // Move any entities standing on the block slightly upward - level.getEntities(null, existing.getShape(level, pos).bounds().move(pos.getX(), pos.getY(), pos.getZ())) - .forEach(e -> e.setPos(e.getX(), e.getY() + 1.0, e.getZ())); - } - - - + private final Mob miner; + private LivingEntity target; + private final double reachDistance; + private final List targetBlocks = new ArrayList<>(); + private int tickToBreak = 0; + private int breakingTick = 0; + private BlockState blockState = null; + private int prevBreakProgress = 0; + + private Vec3 lastPosition = null; + private int lastPositionTickstamp = 0; + + private Path path = null; + + public MineTowardsTargetGoal(Mob miner) { + this.miner = miner; + this.reachDistance = miner.getAttribute(ForgeMod.BLOCK_REACH.get()) == null ? 4.5 : miner.getAttributeValue(ForgeMod.BLOCK_REACH.get()); + this.setFlags(EnumSet.of(Flag.LOOK, Flag.MOVE)); + } + + + + public boolean canUse() { + if (!MinerMobs.isValidDimension(this.miner) + || !this.miner.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) + || (MinerMobs.TOOL_REQUIREMENT.get(this.miner) == MinerMobs.ToolRequirement.ANY_TOOL && !(this.miner.getOffhandItem().getItem() instanceof DiggerItem)) + || this.miner.getTarget() == null) + return false; + float maxTargetDistance = MinerMobs.MAX_TARGET_DISTANCE.get(this.miner); + maxTargetDistance *= maxTargetDistance; + return this.isStuck() + && (!MeleeAttacking.isWithinMeleeAttackRange(this.miner, this.miner.getTarget()) || !this.miner.getSensing().hasLineOfSight(this.miner.getTarget())) + && (this.miner.distanceToSqr(miner.getTarget()) < maxTargetDistance || maxTargetDistance == 0); + } + + + public boolean canContinueToUse() { + if (this.targetBlocks.isEmpty()) + return false; + if (this.blockState != null && !this.canBreakBlock()) + return false; + if (this.target == null || !this.target.isAlive()) + return false; + + return this.targetBlocks.get(0).distSqr(this.miner.blockPosition()) < this.reachDistance * this.reachDistance + && this.miner.getNavigation().isDone() + && !this.miner.level().getBlockState(this.targetBlocks.get(0)).isAir() + && this.path != null && (this.path.getDistToTarget() > 1.5d || !this.miner.hasLineOfSight(this.target)); + } + + + public void start() { + this.target = this.miner.getTarget(); + if (this.target == null) + return; + fillTargetBlocks(); + if (!this.targetBlocks.isEmpty()) { + initBlockBreak(); + } + } + + + public void stop() { + this.target = null; + if (!this.targetBlocks.isEmpty()) { + this.miner.level().destroyBlockProgress(this.miner.getId(), targetBlocks.get(0), -1); + this.targetBlocks.clear(); + } + this.tickToBreak = 0; + this.breakingTick = 0; + this.blockState = null; + this.prevBreakProgress = 0; + this.lastPosition = null; + this.path = null; + this.miner.setAggressive(false); + } + + + public void tick() { + if (this.targetBlocks.isEmpty()) + return; + if (this.blockState != null && !this.canBreakBlock()) + return; + this.miner.setAggressive(true); + BlockPos pos = this.targetBlocks.get(0); + this.breakingTick++; + this.miner.getLookControl().setLookAt(pos.getX() + 0.5d, pos.getY() + 0.5d, pos.getZ() + 0.5d); + + // progress visuals + int progress = (int) ((this.breakingTick / (float) this.tickToBreak) * 10); + if (this.prevBreakProgress != progress) { + this.prevBreakProgress = progress; + this.miner.level().destroyBlockProgress(this.miner.getId(), pos, progress); + } + + if (this.breakingTick % 6 == 0) + this.miner.swing(InteractionHand.MAIN_HAND); + + if (this.breakingTick % 4 == 0) { + SoundType soundType = this.blockState.getSoundType(this.miner.level(), pos, this.miner); + this.miner.level().playSound(null, pos, soundType.getHitSound(), SoundSource.BLOCKS, + (soundType.getVolume() + 1.0F) / 8.0F, soundType.getPitch() * 0.5F); + } + + // --- Respawn-aware block breaking --- + if (this.breakingTick >= this.tickToBreak && this.miner.level() instanceof ServerLevel level) { + if (!ForgeEventFactory.onEntityDestroyBlock(this.miner, pos, this.blockState)) return; + + int respawnTime = MinerMobs.BLOCK_RESPAWN_TIME.get(this.miner); + boolean scaleByHardness = MinerMobs.SCALE_RESPAWN_BY_HARDNESS.get(this.miner); + + if (scaleByHardness) { + double hardness = Math.max(0, this.blockState.getDestroySpeed(level, pos)); + int baseTime = MinerMobs.BASE_RESPAWN_TIME.get(this.miner); + double multiplier = MinerMobs.HARDNESS_RESPAWN_MULTIPLIER.get(this.miner); + respawnTime = (int) Math.ceil(baseTime + hardness * multiplier); + } + + boolean willRespawn = respawnTime > 0; + + CompoundTag blockNbt = null; + if (willRespawn && MinerMobs.shouldSaveBlockNBT(this.blockState)) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity != null) + blockNbt = blockEntity.saveWithFullMetadata(); + } + + if (willRespawn) { + // Remove block without drops, record for respawn + level.removeBlockEntity(pos); + level.setBlock(pos, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState(), 3); + + long respawnAt = level.getGameTime() + respawnTime; + BlockRespawnData data = BlockRespawnData.get(level); + data.set(pos, respawnAt, this.blockState, blockNbt); + + EnhancedAI.LOGGER.debug("Scheduled respawn for block {} at {} after {} ticks", + this.blockState.getBlock().getName().getString(), pos, respawnTime); + } + else { + // Normal destruction with drops + if (this.miner.getItemBySlot(EquipmentSlot.OFFHAND).isCorrectToolForDrops(this.blockState)) { + BlockEntity blockEntity = this.blockState.hasBlockEntity() ? level.getBlockEntity(pos) : null; + LootParams.Builder lootparams = (new LootParams.Builder(level)) + .withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(pos)) + .withParameter(LootContextParams.TOOL, this.miner.getOffhandItem()) + .withOptionalParameter(LootContextParams.BLOCK_ENTITY, blockEntity) + .withOptionalParameter(LootContextParams.THIS_ENTITY, this.miner); + this.blockState.spawnAfterBreak(level, pos, this.miner.getOffhandItem(), false); + this.blockState.getDrops(lootparams).forEach(stack -> + level.addFreshEntity(new ItemEntity(level, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, stack))); + level.removeBlock(pos, false); + } + } + + this.miner.level().destroyBlockProgress(this.miner.getId(), pos, -1); + this.targetBlocks.remove(0); + if (!this.targetBlocks.isEmpty()) + initBlockBreak(); + else if (this.miner.distanceToSqr(this.target) > 2d && !this.miner.getSensing().hasLineOfSight(this.target)) + start(); + } + } + + private void initBlockBreak() { + this.blockState = this.miner.level().getBlockState(this.targetBlocks.get(0)); + this.tickToBreak = computeTickToBreak(); + this.breakingTick = 0; + this.path = this.miner.getNavigation().createPath(this.target, 1); + } + + private void fillTargetBlocks() { + int mobHeight = Mth.ceil(this.miner.getBbHeight()); + for (int i = 0; i < mobHeight; i++) { + BlockHitResult rayTrace = this.miner.level().clip(new ClipContext(this.miner.position().add(0, i + 0.5d, 0), this.target.getEyePosition(1f).add(0, i, 0), ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this.miner)); + if (rayTrace.getType() == HitResult.Type.MISS + || this.targetBlocks.contains(rayTrace.getBlockPos()) + || rayTrace.getBlockPos().getY() > MinerMobs.MAX_Y.get(this.miner)) + continue; + + double distance = this.miner.distanceToSqr(rayTrace.getLocation()); + if (distance > this.reachDistance * this.reachDistance) + continue; + + BlockState state = this.miner.level().getBlockState(rayTrace.getBlockPos()); + + if (state.getDestroySpeed(this.miner.level(), rayTrace.getBlockPos()) == -1 + || (state.hasBlockEntity() && blacklistTileEntities)) + continue; + + boolean listed = state.is(MinerMobs.BLOCK_BLACKLIST); + if (listed != MinerMobs.blockBlacklistAsWhitelist) + continue; + + this.targetBlocks.add(rayTrace.getBlockPos()); + } + Collections.reverse(this.targetBlocks); + } + + private int computeTickToBreak() { + int canHarvestBlock = this.canHarvestBlock() ? 30 : 100; + double diggingSpeed = this.getDigSpeed() / this.blockState.getDestroySpeed(this.miner.level(), this.targetBlocks.get(0)) / canHarvestBlock; + return Mth.ceil((1f / diggingSpeed) * MinerMobs.TIME_TO_BREAK_MULTIPLIER.get(this.miner)); + } + + private float getDigSpeed() { + float digSpeed = this.miner.getOffhandItem().getDestroySpeed(this.blockState); + if (digSpeed > 1.0F) { + int efficiency = EnchantmentHelper.getBlockEfficiency(this.miner); + if (efficiency > 0) digSpeed += (float) (efficiency * efficiency + 1); + } + if (MobEffectUtil.hasDigSpeed(this.miner)) + digSpeed *= 1.0F + (MobEffectUtil.getDigSpeedAmplification(this.miner) + 1) * 0.2F; + if (this.miner.hasEffect(MobEffects.DIG_SLOWDOWN)) { + float f = switch (this.miner.getEffect(MobEffects.DIG_SLOWDOWN).getAmplifier()) { + case 0 -> 0.3F; + case 1 -> 0.09F; + case 2 -> 0.0027F; + default -> 8.1E-4F; + }; + digSpeed *= f; + } + if (this.miner.isEyeInFluidType(ForgeMod.WATER_TYPE.get()) && !EnchantmentHelper.hasAquaAffinity(this.miner)) + digSpeed /= 5.0F; + return digSpeed; + } + + private boolean canBreakBlock() { + MinerMobs.ToolRequirement toolReq = MinerMobs.TOOL_REQUIREMENT.get(this.miner); + if (toolReq == MinerMobs.ToolRequirement.NONE || toolReq == MinerMobs.ToolRequirement.ANY_TOOL) + return true; + if (toolReq == MinerMobs.ToolRequirement.CORRECT_TOOL_FOR_REQUIRED && !this.blockState.requiresCorrectToolForDrops()) + return true; + ItemStack stack = this.miner.getOffhandItem(); + return !stack.isEmpty() && stack.isCorrectToolForDrops(this.blockState); + } + + private boolean canHarvestBlock() { + if (!this.blockState.requiresCorrectToolForDrops()) + return true; + ItemStack stack = this.miner.getOffhandItem(); + return !stack.isEmpty() && stack.isCorrectToolForDrops(this.blockState); + } + + public boolean requiresUpdateEveryTick() { return true; } + + private boolean isStuck() { + if (this.miner.getTarget() == null) + return false; + if (MeleeAttacking.isWithinMeleeAttackRange(this.miner, this.miner.getTarget()) + && this.miner.getSensing().hasLineOfSight(this.miner.getTarget())) + return false; + if (this.lastPosition == null || this.miner.distanceToSqr(this.lastPosition) > 2.25d) { + this.lastPosition = this.miner.position(); + this.lastPositionTickstamp = this.miner.tickCount; + } + return this.miner.getNavigation().isDone() || this.miner.tickCount - this.lastPositionTickstamp >= 60; + } + + private void freeSpaceForRespawn(ServerLevel level, BlockPos pos) { + BlockState existing = level.getBlockState(pos); + + // If block is already air, nothing to do + if (existing.isAir()) return; + + // Drop the block as an item + existing.spawnAfterBreak(level, pos, ItemStack.EMPTY, true); + level.removeBlock(pos, false); + + // Move any entities standing on the block slightly upward + level.getEntities(null, existing.getShape(level, pos).bounds().move(pos.getX(), pos.getY(), pos.getZ())) + .forEach(e -> e.setPos(e.getX(), e.getY() + 1.0, e.getZ())); + } } From 719eb8fc47bff9f0e172585d70d7a3f27463029b Mon Sep 17 00:00:00 2001 From: GulpieEXE Date: Sat, 8 Nov 2025 17:42:58 -0500 Subject: [PATCH 5/7] Update MinerMobs.java --- .../modules/mobs/miner/MinerMobs.java | 327 +++++++++++------- 1 file changed, 209 insertions(+), 118 deletions(-) diff --git a/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/MinerMobs.java b/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/MinerMobs.java index 96bedde6..0a3752a2 100644 --- a/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/MinerMobs.java +++ b/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/MinerMobs.java @@ -5,146 +5,237 @@ import insane96mcp.enhancedai.data.EAIDataEnum; import insane96mcp.enhancedai.data.EAIDataList; import insane96mcp.enhancedai.modules.Modules; +import insane96mcp.enhancedai.modules.mobs.miner.persistence.BlockRespawnData; import insane96mcp.enhancedai.utils.GoalHelper; import insane96mcp.insanelib.base.Feature; import insane96mcp.insanelib.base.LoadFeature; import insane96mcp.insanelib.base.Module; import insane96mcp.insanelib.base.config.Config; +import net.minecraft.core.BlockPos; import net.minecraft.core.registries.Registries; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.TagKey; +import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; -import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.AABB; import net.minecraftforge.common.ForgeMod; +import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.EntityAttributeModificationEvent; import net.minecraftforge.event.entity.EntityJoinLevelEvent; import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraft.world.level.block.Block; +import java.util.Iterator; import java.util.List; +import java.util.Map; @LoadFeature(module = Modules.Ids.MOBS, description = "Mobs can mine blocks to reach the target. Uses offhand item to mine. Only mobs in the entity type tag enhancedai:mobs/can_mine can spawn with the ability to mine and blocks in the tag enhancedai:miner_blacklist cannot be mined. This feature also adds the block reach attribute to all entities.") public class MinerMobs extends Feature { - public static final TagKey> CAN_BE_MINER = TagKey.create(Registries.ENTITY_TYPE, EnhancedAI.location("mobs/can_mine")); - public static final TagKey BLOCK_BLACKLIST = TagKey.create(Registries.BLOCK, EnhancedAI.location("miner_blacklist")); - - @Config(min = 0d, max = 1d, description = "Chance for a mob in the entity type tag enhancedai:can_be_miner to spawn with the miner ability") - public static Double minerChance = 0.07d; - @Config(description = "NONE: No item is required. ANY_TOOL: Any item that can break blocks in the off-hand is required. CORRECT_TOOL_FOR_REQUIRED: The mob is able to mine any blocks that don't require a tool, and require a tool for blocks that require it (e.g. can always mine dirt but can't mine stone). CORRECT_TOOL_FOR_ANY_BLOCK: The mob can only mine blocks if the tool is the right one for the block (e.g. can mine dirt only with a shovel).") - public static ToolRequirement toolRequirement = ToolRequirement.CORRECT_TOOL_FOR_REQUIRED; - @Config(min = -512, max = 1024, description = "Mobs can mine from the bottom of the world to this Y level.") - public static Integer maxY = 320; - @Config(min = 0, max = 128, description = "The maximum distance from the target at which the Mobs can mine. Set to 0 to always mine.") - public static Integer maxTargetDistance = 0; - @Config(min = 0d, max = 128d, description = "Multiplier for the time a mob takes to break blocks. E.g. with this set to 2, mobs will take twice the time to mine a block.") - public static Double timeToBreakMultiplier = 1.25d; - @Config(description = "Dimensions where mobs can mine.") - public static List dimensionWhitelist = List.of("minecraft:overworld", "minecraft:the_nether", "minecraft:the_end"); - @Config(description = "If true, the block tag `enhancedai:miner_mobs/blacklist` will be treated as a whitelist instead of blacklist") - public static Boolean blockBlacklistAsWhitelist = false; - @Config(description = "Mobs with Miner AI will not be able to break tile entities") - public static Boolean blacklistTileEntities = true; - @Config(description = "Mobs with Miner AI will spawn with a Stone Pickaxe that never drops.") - public static Boolean equipStonePick = true; - @Config(min = 0, max = 1200, description = "Time in ticks for a mined block to respawn. Set to 0 for no respawn. (20 ticks = 1 second)") - public static Integer blockRespawnTime = 0; - @Config(description = "If true, block respawn time will scale based on the block's hardness.") - public static Boolean scaleRespawnByHardness = false; - @Config(min = 0, max = 1200, description = "Base respawn time in ticks for a block (used if scaling by hardness is enabled).") - public static Integer baseRespawnTime = 200; - @Config(min = 0d, max = 100d, description = "Multiplier applied to the block's hardness when calculating respawn time (used if scaling by hardness is enabled).") - public static Double hardnessRespawnMultiplier = 100d; - - public static EAIData MINER; - public static EAIDataEnum TOOL_REQUIREMENT; - public static EAIData MAX_Y; - public static EAIData MAX_TARGET_DISTANCE; - public static EAIData TIME_TO_BREAK_MULTIPLIER; - public static EAIDataList DIMENSION_WHITELIST; - public static EAIData BLOCK_RESPAWN_TIME; - public static EAIData SCALE_RESPAWN_BY_HARDNESS; - public static EAIData BASE_RESPAWN_TIME; - public static EAIData HARDNESS_RESPAWN_MULTIPLIER; - - public void init(Module module, boolean enabledByDefault, boolean canBeDisabled) { - super.init(module, enabledByDefault, canBeDisabled); - MINER = EAIData.ofBool(this.createDataKey("miner"), (mob, miner) -> { - GoalHelper.removeGoal(mob.goalSelector, MineTowardsTargetGoal.class); - if (miner) - mob.goalSelector.addGoal(1, new MineTowardsTargetGoal(mob)); - }); - TOOL_REQUIREMENT = EAIDataEnum.of(this.createDataKey("tool_requirement"), ToolRequirement.class); - MAX_Y = EAIData.ofInt(this.createDataKey("max_y")); - MAX_TARGET_DISTANCE = EAIData.ofInt(this.createDataKey("max_target_distance")); - TIME_TO_BREAK_MULTIPLIER = EAIData.ofDouble(this.createDataKey("time_to_break_multiplier")); - DIMENSION_WHITELIST = EAIDataList.of(this.createDataKey("dimension_whitelist"), String.class); - BLOCK_RESPAWN_TIME = EAIData.ofInt(this.createDataKey("block_respawn_time")); - SCALE_RESPAWN_BY_HARDNESS = EAIData.ofBool(this.createDataKey("scale_respawn_by_hardness")); - BASE_RESPAWN_TIME = EAIData.ofInt(this.createDataKey("base_respawn_time")); - HARDNESS_RESPAWN_MULTIPLIER = EAIData.ofDouble(this.createDataKey("hardness_respawn_multiplier")); - net.minecraftforge.common.MinecraftForge.EVENT_BUS.register(MineTowardsTargetGoal.class); - } - - public static void addAttribute(EntityAttributeModificationEvent event) { - for (EntityType entityType : event.getTypes()) { - if (event.has(entityType, ForgeMod.BLOCK_REACH.get())) - continue; - - event.add(entityType, ForgeMod.BLOCK_REACH.get()); - } - } - - //Lowest priority so other mods can set persistent data - @SubscribeEvent(priority = EventPriority.LOWEST) - public void onSpawn(EntityJoinLevelEvent event) { - if (!this.isEnabled() - || event.getLevel().isClientSide - || !(event.getEntity() instanceof Mob mob) - || !mob.getType().is(CAN_BE_MINER)) - return; - - boolean isMiner = mob.getRandom().nextDouble() < minerChance; - MINER.applyIfAbsent(mob, isMiner); - if (isMiner && equipStonePick && mob.getOffhandItem().isEmpty()) - { - mob.setItemSlot(EquipmentSlot.OFFHAND, new ItemStack(Items.STONE_PICKAXE)); - mob.setDropChance(EquipmentSlot.OFFHAND, -1f); - } - TOOL_REQUIREMENT.applyIfAbsent(mob, toolRequirement); - MAX_Y.applyIfAbsent(mob, maxY); - MAX_TARGET_DISTANCE.applyIfAbsent(mob, maxTargetDistance); - TIME_TO_BREAK_MULTIPLIER.applyIfAbsent(mob, timeToBreakMultiplier); - DIMENSION_WHITELIST.applyIfAbsent(mob, dimensionWhitelist); - BLOCK_RESPAWN_TIME.applyIfAbsent(mob, blockRespawnTime); - SCALE_RESPAWN_BY_HARDNESS.applyIfAbsent(mob, scaleRespawnByHardness); - BASE_RESPAWN_TIME.applyIfAbsent(mob, baseRespawnTime); - HARDNESS_RESPAWN_MULTIPLIER.applyIfAbsent(mob, hardnessRespawnMultiplier); - - } - - public static boolean isValidDimension(Mob mob) { - List dimensionWhitelist = DIMENSION_WHITELIST.get(mob); - for (String dimension : dimensionWhitelist) { - if (dimension.equals(mob.level().dimension().location().toString())) - return true; - } - return false; - } - - public enum ToolRequirement { - NONE, - ANY_TOOL, - CORRECT_TOOL_FOR_REQUIRED, - CORRECT_TOOL_FOR_ANY_BLOCK - } - public static boolean shouldSaveBlockNBT(BlockState state) { - if (state.hasBlockEntity()) return true; - // Could add a tag whitelist or any other dynamic condition - return false; - } + public static final TagKey> CAN_BE_MINER = TagKey.create(Registries.ENTITY_TYPE, EnhancedAI.location("mobs/can_mine")); + public static final TagKey BLOCK_BLACKLIST = TagKey.create(Registries.BLOCK, EnhancedAI.location("miner_blacklist")); + + @Config(min = 0d, max = 1d, description = "Chance for a mob in the entity type tag enhancedai:can_be_miner to spawn with the miner ability") + public static Double minerChance = 0.07d; + @Config(description = "NONE: No item is required. ANY_TOOL: Any item that can break blocks in the off-hand is required. CORRECT_TOOL_FOR_REQUIRED: The mob is able to mine any blocks that don't require a tool, and require a tool for blocks that require it (e.g. can always mine dirt but can't mine stone). CORRECT_TOOL_FOR_ANY_BLOCK: The mob can only mine blocks if the tool is the right one for the block (e.g. can mine dirt only with a shovel).") + public static ToolRequirement toolRequirement = ToolRequirement.CORRECT_TOOL_FOR_REQUIRED; + @Config(min = -512, max = 1024, description = "Mobs can mine from the bottom of the world to this Y level.") + public static Integer maxY = 320; + @Config(min = 0, max = 128, description = "The maximum distance from the target at which the Mobs can mine. Set to 0 to always mine.") + public static Integer maxTargetDistance = 0; + @Config(min = 0d, max = 128d, description = "Multiplier for the time a mob takes to break blocks. E.g. with this set to 2, mobs will take twice the time to mine a block.") + public static Double timeToBreakMultiplier = 1.25d; + @Config(description = "Dimensions where mobs can mine.") + public static List dimensionWhitelist = List.of("minecraft:overworld", "minecraft:the_nether", "minecraft:the_end"); + @Config(description = "If true, the block tag `enhancedai:miner_mobs/blacklist` will be treated as a whitelist instead of blacklist") + public static Boolean blockBlacklistAsWhitelist = false; + @Config(description = "Mobs with Miner AI will not be able to break tile entities") + public static Boolean blacklistTileEntities = true; + @Config(description = "Mobs with Miner AI will spawn with a Stone Pickaxe that never drops.") + public static Boolean equipStonePick = true; + @Config(min = 0, max = 1200, description = "Time in ticks for a mined block to respawn. Set to 0 for no respawn. (20 ticks = 1 second)") + public static Integer blockRespawnTime = 0; + @Config(description = "If true, block respawn time will scale based on the block's hardness.") + public static Boolean scaleRespawnByHardness = false; + @Config(min = 0, max = 1200, description = "Base respawn time in ticks for a block (used if scaling by hardness is enabled).") + public static Integer baseRespawnTime = 200; + @Config(min = 0d, max = 100d, description = "Multiplier applied to the block's hardness when calculating respawn time (used if scaling by hardness is enabled).") + public static Double hardnessRespawnMultiplier = 100d; + + public static EAIData MINER; + public static EAIDataEnum TOOL_REQUIREMENT; + public static EAIData MAX_Y; + public static EAIData MAX_TARGET_DISTANCE; + public static EAIData TIME_TO_BREAK_MULTIPLIER; + public static EAIDataList DIMENSION_WHITELIST; + public static EAIData BLOCK_RESPAWN_TIME; + public static EAIData SCALE_RESPAWN_BY_HARDNESS; + public static EAIData BASE_RESPAWN_TIME; + public static EAIData HARDNESS_RESPAWN_MULTIPLIER; + + public void init(Module module, boolean enabledByDefault, boolean canBeDisabled) { + super.init(module, enabledByDefault, canBeDisabled); + MINER = EAIData.ofBool(this.createDataKey("miner"), (mob, miner) -> { + GoalHelper.removeGoal(mob.goalSelector, MineTowardsTargetGoal.class); + if (miner) + mob.goalSelector.addGoal(1, new MineTowardsTargetGoal(mob)); + }); + TOOL_REQUIREMENT = EAIDataEnum.of(this.createDataKey("tool_requirement"), ToolRequirement.class); + MAX_Y = EAIData.ofInt(this.createDataKey("max_y")); + MAX_TARGET_DISTANCE = EAIData.ofInt(this.createDataKey("max_target_distance")); + TIME_TO_BREAK_MULTIPLIER = EAIData.ofDouble(this.createDataKey("time_to_break_multiplier")); + DIMENSION_WHITELIST = EAIDataList.of(this.createDataKey("dimension_whitelist"), String.class); + BLOCK_RESPAWN_TIME = EAIData.ofInt(this.createDataKey("block_respawn_time")); + SCALE_RESPAWN_BY_HARDNESS = EAIData.ofBool(this.createDataKey("scale_respawn_by_hardness")); + BASE_RESPAWN_TIME = EAIData.ofInt(this.createDataKey("base_respawn_time")); + HARDNESS_RESPAWN_MULTIPLIER = EAIData.ofDouble(this.createDataKey("hardness_respawn_multiplier")); + net.minecraftforge.common.MinecraftForge.EVENT_BUS.register(MineTowardsTargetGoal.class); + } + + public static void addAttribute(EntityAttributeModificationEvent event) { + for (EntityType entityType : event.getTypes()) { + if (event.has(entityType, ForgeMod.BLOCK_REACH.get())) + continue; + + event.add(entityType, ForgeMod.BLOCK_REACH.get()); + } + } + + //Lowest priority so other mods can set persistent data + @SubscribeEvent(priority = EventPriority.LOWEST) + public void onSpawn(EntityJoinLevelEvent event) { + if (!this.isEnabled() + || event.getLevel().isClientSide + || !(event.getEntity() instanceof Mob mob) + || !mob.getType().is(CAN_BE_MINER)) + return; + + boolean isMiner = mob.getRandom().nextDouble() < minerChance; + MINER.applyIfAbsent(mob, isMiner); + if (isMiner && equipStonePick && mob.getOffhandItem().isEmpty()) + { + mob.setItemSlot(EquipmentSlot.OFFHAND, new ItemStack(Items.STONE_PICKAXE)); + mob.setDropChance(EquipmentSlot.OFFHAND, -1f); + } + TOOL_REQUIREMENT.applyIfAbsent(mob, toolRequirement); + MAX_Y.applyIfAbsent(mob, maxY); + MAX_TARGET_DISTANCE.applyIfAbsent(mob, maxTargetDistance); + TIME_TO_BREAK_MULTIPLIER.applyIfAbsent(mob, timeToBreakMultiplier); + DIMENSION_WHITELIST.applyIfAbsent(mob, dimensionWhitelist); + BLOCK_RESPAWN_TIME.applyIfAbsent(mob, blockRespawnTime); + SCALE_RESPAWN_BY_HARDNESS.applyIfAbsent(mob, scaleRespawnByHardness); + BASE_RESPAWN_TIME.applyIfAbsent(mob, baseRespawnTime); + HARDNESS_RESPAWN_MULTIPLIER.applyIfAbsent(mob, hardnessRespawnMultiplier); + + } + + public static boolean isValidDimension(Mob mob) { + List dimensionWhitelist = DIMENSION_WHITELIST.get(mob); + for (String dimension : dimensionWhitelist) { + if (dimension.equals(mob.level().dimension().location().toString())) + return true; + } + return false; + } + + public enum ToolRequirement { + NONE, + ANY_TOOL, + CORRECT_TOOL_FOR_REQUIRED, + CORRECT_TOOL_FOR_ANY_BLOCK + } + public static boolean shouldSaveBlockNBT(BlockState state) { + if (state.hasBlockEntity()) return true; + // Could add a tag whitelist or any other dynamic condition + return false; + } + // --- Merged BlockRespawnHandler logic --- + @SubscribeEvent + public static void onServerTick(TickEvent.ServerTickEvent event) { + if (event.phase != TickEvent.Phase.END) + return; + + MinecraftServer server = event.getServer(); + if (server == null) + return; + + for (ServerLevel level : server.getAllLevels()) { + BlockRespawnData data = BlockRespawnData.get(level); + long time = level.getGameTime(); + boolean changed = false; + + Iterator> it = data.getEntries().entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + BlockPos pos = entry.getKey(); + BlockRespawnData.RespawnEntry info = entry.getValue(); + + if (time >= info.time) { + try { + BlockState existing = level.getBlockState(pos); + if (!existing.isAir()) { + BlockEntity existingBe = level.getBlockEntity(pos); + + LootParams.Builder lootBuilder = new LootParams.Builder(level) + .withParameter(LootContextParams.ORIGIN, pos.getCenter()) + .withOptionalParameter(LootContextParams.BLOCK_ENTITY, existingBe) + .withOptionalParameter(LootContextParams.TOOL, ItemStack.EMPTY); + + for (ItemStack drop : existing.getDrops(lootBuilder)) { + level.addFreshEntity(new ItemEntity(level, + pos.getX() + 0.5d, + pos.getY() + 0.5d, + pos.getZ() + 0.5d, + drop)); + } + + existing.spawnAfterBreak(level, pos, ItemStack.EMPTY, false); + level.removeBlock(pos, false); + } + + AABB box = new AABB(pos); + for (Entity e : level.getEntities(null, box)) { + e.setPos(e.getX(), e.getY() + 1.0, e.getZ()); + } + + level.setBlock(pos, info.state, 3); + + if (info.nbt != null) { + BlockEntity be = level.getBlockEntity(pos); + if (be != null) { + be.load(info.nbt); + be.setChanged(); + } + else { + EnhancedAI.LOGGER.warn("BlockEntity missing at {} when respawning; NBT skipped.", pos); + } + } + + EnhancedAI.LOGGER.debug("Respawned block {} at {}", info.state.getBlock().getName().getString(), pos); + + } + catch (Exception e) { + EnhancedAI.LOGGER.warn("Failed to respawn block at {}: {}", pos, e.getMessage()); + } + + it.remove(); + changed = true; + } + } + + if (changed) + data.setDirty(); + } + } } From b4f0e68fb42025b74ada4778c917a456f87dd0d8 Mon Sep 17 00:00:00 2001 From: GulpieEXE Date: Sat, 8 Nov 2025 17:43:14 -0500 Subject: [PATCH 6/7] Delete src/main/java/insane96mcp/enhancedai/modules/mobs/miner/persistence/BlockRespawnHandler.java --- .../persistence/BlockRespawnHandler.java | 101 ------------------ 1 file changed, 101 deletions(-) delete mode 100644 src/main/java/insane96mcp/enhancedai/modules/mobs/miner/persistence/BlockRespawnHandler.java diff --git a/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/persistence/BlockRespawnHandler.java b/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/persistence/BlockRespawnHandler.java deleted file mode 100644 index 9cb1275d..00000000 --- a/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/persistence/BlockRespawnHandler.java +++ /dev/null @@ -1,101 +0,0 @@ -package insane96mcp.enhancedai.modules.mobs.miner.persistence; - -import insane96mcp.enhancedai.EnhancedAI; -import net.minecraft.core.BlockPos; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.storage.loot.LootParams; -import net.minecraft.world.level.storage.loot.parameters.LootContextParams; -import net.minecraft.world.phys.AABB; -import net.minecraftforge.event.TickEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; - -import java.util.Iterator; -import java.util.Map; - -@Mod.EventBusSubscriber(modid = EnhancedAI.MOD_ID) -public class BlockRespawnHandler { - - @SubscribeEvent - public static void onServerTick(TickEvent.ServerTickEvent event) { - if (event.phase != TickEvent.Phase.END) return; - - MinecraftServer server = event.getServer(); - if (server == null) return; - - for (ServerLevel level : server.getAllLevels()) { - BlockRespawnData data = BlockRespawnData.get(level); - long time = level.getGameTime(); - boolean changed = false; - - Iterator> it = data.getEntries().entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = it.next(); - BlockPos pos = entry.getKey(); - BlockRespawnData.RespawnEntry info = entry.getValue(); - - if (time >= info.time) { - try { - BlockState existing = level.getBlockState(pos); - if (!existing.isAir()) { - BlockEntity existingBe = level.getBlockEntity(pos); - - // --- Properly drop blocks obstructing respawn (like mining with a pickaxe) --- - LootParams.Builder lootBuilder = new LootParams.Builder(level) - .withParameter(LootContextParams.ORIGIN, pos.getCenter()) - .withOptionalParameter(LootContextParams.BLOCK_ENTITY, existingBe) - .withOptionalParameter(LootContextParams.TOOL, ItemStack.EMPTY); - - for (ItemStack drop : existing.getDrops(lootBuilder)) { - level.addFreshEntity(new ItemEntity(level, - pos.getX() + 0.5d, - pos.getY() + 0.5d, - pos.getZ() + 0.5d, - drop)); - } - - existing.spawnAfterBreak(level, pos, ItemStack.EMPTY, false); - level.removeBlock(pos, false); - } - - // --- Push any entities up to avoid suffocation --- - AABB box = new AABB(pos); - for (Entity e : level.getEntities(null, box)) { - e.setPos(e.getX(), e.getY() + 1.0, e.getZ()); - } - - // --- Restore the saved block --- - level.setBlock(pos, info.state, 3); - - // --- Restore NBT if tile entity --- - if (info.nbt != null) { - BlockEntity be = level.getBlockEntity(pos); - if (be != null) { - be.load(info.nbt); - be.setChanged(); - } else { - EnhancedAI.LOGGER.warn("BlockEntity missing at {} when respawning; NBT skipped.", pos); - } - } - - EnhancedAI.LOGGER.debug("Respawned block {} at {}", info.state.getBlock().getName().getString(), pos); - - } catch (Exception e) { - EnhancedAI.LOGGER.warn("Failed to respawn block at {}: {}", pos, e.getMessage()); - } - - it.remove(); - changed = true; - } - } - - if (changed) data.setDirty(); - } - } -} From 38cb6a7c111a760565f9c8c39af24dcf58ea195f Mon Sep 17 00:00:00 2001 From: GulpieEXE Date: Sat, 8 Nov 2025 17:43:43 -0500 Subject: [PATCH 7/7] Update BlockRespawnData.java --- .../miner/persistence/BlockRespawnData.java | 164 +++++++++--------- 1 file changed, 81 insertions(+), 83 deletions(-) diff --git a/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/persistence/BlockRespawnData.java b/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/persistence/BlockRespawnData.java index 255c5071..466bf882 100644 --- a/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/persistence/BlockRespawnData.java +++ b/src/main/java/insane96mcp/enhancedai/modules/mobs/miner/persistence/BlockRespawnData.java @@ -4,98 +4,96 @@ import net.minecraft.core.registries.Registries; import net.minecraft.nbt.*; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.saveddata.SavedData; import net.minecraft.core.HolderGetter; import net.minecraft.nbt.NbtUtils; import java.util.HashMap; -import java.util.List; import java.util.Map; public class BlockRespawnData extends SavedData { - private final Map respawnEntries = new HashMap<>(); - - public static class RespawnEntry { - public final long time; - public final BlockState state; - public final CompoundTag nbt; - - public RespawnEntry(long time, BlockState state, CompoundTag nbt) { - this.time = time; - this.state = state; - this.nbt = nbt != null ? nbt.copy() : null; - } - } - - public BlockRespawnData() {} - - // Get or create the data instance for a level - public static BlockRespawnData get(ServerLevel level) { - return level.getDataStorage().computeIfAbsent( - nbt -> load(nbt, level), - BlockRespawnData::new, - "enhancedai_block_respawns" - ); - } - - // Load from NBT (requires ServerLevel for registry access) - public static BlockRespawnData load(CompoundTag nbt, ServerLevel level) { - BlockRespawnData data = new BlockRespawnData(); - ListTag list = nbt.getList("Respawns", Tag.TAG_COMPOUND); - - HolderGetter blockRegistry = level.holderLookup(Registries.BLOCK); - - for (Tag t : list) { - if (!(t instanceof CompoundTag tag)) continue; - - BlockPos pos = NbtUtils.readBlockPos(tag.getCompound("Pos")); - long time = tag.getLong("Time"); - BlockState state = NbtUtils.readBlockState(blockRegistry, tag.getCompound("State")); - CompoundTag beNbt = tag.contains("BlockEntity") ? tag.getCompound("BlockEntity").copy() : null; - - data.respawnEntries.put(pos, new RespawnEntry(time, state, beNbt)); - } - - return data; - } - - - - public CompoundTag save(CompoundTag nbt) { - ListTag list = new ListTag(); - - for (Map.Entry entry : respawnEntries.entrySet()) { - CompoundTag tag = new CompoundTag(); - tag.put("Pos", NbtUtils.writeBlockPos(entry.getKey())); - tag.putLong("Time", entry.getValue().time); - tag.put("State", NbtUtils.writeBlockState(entry.getValue().state)); - if (entry.getValue().nbt != null) tag.put("BlockEntity", entry.getValue().nbt.copy()); - list.add(tag); - } - - nbt.put("Respawns", list); - return nbt; - } - - // Add or update a respawn entry - public void set(BlockPos pos, long time, BlockState state, CompoundTag beNbt) { - respawnEntries.put(pos, new RespawnEntry(time, state, beNbt)); - this.setDirty(); - } - - // Remove a respawn entry - public void remove(BlockPos pos) { - respawnEntries.remove(pos); - this.setDirty(); - } - - // Access all entries - public Map getEntries() { - return respawnEntries; - } + private final Map respawnEntries = new HashMap<>(); + + public static class RespawnEntry { + public final long time; + public final BlockState state; + public final CompoundTag nbt; + + public RespawnEntry(long time, BlockState state, CompoundTag nbt) { + this.time = time; + this.state = state; + this.nbt = nbt != null ? nbt.copy() : null; + } + } + + public BlockRespawnData() {} + + // Get or create the data instance for a level + public static BlockRespawnData get(ServerLevel level) { + return level.getDataStorage().computeIfAbsent( + nbt -> load(nbt, level), + BlockRespawnData::new, + "enhancedai_block_respawns" + ); + } + + // Load from NBT (requires ServerLevel for registry access) + public static BlockRespawnData load(CompoundTag nbt, ServerLevel level) { + BlockRespawnData data = new BlockRespawnData(); + ListTag list = nbt.getList("Respawns", Tag.TAG_COMPOUND); + + HolderGetter blockRegistry = level.holderLookup(Registries.BLOCK); + + for (Tag t : list) { + if (!(t instanceof CompoundTag tag)) continue; + + BlockPos pos = NbtUtils.readBlockPos(tag.getCompound("Pos")); + long time = tag.getLong("Time"); + BlockState state = NbtUtils.readBlockState(blockRegistry, tag.getCompound("State")); + CompoundTag beNbt = tag.contains("BlockEntity") ? tag.getCompound("BlockEntity").copy() : null; + + data.respawnEntries.put(pos, new RespawnEntry(time, state, beNbt)); + } + + return data; + } + + + + @Override + public CompoundTag save(CompoundTag nbt) { + ListTag list = new ListTag(); + + for (Map.Entry entry : respawnEntries.entrySet()) { + CompoundTag tag = new CompoundTag(); + tag.put("Pos", NbtUtils.writeBlockPos(entry.getKey())); + tag.putLong("Time", entry.getValue().time); + tag.put("State", NbtUtils.writeBlockState(entry.getValue().state)); + if (entry.getValue().nbt != null) tag.put("BlockEntity", entry.getValue().nbt.copy()); + list.add(tag); + } + + nbt.put("Respawns", list); + return nbt; + } + + // Add or update a respawn entry + public void set(BlockPos pos, long time, BlockState state, CompoundTag beNbt) { + respawnEntries.put(pos, new RespawnEntry(time, state, beNbt)); + this.setDirty(); + } + + // Remove a respawn entry + public void remove(BlockPos pos) { + respawnEntries.remove(pos); + this.setDirty(); + } + + // Access all entries + public Map getEntries() { + return respawnEntries; + } }