From 5011f70932aa253898532caa414569783b7f03ee Mon Sep 17 00:00:00 2001 From: Lithewings <2631810335@qq.com> Date: Sun, 4 Aug 2024 14:29:52 +0800 Subject: [PATCH] =?UTF-8?q?Merge=20remote-tracking=20branch=20'origin/mast?= =?UTF-8?q?er=E2=80=94'=20into=20master=E2=80=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/goal/AdvanceActiveTargetGoal.java | 98 ++++++ .../entity/goal/AdvanceTargetPredicate.java | 133 +++++++ .../goal/AttackPassiveEntitiesGoal.java | 53 +++ .../entity/goal/BreakBlockGoal.java | 328 +++++++++++------- .../entity/goal/LookAtTargetGoal.java | 27 ++ .../mixin/entitymixin/ZombieEntityMixin.java | 163 ++++++++- .../goal_mixin/LookAtEntityGoalMixin.java | 9 - .../com/equilibrium/tags/ModBlockTags.java | 8 + .../tags/block/transparent_for_zombie.json | 10 + src/main/resources/name.accesswidener | 3 + 10 files changed, 685 insertions(+), 147 deletions(-) create mode 100644 src/main/java/com/equilibrium/entity/goal/AdvanceActiveTargetGoal.java create mode 100644 src/main/java/com/equilibrium/entity/goal/AdvanceTargetPredicate.java create mode 100644 src/main/java/com/equilibrium/entity/goal/AttackPassiveEntitiesGoal.java create mode 100644 src/main/java/com/equilibrium/entity/goal/LookAtTargetGoal.java create mode 100644 src/main/resources/data/miteequilibrium/tags/block/transparent_for_zombie.json diff --git a/src/main/java/com/equilibrium/entity/goal/AdvanceActiveTargetGoal.java b/src/main/java/com/equilibrium/entity/goal/AdvanceActiveTargetGoal.java new file mode 100644 index 0000000..854158a --- /dev/null +++ b/src/main/java/com/equilibrium/entity/goal/AdvanceActiveTargetGoal.java @@ -0,0 +1,98 @@ +package com.equilibrium.entity.goal; + +import java.util.EnumSet; +import java.util.function.Predicate; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.TargetPredicate; +import net.minecraft.entity.ai.goal.Goal; +import net.minecraft.entity.ai.goal.TrackTargetGoal; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.math.Box; +import org.jetbrains.annotations.Nullable; + +/** + * A target goal that finds a target by entity class when the goal starts. + */ +public class AdvanceActiveTargetGoal extends TrackTargetGoal { + private static final int DEFAULT_RECIPROCAL_CHANCE = 10; + protected final Class targetClass; + /** + * The reciprocal of chance to actually search for a target on every tick + * when this goal is not started. This is also the average number of ticks + * between each search (as in a poisson distribution). + */ + protected final int reciprocalChance; + @Nullable + protected LivingEntity targetEntity; + protected AdvanceTargetPredicate targetPredicate; + + public AdvanceActiveTargetGoal(MobEntity mob, Class targetClass, boolean checkVisibility) { + this(mob, targetClass, 10, checkVisibility, false, null); + } + + public AdvanceActiveTargetGoal(MobEntity mob, Class targetClass, boolean checkVisibility, Predicate targetPredicate) { + this(mob, targetClass, 10, checkVisibility, false, targetPredicate); + } + + public AdvanceActiveTargetGoal(MobEntity mob, Class targetClass, boolean checkVisibility, boolean checkCanNavigate) { + this(mob, targetClass, 10, checkVisibility, checkCanNavigate, null); + } + + public AdvanceActiveTargetGoal( + MobEntity mob, + Class targetClass, + int reciprocalChance, + boolean checkVisibility, + boolean checkCanNavigate, + @Nullable Predicate targetPredicate + ) { + super(mob, checkVisibility, checkCanNavigate); + this.targetClass = targetClass; + this.reciprocalChance = toGoalTicks(reciprocalChance); + this.setControls(EnumSet.of(Goal.Control.TARGET)); + this.targetPredicate = AdvanceTargetPredicate.createAttackable().setBaseMaxDistance(this.getFollowRange()).setPredicate(targetPredicate); + } + + @Override + public boolean canStart() { + if (this.reciprocalChance > 0 && this.mob.getRandom().nextInt(this.reciprocalChance) != 0) { + return false; + } else { + this.findClosestTarget(); + return this.targetEntity != null; + } + } + + protected Box getSearchBox(double distance) { + return this.mob.getBoundingBox().expand(distance, 4.0, distance); + } + + protected void findClosestTarget() { + if (this.targetClass != PlayerEntity.class && this.targetClass != ServerPlayerEntity.class) { + this.targetEntity = this.mob + .getWorld() + .getClosestEntity( + this.mob.getWorld().getEntitiesByClass(this.targetClass, this.getSearchBox(this.getFollowRange()), livingEntity -> true), + this.targetPredicate, + this.mob, + this.mob.getX(), + this.mob.getEyeY(), + this.mob.getZ() + ); + } else { + this.targetEntity = this.mob.getWorld().getClosestPlayer(this.targetPredicate, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); + } + } + + @Override + public void start() { + this.mob.setTarget(this.targetEntity); + super.start(); + } + + public void setTargetEntity(@Nullable LivingEntity targetEntity) { + this.targetEntity = targetEntity; + } +} diff --git a/src/main/java/com/equilibrium/entity/goal/AdvanceTargetPredicate.java b/src/main/java/com/equilibrium/entity/goal/AdvanceTargetPredicate.java new file mode 100644 index 0000000..221a285 --- /dev/null +++ b/src/main/java/com/equilibrium/entity/goal/AdvanceTargetPredicate.java @@ -0,0 +1,133 @@ +package com.equilibrium.entity.goal; + +import java.util.function.Predicate; + +import com.equilibrium.tags.ModBlockTags; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.TargetPredicate; +import net.minecraft.entity.attribute.EntityAttributes; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.Difficulty; +import net.minecraft.world.RaycastContext; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +public class AdvanceTargetPredicate extends TargetPredicate { + public static final AdvanceTargetPredicate DEFAULT = createAttackable(); + private static final double MIN_DISTANCE = 2.0; + private final boolean attackable; + private double baseMaxDistance = -1.0; + private boolean respectsVisibility = true; + private boolean useDistanceScalingFactor = true; + @Nullable + private Predicate predicate; + + private AdvanceTargetPredicate(boolean attackable) { + super(attackable); + this.attackable = attackable; + } + + public static AdvanceTargetPredicate createAttackable() { + return new AdvanceTargetPredicate(true); + } + + public static AdvanceTargetPredicate createNonAttackable() { + return new AdvanceTargetPredicate(false); + } + + public AdvanceTargetPredicate copy() { + + AdvanceTargetPredicate advanceTargetPredicate = this.attackable ? createAttackable() : createNonAttackable(); + advanceTargetPredicate.baseMaxDistance = this.baseMaxDistance; + advanceTargetPredicate.respectsVisibility = this.respectsVisibility; + advanceTargetPredicate.useDistanceScalingFactor = this.useDistanceScalingFactor; + advanceTargetPredicate.predicate = this.predicate; + return advanceTargetPredicate; + } + + public AdvanceTargetPredicate setBaseMaxDistance(double baseMaxDistance) { + this.baseMaxDistance = baseMaxDistance; + return this; + } + + public AdvanceTargetPredicate ignoreVisibility() { + this.respectsVisibility = false; + return this; + } + + public AdvanceTargetPredicate ignoreDistanceScalingFactor() { + this.useDistanceScalingFactor = false; + return this; + } + + public AdvanceTargetPredicate setPredicate(@Nullable Predicate predicate) { + this.predicate = predicate; + return this; + } + + public boolean test(@Nullable LivingEntity baseEntity, LivingEntity targetEntity) { + if (baseEntity == targetEntity) { + return false; + } else if (!targetEntity.isPartOfGame()) { + return false; + } else if (this.predicate != null && !this.predicate.test(targetEntity)) { + return false; + } else { + if (baseEntity == null) { + if (this.attackable && (!targetEntity.canTakeDamage() || targetEntity.getWorld().getDifficulty() == Difficulty.PEACEFUL)) { + return false; + } + } else { + if (this.attackable && (!baseEntity.canTarget(targetEntity) || !baseEntity.canTarget(targetEntity.getType()) || baseEntity.isTeammate(targetEntity))) { + return false; + } + + if (this.baseMaxDistance > 0.0) { + double d = this.useDistanceScalingFactor ? targetEntity.getAttackDistanceScalingFactor(baseEntity) : 1.0; + double e = Math.max(this.baseMaxDistance * d, 2.0); + double f = baseEntity.squaredDistanceTo(targetEntity.getX(), targetEntity.getY(), targetEntity.getZ()); + if (f > e * e) { + return false; + } + } + + if (this.respectsVisibility && baseEntity instanceof MobEntity mobEntity && !canSeeThroughTransparentBlocks(mobEntity, targetEntity)) { + return false; + } + } + + return true; + } + } + private boolean canSeeThroughTransparentBlocks(MobEntity mobEntity, LivingEntity targetEntity) { + World world = mobEntity.getWorld(); + Vec3d startPos = new Vec3d(mobEntity.getX(), mobEntity.getEyeY(), mobEntity.getZ()); + Vec3d endPos = new Vec3d(targetEntity.getX(), targetEntity.getEyeY(), targetEntity.getZ()); + + double followRange = mobEntity.getAttributeValue(EntityAttributes.GENERIC_FOLLOW_RANGE); + double distance = startPos.distanceTo(endPos); + + // If the distance exceeds the follow range, return false + if (distance > followRange) { + return false; + } + + + + + + + BlockHitResult hitResult = world.raycast(new RaycastContext(startPos, endPos, RaycastContext.ShapeType.OUTLINE, RaycastContext.FluidHandling.NONE, mobEntity)); + + BlockPos hitPos = hitResult.getBlockPos(); + BlockState blockState = world.getBlockState(hitPos); + + // Check if the block is transparent + return blockState.isTransparent(world,hitPos) || blockState.isAir()||blockState.isIn(ModBlockTags.TRANSPARENT_FOR_ZOMBIE); + } +} diff --git a/src/main/java/com/equilibrium/entity/goal/AttackPassiveEntitiesGoal.java b/src/main/java/com/equilibrium/entity/goal/AttackPassiveEntitiesGoal.java new file mode 100644 index 0000000..f008fa4 --- /dev/null +++ b/src/main/java/com/equilibrium/entity/goal/AttackPassiveEntitiesGoal.java @@ -0,0 +1,53 @@ +package com.equilibrium.entity.goal; + +import java.util.EnumSet; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.goal.Goal; +import net.minecraft.entity.ai.goal.TrackTargetGoal; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.entity.passive.PassiveEntity; +import net.minecraft.entity.ai.TargetPredicate; +import net.minecraft.util.math.Box; +import org.jetbrains.annotations.Nullable; + +/** + * A target goal that finds a passive entity as the target. + */ +public class AttackPassiveEntitiesGoal extends TrackTargetGoal { + private final Class targetClass; + @Nullable + private LivingEntity targetEntity; + private final TargetPredicate targetPredicate; + + public AttackPassiveEntitiesGoal(MobEntity mob, Class targetClass, boolean checkVisibility, boolean checkCanNavigate) { + super(mob, checkVisibility, checkCanNavigate); + this.targetClass = targetClass; + this.targetPredicate = TargetPredicate.createAttackable().setBaseMaxDistance(this.getFollowRange()); + this.setControls(EnumSet.of(Goal.Control.TARGET)); + } + protected Box getSearchBox(double distance) { + return this.mob.getBoundingBox().expand(distance, 4.0, distance); + } + @Override + public boolean canStart() { + this.findClosestTarget(); + return this.targetEntity != null; + } + + private void findClosestTarget() { + this.targetEntity = this.mob.getWorld().getClosestEntity( + this.mob.getWorld().getEntitiesByClass(this.targetClass, this.getSearchBox(this.getFollowRange()), (entity) -> entity instanceof PassiveEntity), + this.targetPredicate, + this.mob, + this.mob.getX(), + this.mob.getEyeY(), + this.mob.getZ() + ); + } + + @Override + public void start() { + this.mob.setTarget(this.targetEntity); + super.start(); + } +} diff --git a/src/main/java/com/equilibrium/entity/goal/BreakBlockGoal.java b/src/main/java/com/equilibrium/entity/goal/BreakBlockGoal.java index aba371c..5d9316e 100644 --- a/src/main/java/com/equilibrium/entity/goal/BreakBlockGoal.java +++ b/src/main/java/com/equilibrium/entity/goal/BreakBlockGoal.java @@ -1,126 +1,202 @@ -//package com.equilibrium.entity.goal; -// -//import net.minecraft.block.Block; -//import net.minecraft.block.BlockState; -//import net.minecraft.block.Blocks; -//import net.minecraft.entity.Entity; -//import net.minecraft.entity.LivingEntity; -//import net.minecraft.entity.ai.NavigationConditions; -//import net.minecraft.entity.ai.NoPenaltyTargeting; -//import net.minecraft.entity.ai.goal.Goal; -//import net.minecraft.entity.damage.DamageType; -//import net.minecraft.entity.mob.MobEntity; -//import net.minecraft.entity.mob.PathAwareEntity; -//import net.minecraft.item.PickaxeItem; -//import net.minecraft.item.ShovelItem; -//import net.minecraft.registry.tag.BlockTags; -//import net.minecraft.registry.tag.DamageTypeTags; -//import net.minecraft.registry.tag.FluidTags; -//import net.minecraft.registry.tag.TagKey; -//import net.minecraft.util.math.BlockPos; -//import net.minecraft.util.math.Vec3d; -//import net.minecraft.world.BlockView; -//import net.minecraft.world.GameRules; -//import net.minecraft.world.WorldEvents; -//import org.jetbrains.annotations.NotNull; -//import org.jetbrains.annotations.Nullable; -// -//import java.util.EnumSet; -//import java.util.function.Function; -// -//public class BreakBlockGoal extends Goal { -// protected final MobEntity mob; -// protected BlockPos breakPos = BlockPos.ORIGIN; -// protected BlockState breakState = Blocks.AIR.getDefaultState(); -// protected boolean shouldStop; -// private float offsetX, offsetZ; -// protected int breakProgress = -1, prevBreakStage = -1; -// -// public BreakBlockGoal(MobEntity mob) { -// this.mob = mob; -// if (!NavigationConditions.hasMobNavigation(mob)) { -// throw new IllegalArgumentException("Unsupported mob type for BreakBlockGoal"); -// } -// } -// -// @Override -// public boolean canStart() { -// //Choose a block to break -// LivingEntity target = this.mob.getTarget(); -// if (target == null || !this.mob.getWorld().getGameRules().getBoolean(GameRules.DO_MOB_GRIEFING)) return false; -// for (double[] findPos : EntityHelper.FIND_NEAREST_BLOCKS) { -// //Should not dig upward when not above target -// if (findPos[1] == -1 && this.mob.getY() <= target.getY()) continue; -// //Should not dig downward when not under target -// if (findPos[1] == 2 && this.mob.getY() >= target.getY()) continue; -// //Choose a pos to break -// BlockPos pendingBreakPos = new BlockPos((int) (this.mob.getX() + findPos[0]), (int) (this.mob.getY() + findPos[1]), (int) (this.mob.getZ() + findPos[2])); -// //Should not break the block behind itself -// BlockPos backPos = EntityHelper.getPosFacing(this.mob, true); -// if (pendingBreakPos.getX() == backPos.getX() && pendingBreakPos.getZ() == backPos.getZ()) continue; -// BlockState pendingBreakState = this.mob.world.getBlockState(pendingBreakPos); -// Material material = pendingBreakState.getMaterial(); -// //Avoid redundant destroying -// if (material == Material.REPLACEABLE_PLANT || material == Material.PLANT) continue; -// //Determine whether to start -// if (canBreakBlock(pendingBreakState) && this.mob.getNavigation().isIdle()) { -// this.breakPos = pendingBreakPos; -// this.breakState = pendingBreakState; -// return true; -// } -// } -// return false; -// } -// -// @Override -// public void start() { -// this.shouldStop = false; -// this.offsetX = (float) ((double) this.breakPos.getX() + 0.5 - this.mob.getX()); -// this.offsetZ = (float) ((double) this.breakPos.getZ() + 0.5 - this.mob.getZ()); -// this.breakProgress = 0; -// } -// -// @Override -// public void stop() { -// super.stop(); -// this.mob.getWorld().setBlockBreakingInfo(this.mob.getId(), this.breakPos, -1); -// } -// -// @Override -// public boolean shouldRunEveryTick() { -// return true; -// } -// -// -// @Override -// public boolean shouldContinue() { -//// System.out.println("state=" + this.breakState + "\t!shouldStop=" + !this.shouldStop + "\t breakProgress=" + this.breakProgress + "\t max=" + this.getMaxProgress() + "\t canBreak=" + canBreakBlock(this.breakState) + "\t withinDistance=" + this.breakPos.isWithinDistance(this.mob.getPos(), 5) + "\tTimeSinceLastAttack=" + this.mob.getDamageTracker().getTimeSinceLastAttack() + "\tRecently attacked=" + this.mob.getDamageTracker().wasRecentlyAttacked()); -// return !this.shouldStop && this.breakProgress <= this.getMaxProgress() && canBreakBlock(this.breakState) && this.breakPos.isWithinDistance(this.mob.getPos(), 5) && (!this.mob.getDamageTracker().wasRecentlyAttacked() || this.mob.getDamageTracker().getTimeSinceLastAttack() > 20); -// } -// -// public int getMaxProgress() { -// return (int) (HcsDifficulty.chooseVal(this.mob.getWorld(), 2000.0F, 1000.0F, 500.0F) * this.breakState.getBlock().getHardness() * ((this.mob.getMainHandStack().getItem() instanceof ShovelItem) ? 0.2F : 1.0F)); -// } -// -// public boolean canBreakBlock(@NotNull BlockState state) { -// return !state.isAir() && (DoorBlock.isWoodenDoor(state) || (DigRestrictHelper.canBreak(this.mob.getMainHandStack().getItem(), state) && (state.getBlock().getHardness() < Blocks.STONE.getHardness() || this.mob.getMainHandStack().getItem() instanceof PickaxeItem)) /*&& !state.getBlock().getTranslationKey().contains("brick")*/) || state.isIn(BlockTags.SHOVEL_MINEABLE); -// } -// -// @Override -// public void tick() { -// ++this.breakProgress; -// if (this.offsetX * (float) ((double) this.breakPos.getX() + 0.5 - this.mob.getX()) + this.offsetZ * (float) ((double) this.breakPos.getZ() + 0.5 - this.mob.getZ()) < 0.0f) -// this.shouldStop = true; -// if (this.breakProgress % 40 == 0 && !this.mob.handSwinging) this.mob.swingHand(this.mob.getActiveHand()); -// int breakStage = (int) ((float) this.breakProgress / (float) this.getMaxProgress() * 10.0f); -// if (breakStage != this.prevBreakStage) { -// this.mob.getWorld().setBlockBreakingInfo(this.mob.getId(), this.breakPos, breakStage); -// this.mob.getWorld().syncWorldEvent(WorldEvents.BLOCK_BROKEN, this.breakPos, Block.getRawIdFromState(this.breakState)); -// this.prevBreakStage = breakStage; -// } -// if (this.breakProgress >= this.getMaxProgress()) { -// this.mob.getWorld().breakBlock(this.breakPos, true, this.mob); -// WorldHelper.checkBlockGravity(this.mob.getWorld(), this.breakPos); -// } -// } -//} \ No newline at end of file +package com.equilibrium.entity.goal; + +import com.equilibrium.MITEequilibrium; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.FallingBlock; +import net.minecraft.entity.Entity; +import net.minecraft.entity.FallingBlockEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.goal.Goal; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.Difficulty; +import net.minecraft.world.World; +import net.minecraft.world.WorldEvents; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Predicate; + +public class BreakBlockGoal extends Goal { + private static final int MIN_MAX_PROGRESS = 240; + private final Predicate difficultySufficientPredicate; + private final MobEntity mob; + + private int maxProgress; + + + protected boolean shouldStop; + + private float offsetX, offsetZ; + + protected BlockPos breakPos = BlockPos.ORIGIN; + protected BlockState breakState = Blocks.AIR.getDefaultState(); + protected int breakProgress = -1, prevBreakStage = -1; + + @Override + public void start() { + this.shouldStop = false; + this.offsetX = (float) ((double) this.breakPos.getX() + 0.5 - this.mob.getX()); + this.offsetZ = (float) ((double) this.breakPos.getZ() + 0.5 - this.mob.getZ()); + this.breakProgress = 0; + } + + + + @Override + public void stop() { + super.stop(); + this.mob.getWorld().setBlockBreakingInfo(this.mob.getId(), this.breakPos, -1); + } + + + + + + + public BreakBlockGoal(MobEntity mob, int maxProgress, Predicate difficultySufficientPredicate) { + this.mob = mob; + this.maxProgress = maxProgress; + this.difficultySufficientPredicate = difficultySufficientPredicate; + + } + float harvestBonus = 1; + + @Override + public boolean shouldRunEveryTick() { + return true; + } + + + protected int getMaxProgress() { + return(int)(800*harvestBonus); + } + + public static final double[][] FIND_NEAREST_BLOCKS = {{0, -1, 0}, {0, 1, 0}, {0, 2, 0}, {-1, 0, 0}, {-1, 1, 0}, {1, 0, 0}, {1, 1, 0}, {0, 0, 1}, {0, 1, 1}, {0, 0, -1}, {0, 1, -1}}; + + public static BlockPos getPosFacing(Entity entity, boolean isBackward) { + if (entity == null) { + MITEequilibrium.LOGGER.error("EntityHelper/getPosFacing;entity==null"); + return BlockPos.ORIGIN; + } + var entityPos = entity.getBlockPos(); + var facing = entity.getHorizontalFacing(); + return switch (isBackward ? facing.getOpposite() : facing) { + case EAST -> entityPos.east(); + case SOUTH -> entityPos.south(); + case WEST -> entityPos.west(); + default -> entityPos.north(); + }; + } + + public boolean canBreakBlock(@NotNull BlockState state) { + return !state.isAir(); + } + + + @Override + public boolean canStart() { + //Choose a block to break + LivingEntity target = this.mob.getTarget(); + if (target != null) { + for (double[] findPos : FIND_NEAREST_BLOCKS) { + //Should not dig upward when not above target + if (findPos[1] == -1 && this.mob.getY() <= target.getY()) continue; + //Should not dig downward when not under target + if (findPos[1] == 2 && this.mob.getY() >= target.getY()) continue; + //Choose a pos to break + BlockPos pendingBreakPos = new BlockPos((int) (this.mob.getX() + findPos[0]), (int) (this.mob.getY() + findPos[1]), (int) (this.mob.getZ() + findPos[2])); + //Should not break the block behind itself + BlockPos backPos = getPosFacing(this.mob, true); + if (pendingBreakPos.getX() == backPos.getX() && pendingBreakPos.getZ() == backPos.getZ()) continue; + BlockState pendingBreakState = this.mob.getWorld().getBlockState(pendingBreakPos); + + //Determine whether to start + if (canBreakBlock(pendingBreakState) && this.mob.getNavigation().isIdle()) { + this.breakPos = pendingBreakPos; + this.breakState = pendingBreakState; + if(this.mob.getMainHandStack().isSuitableFor(breakState)) + harvestBonus=0.5f; + return true; + } + } + } + return false; + } + + + @Override + public boolean shouldContinue() { + if (this.mob.getTarget() == null) { + return false; + } + boolean b1 = !this.shouldStop; + boolean b2 = this.breakProgress <= this.getMaxProgress(); + boolean b3 = canBreakBlock(this.breakState); + boolean b4 = this.breakPos.isWithinDistance(this.mob.getPos(), 2); + boolean b5 = this.mob.getDamageTracker().getTimeSinceLastAttack() > 20; + return (!this.shouldStop) && (this.breakProgress <= this.getMaxProgress() )&& (canBreakBlock(this.breakState)) &&( this.breakPos.isWithinDistance(this.mob.getPos(), 2)); + } + + + + + public static final Predicate IS_GRAVITY_AFFECTED = state -> state != null && (state.isOf(Blocks.GRAVEL)); + + + public static void checkBlockGravity(World world, BlockPos pos) { + try { + if (!(world instanceof ServerWorld)) return; + for (BlockPos bp : new BlockPos[]{pos, pos.up(), pos.down(), pos.east(), pos.west(), pos.south(), pos.north()}) { + //Check the pos and its immediate pos + BlockState state = world.getBlockState(bp); + if (IS_GRAVITY_AFFECTED.test(state)) { + if (FallingBlock.canFallThrough(world.getBlockState(bp.down())) || bp.getY() < world.getBottomY()) { + if (IS_GRAVITY_AFFECTED.test(state)) FallingBlockEntity.spawnFromBlock(world, bp, state); + //Recurse for further neighbor tick + for (BlockPos bpn : new BlockPos[]{bp.up(), bp.down(), bp.east(), bp.west(), bp.south(), bp.north()}) + checkBlockGravity(world, bpn); + } + } + } + } catch (StackOverflowError error) { + MITEequilibrium.LOGGER.error("WorldHelper/checkBlockGravity(): StackOverflowError"); + } + } + + + + + + + //maxProgress = 800 + @Override + public void tick() { + ++this.breakProgress; + //更新方块状态,比如在挖掘一半时被提前破坏了 + BlockState pendingBreakState = this.mob.getWorld().getBlockState(this.breakPos); + if(pendingBreakState.isOf(Blocks.AIR)) + this.shouldStop = true; + if (this.offsetX * (float) ((double) this.breakPos.getX() + 0.5 - this.mob.getX()) + this.offsetZ * (float) ((double) this.breakPos.getZ() + 0.5 - this.mob.getZ()) < 0.0f) + this.shouldStop = true; + + + float breakStage = ((float) this.breakProgress / getMaxProgress() )*8; + if ((int)breakStage != this.prevBreakStage) { + this.mob.swingHand(this.mob.getActiveHand()); + this.mob.getWorld().setBlockBreakingInfo(this.mob.getId(), this.breakPos, (int)breakStage); + this.mob.getWorld().syncWorldEvent(WorldEvents.BLOCK_BROKEN, this.breakPos, Block.getRawIdFromState(this.breakState)); + this.prevBreakStage = (int)breakStage; + } + if (this.breakProgress >= this.getMaxProgress()) { + this.mob.getWorld().breakBlock(this.breakPos, true, this.mob); + checkBlockGravity(this.mob.getWorld(), this.breakPos); + } + } + + + + +} diff --git a/src/main/java/com/equilibrium/entity/goal/LookAtTargetGoal.java b/src/main/java/com/equilibrium/entity/goal/LookAtTargetGoal.java new file mode 100644 index 0000000..ad3b895 --- /dev/null +++ b/src/main/java/com/equilibrium/entity/goal/LookAtTargetGoal.java @@ -0,0 +1,27 @@ +package com.equilibrium.entity.goal; + +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.goal.Goal; +import net.minecraft.entity.mob.MobEntity; + +public class LookAtTargetGoal extends Goal { + private final MobEntity mob; + private LivingEntity target; + + public LookAtTargetGoal(MobEntity mob) { + this.mob = mob; + } + + @Override + public boolean canStart() { + this.target = this.mob.getTarget(); + return this.target != null && this.target.isAlive(); + } + + @Override + public void tick() { + if (this.target != null) { + this.mob.getLookControl().lookAt(this.target, 30.0F, 30.0F); + } + } +} diff --git a/src/main/java/com/equilibrium/mixin/entitymixin/ZombieEntityMixin.java b/src/main/java/com/equilibrium/mixin/entitymixin/ZombieEntityMixin.java index f756d0d..8c2b9d8 100644 --- a/src/main/java/com/equilibrium/mixin/entitymixin/ZombieEntityMixin.java +++ b/src/main/java/com/equilibrium/mixin/entitymixin/ZombieEntityMixin.java @@ -1,29 +1,43 @@ package com.equilibrium.mixin.entitymixin; -import net.minecraft.block.Blocks; +import com.equilibrium.entity.goal.AdvanceActiveTargetGoal; +import com.equilibrium.entity.goal.AttackPassiveEntitiesGoal; +import com.equilibrium.entity.goal.BreakBlockGoal; +import com.equilibrium.entity.goal.LookAtTargetGoal; +import com.mojang.serialization.Decoder; +import net.minecraft.entity.EntityData; import net.minecraft.entity.EntityType; +import net.minecraft.entity.ItemEntity; +import net.minecraft.entity.SpawnReason; import net.minecraft.entity.ai.goal.*; + import net.minecraft.entity.attribute.DefaultAttributeContainer; import net.minecraft.entity.attribute.EntityAttributes; -import net.minecraft.entity.effect.StatusEffect; +import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.effect.StatusEffectInstance; import net.minecraft.entity.effect.StatusEffects; import net.minecraft.entity.mob.HostileEntity; -import net.minecraft.entity.mob.PathAwareEntity; import net.minecraft.entity.mob.ZombieEntity; import net.minecraft.entity.mob.ZombifiedPiglinEntity; import net.minecraft.entity.passive.IronGolemEntity; import net.minecraft.entity.passive.MerchantEntity; +import net.minecraft.entity.passive.PassiveEntity; import net.minecraft.entity.passive.TurtleEntity; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.registry.tag.FluidTags; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.SimpleInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.registry.tag.ItemTags; import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundEvents; -import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.Difficulty; +import net.minecraft.world.LocalDifficulty; +import net.minecraft.world.ServerWorldAccess; import net.minecraft.world.World; -import net.minecraft.world.WorldAccess; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -39,6 +53,17 @@ protected ZombieEntityMixin(EntityType entityType, Worl super(entityType, world); } + +// @Unique +// @Override +// public boolean canPickupItem(ItemStack stack) { +// return stack.isOf(Items.EGG) && this.isBaby() && this.hasVehicle() ? false : true; +// } + @Inject(method = "canPickupItem",at = @At(value = "HEAD"),cancellable = true) + public void canPickupItem(ItemStack stack, CallbackInfoReturnable cir) { + cir.setReturnValue(true); + } + @Inject(method = "createZombieAttributes", at = @At(value = "HEAD"), cancellable = true) private static void createZombieAttributes(CallbackInfoReturnable cir) { cir.cancel(); @@ -53,18 +78,29 @@ private static void createZombieAttributes(CallbackInfoReturnable(this, PlayerEntity.class, false)); + this.goalSelector.add(3, new BreakBlockGoal(this, 800, difficulty -> difficulty == Difficulty.NORMAL || difficulty == Difficulty.HARD)); + this.targetSelector.add(4, new ActiveTargetGoal(this, MerchantEntity.class, false)); + this.targetSelector.add(4, new ActiveTargetGoal(this, IronGolemEntity.class, true)); + this.targetSelector.add(4, new AdvanceActiveTargetGoal<>(this,PassiveEntity.class, false)); this.targetSelector.add(5, new ActiveTargetGoal(this, TurtleEntity.class, 10, true, false, TurtleEntity.BABY_TURTLE_ON_LAND_FILTER)); + this.targetSelector.add(3, new ActiveTargetGoal<>(this, PassiveEntity.class, true, false)); + + // 添加盯着目标的目标选择器 + this.goalSelector.add(3, new LookAtTargetGoal(this)); } @Inject(method = "tick", at = @At(value = "TAIL")) @@ -73,5 +109,108 @@ public void tick(CallbackInfo ci) { this.addStatusEffect(statusEffectInstance); } } + @Unique + @Override + protected Vec3i getItemPickUpRangeExpander() { + return super.getItemPickUpRangeExpander().add(3,0,3); + + } + @Unique + @Override + protected void loot(ItemEntity itemEntity) { + ItemStack itemStack = itemEntity.getStack(); + if (this.canPickupItem(itemStack)) { + if (isFood(itemStack)) { + ItemStack leftover = this.addFoodToInventory(itemStack); + if (leftover.isEmpty()) { + itemEntity.discard(); + // 播放音效 + this.getWorld().playSound(null, this.getBlockPos(), SoundEvents.ENTITY_ITEM_PICKUP, SoundCategory.PLAYERS, 1.0F, 1.0F); + } else { + itemEntity.setStack(leftover); + } + } else { + super.loot(itemEntity); + } + } + } + + @Unique + private ItemStack addFoodToInventory(ItemStack stack) { + for (int i = 0; i < this.getInventory().size(); i++) { + ItemStack itemStack = this.getInventory().getStack(i); + if (itemStack.isEmpty()) { + this.getInventory().setStack(i, stack.copy()); + return ItemStack.EMPTY; + } else if (ItemStack.areItemsEqual(itemStack, stack) && itemStack.getCount() < itemStack.getMaxCount()) { + int remaining = itemStack.getMaxCount() - itemStack.getCount(); + int countToAdd = Math.min(stack.getCount(), remaining); + itemStack.increment(countToAdd); + stack.decrement(countToAdd); + if (stack.isEmpty()) { + return ItemStack.EMPTY; + } + } + } + return stack; + } + + + + + // 处理僵尸死亡事件 + @Unique + @Override + public void onDeath(DamageSource source) { + super.onDeath(source); + dropFoodOnDeath(); + } + + @Unique + private void dropFoodOnDeath() { + for (int i = 0; i < this.getInventory().size(); i++) { + ItemStack stack = this.getInventory().getStack(i); + if (isFood(stack)) { + int dropCount = stack.getCount() / 2; + if (dropCount > 0) { + ItemStack dropStack = stack.copy(); + dropStack.setCount(dropCount); + this.getWorld().spawnEntity(new ItemEntity(this.getWorld(), this.getX(), this.getY(), this.getZ(), dropStack)); + stack.decrement(dropCount); + } + } + } + + } + + + @Unique + private boolean isFood(ItemStack stack) { + return stack.isIn(ItemTags.MEAT); + } + + @Unique + private final SimpleInventory inventory = new SimpleInventory(10); + @Unique + public SimpleInventory getInventory() { + return inventory; + } + + @Inject(method = "initialize",at = @At(value = "TAIL")) + public void initialize(ServerWorldAccess world, LocalDifficulty difficulty, SpawnReason spawnReason, EntityData entityData, CallbackInfoReturnable cir) { + this.setCanPickUpLoot(true); + + } + + + + + + + + + + + } diff --git a/src/main/java/com/equilibrium/mixin/entitymixin/goal_mixin/LookAtEntityGoalMixin.java b/src/main/java/com/equilibrium/mixin/entitymixin/goal_mixin/LookAtEntityGoalMixin.java index 9c3141a..0e4d2d4 100644 --- a/src/main/java/com/equilibrium/mixin/entitymixin/goal_mixin/LookAtEntityGoalMixin.java +++ b/src/main/java/com/equilibrium/mixin/entitymixin/goal_mixin/LookAtEntityGoalMixin.java @@ -17,13 +17,4 @@ @Mixin(LookAtEntityGoal.class) public abstract class LookAtEntityGoalMixin { - @Shadow - private int lookTime; - @Shadow - @Final - protected MobEntity mob; - @Shadow - @Final - protected float chance = 1; - } diff --git a/src/main/java/com/equilibrium/tags/ModBlockTags.java b/src/main/java/com/equilibrium/tags/ModBlockTags.java index 8106432..8139fa3 100644 --- a/src/main/java/com/equilibrium/tags/ModBlockTags.java +++ b/src/main/java/com/equilibrium/tags/ModBlockTags.java @@ -25,6 +25,14 @@ public class ModBlockTags { public static final TagKey HARVEST_THREE = of("block_harvest_3"); public static final TagKey HARVEST_FOUR = of("block_harvest_4"); public static final TagKey HARVEST_FIVE = of("block_harvest_5"); + + + public static final TagKey TRANSPARENT_FOR_ZOMBIE= of("transparent_for_zombie"); + + + + + private static TagKey of(String id) { return TagKey.of(RegistryKeys.BLOCK, Identifier.of("miteequilibrium",id)); } diff --git a/src/main/resources/data/miteequilibrium/tags/block/transparent_for_zombie.json b/src/main/resources/data/miteequilibrium/tags/block/transparent_for_zombie.json new file mode 100644 index 0000000..8c2a0b4 --- /dev/null +++ b/src/main/resources/data/miteequilibrium/tags/block/transparent_for_zombie.json @@ -0,0 +1,10 @@ +{ + "values": [ + "#minecraft:leaves", + "minecraft:glass", + "minecraft:dirt", + "minecraft:grass_block", + "minecraft:gravel", + "minecraft:glass_pane" + ] +} \ No newline at end of file diff --git a/src/main/resources/name.accesswidener b/src/main/resources/name.accesswidener index b9cb170..bbfd217 100644 --- a/src/main/resources/name.accesswidener +++ b/src/main/resources/name.accesswidener @@ -2,6 +2,9 @@ accessWidener v1 named + +accessible field net/minecraft/entity/damage/DamageTracker recentlyAttacked Z +accessible method net/minecraft/entity/ai/TargetPredicate (Z)V accessible method net/minecraft/entity/mob/ZombieEntity initCustomGoals ()V accessible class net/minecraft/entity/mob/ZombieEntity$DestroyEggGoal