From b096c490ad7c7cde4bc4609c447305bacc9bbcd1 Mon Sep 17 00:00:00 2001 From: tartaric_acid Date: Mon, 7 Oct 2024 20:42:44 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90=E5=A5=B3?= =?UTF-8?q?=E4=BB=86=E9=92=93=E9=B1=BC=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 2 +- .../client/init/InitEntitiesRender.java | 2 + .../entity/MaidFishingHookRenderer.java | 121 +++++ .../ai/brain/ride/MaidRideFindWaterTask.java | 104 ++++ .../ai/brain/task/MaidFindChairTask.java | 54 ++ .../entity/passive/EntityMaid.java | 12 +- .../entity/projectile/MaidFishingHook.java | 465 ++++++++++++++++++ .../entity/task/TaskFishing.java | 48 ++ .../entity/task/TaskManager.java | 1 + .../touhoulittlemaid/init/InitEntities.java | 2 + 10 files changed, 807 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/github/tartaricacid/touhoulittlemaid/client/renderer/entity/MaidFishingHookRenderer.java create mode 100644 src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/ai/brain/ride/MaidRideFindWaterTask.java create mode 100644 src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/ai/brain/task/MaidFindChairTask.java create mode 100644 src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/projectile/MaidFishingHook.java create mode 100644 src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/task/TaskFishing.java diff --git a/gradle.properties b/gradle.properties index 53b07d890..a97a816aa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.jvmargs=-Xmx6G org.gradle.daemon=false -mod_version=1.1.12-hotfix2 +mod_version=1.1.13 forge_version=1.20.1-47.1.0 mc_version=1.20.1 jei_version=15.0.0.12 diff --git a/src/main/java/com/github/tartaricacid/touhoulittlemaid/client/init/InitEntitiesRender.java b/src/main/java/com/github/tartaricacid/touhoulittlemaid/client/init/InitEntitiesRender.java index 1667d4d3f..512b077b7 100644 --- a/src/main/java/com/github/tartaricacid/touhoulittlemaid/client/init/InitEntitiesRender.java +++ b/src/main/java/com/github/tartaricacid/touhoulittlemaid/client/init/InitEntitiesRender.java @@ -9,6 +9,7 @@ import com.github.tartaricacid.touhoulittlemaid.entity.passive.EntityMaid; import com.github.tartaricacid.touhoulittlemaid.entity.projectile.EntityDanmaku; import com.github.tartaricacid.touhoulittlemaid.entity.projectile.EntityThrowPowerPoint; +import com.github.tartaricacid.touhoulittlemaid.entity.projectile.MaidFishingHook; import com.github.tartaricacid.touhoulittlemaid.tileentity.*; import net.minecraft.client.renderer.blockentity.BlockEntityRenderers; import net.minecraft.client.renderer.entity.EntityRenderers; @@ -34,6 +35,7 @@ public static void onEntityRenderers(EntityRenderersEvent.RegisterRenderers evt) EntityRenderers.register(EntityTombstone.TYPE, EntityTombstoneRenderer::new); EntityRenderers.register(EntitySit.TYPE, EntitySitRenderer::new); EntityRenderers.register(EntityBroom.TYPE, EntityBroomRender::new); + EntityRenderers.register(MaidFishingHook.TYPE, MaidFishingHookRenderer::new); EntityRenderers.register(EntityType.SLIME, EntityYukkuriSlimeRender::new); EntityRenderers.register(EntityType.MAGMA_CUBE, EntityMarisaYukkuriSlimeRender::new); diff --git a/src/main/java/com/github/tartaricacid/touhoulittlemaid/client/renderer/entity/MaidFishingHookRenderer.java b/src/main/java/com/github/tartaricacid/touhoulittlemaid/client/renderer/entity/MaidFishingHookRenderer.java new file mode 100644 index 000000000..9d989f09d --- /dev/null +++ b/src/main/java/com/github/tartaricacid/touhoulittlemaid/client/renderer/entity/MaidFishingHookRenderer.java @@ -0,0 +1,121 @@ +package com.github.tartaricacid.touhoulittlemaid.client.renderer.entity; + +import com.github.tartaricacid.touhoulittlemaid.entity.passive.EntityMaid; +import com.github.tartaricacid.touhoulittlemaid.entity.projectile.MaidFishingHook; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.math.Axis; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.client.renderer.entity.EntityRendererProvider; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.HumanoidArm; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import org.joml.Matrix3f; +import org.joml.Matrix4f; + +@OnlyIn(Dist.CLIENT) +public class MaidFishingHookRenderer extends EntityRenderer { + private static final ResourceLocation TEXTURE_LOCATION = new ResourceLocation("textures/entity/fishing_hook.png"); + private static final RenderType RENDER_TYPE = RenderType.entityCutout(TEXTURE_LOCATION); + private static final double VIEW_BOBBING_SCALE = 960.0D; + + public MaidFishingHookRenderer(EntityRendererProvider.Context pContext) { + super(pContext); + } + + public void render(MaidFishingHook pEntity, float pEntityYaw, float pPartialTicks, PoseStack pPoseStack, MultiBufferSource pBuffer, int pPackedLight) { + EntityMaid maid = pEntity.getPlayerOwner(); + if (maid != null) { + pPoseStack.pushPose(); + pPoseStack.pushPose(); + pPoseStack.scale(0.5F, 0.5F, 0.5F); + pPoseStack.mulPose(this.entityRenderDispatcher.cameraOrientation()); + pPoseStack.mulPose(Axis.YP.rotationDegrees(180.0F)); + PoseStack.Pose posestack$pose = pPoseStack.last(); + Matrix4f matrix4f = posestack$pose.pose(); + Matrix3f matrix3f = posestack$pose.normal(); + VertexConsumer vertexconsumer = pBuffer.getBuffer(RENDER_TYPE); + vertex(vertexconsumer, matrix4f, matrix3f, pPackedLight, 0.0F, 0, 0, 1); + vertex(vertexconsumer, matrix4f, matrix3f, pPackedLight, 1.0F, 0, 1, 1); + vertex(vertexconsumer, matrix4f, matrix3f, pPackedLight, 1.0F, 1, 1, 0); + vertex(vertexconsumer, matrix4f, matrix3f, pPackedLight, 0.0F, 1, 0, 0); + pPoseStack.popPose(); + int i = maid.getMainArm() == HumanoidArm.RIGHT ? 1 : -1; + ItemStack itemstack = maid.getMainHandItem(); + if (!itemstack.canPerformAction(net.minecraftforge.common.ToolActions.FISHING_ROD_CAST)) { + i = -i; + } + + float f = maid.getAttackAnim(pPartialTicks); + float f1 = Mth.sin(Mth.sqrt(f) * (float) Math.PI); + float f2 = Mth.lerp(pPartialTicks, maid.yBodyRotO, maid.yBodyRot) * ((float) Math.PI / 180F); + double d0 = (double) Mth.sin(f2); + double d1 = (double) Mth.cos(f2); + double d2 = (double) i * 0.35D; + double d3 = 0.8D; + double d4; + double d5; + double d6; + float f3; + + { + d4 = Mth.lerp((double) pPartialTicks, maid.xo, maid.getX()) - d1 * d2 - d0 * 0.8D; + d5 = maid.yo + (double) maid.getEyeHeight() + (maid.getY() - maid.yo) * (double) pPartialTicks - 0.45D; + d6 = Mth.lerp((double) pPartialTicks, maid.zo, maid.getZ()) - d0 * d2 + d1 * 0.8D; + f3 = -0.1875F; + } + + double d9 = Mth.lerp((double) pPartialTicks, pEntity.xo, pEntity.getX()); + double d10 = Mth.lerp((double) pPartialTicks, pEntity.yo, pEntity.getY()) + 0.25D; + double d8 = Mth.lerp((double) pPartialTicks, pEntity.zo, pEntity.getZ()); + float f4 = (float) (d4 - d9); + float f5 = (float) (d5 - d10) + f3; + float f6 = (float) (d6 - d8); + VertexConsumer vertexconsumer1 = pBuffer.getBuffer(RenderType.lineStrip()); + PoseStack.Pose posestack$pose1 = pPoseStack.last(); + int j = 16; + + for (int k = 0; k <= 16; ++k) { + stringVertex(f4, f5, f6, vertexconsumer1, posestack$pose1, fraction(k, 16), fraction(k + 1, 16)); + } + + pPoseStack.popPose(); + super.render(pEntity, pEntityYaw, pPartialTicks, pPoseStack, pBuffer, pPackedLight); + } + } + + private static float fraction(int pNumerator, int pDenominator) { + return (float) pNumerator / (float) pDenominator; + } + + private static void vertex(VertexConsumer pConsumer, Matrix4f pPose, Matrix3f pNormal, int pLightmapUV, float pX, int pY, int pU, int pV) { + pConsumer.vertex(pPose, pX - 0.5F, (float) pY - 0.5F, 0.0F).color(255, 255, 255, 255).uv((float) pU, (float) pV).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(pLightmapUV).normal(pNormal, 0.0F, 1.0F, 0.0F).endVertex(); + } + + private static void stringVertex(float pX, float pY, float pZ, VertexConsumer pConsumer, PoseStack.Pose pPose, float p_174124_, float p_174125_) { + float f = pX * p_174124_; + float f1 = pY * (p_174124_ * p_174124_ + p_174124_) * 0.5F + 0.25F; + float f2 = pZ * p_174124_; + float f3 = pX * p_174125_ - f; + float f4 = pY * (p_174125_ * p_174125_ + p_174125_) * 0.5F + 0.25F - f1; + float f5 = pZ * p_174125_ - f2; + float f6 = Mth.sqrt(f3 * f3 + f4 * f4 + f5 * f5); + f3 /= f6; + f4 /= f6; + f5 /= f6; + pConsumer.vertex(pPose.pose(), f, f1, f2).color(0, 0, 0, 255).normal(pPose.normal(), f3, f4, f5).endVertex(); + } + + /** + * Returns the location of an entity's texture. + */ + public ResourceLocation getTextureLocation(MaidFishingHook pEntity) { + return TEXTURE_LOCATION; + } +} diff --git a/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/ai/brain/ride/MaidRideFindWaterTask.java b/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/ai/brain/ride/MaidRideFindWaterTask.java new file mode 100644 index 000000000..c2256c3a4 --- /dev/null +++ b/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/ai/brain/ride/MaidRideFindWaterTask.java @@ -0,0 +1,104 @@ +package com.github.tartaricacid.touhoulittlemaid.entity.ai.brain.ride; + +import com.github.tartaricacid.touhoulittlemaid.entity.ai.brain.task.MaidCheckRateTask; +import com.github.tartaricacid.touhoulittlemaid.entity.passive.EntityMaid; +import com.github.tartaricacid.touhoulittlemaid.entity.projectile.MaidFishingHook; +import com.google.common.collect.ImmutableMap; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.common.Tags; + +public class MaidRideFindWaterTask extends MaidCheckRateTask { + private static final int MAX_DELAY_TIME = 20; + + private final int verticalSearchRange; + private final int searchRange; + + protected int verticalSearchStart; + private BlockPos waterPos = null; + + public MaidRideFindWaterTask(int searchRange, int verticalSearchRange) { + super(ImmutableMap.of()); + this.searchRange = searchRange; + this.verticalSearchRange = verticalSearchRange; + this.setMaxCheckRate(MAX_DELAY_TIME); + } + + @Override + protected boolean checkExtraStartConditions(ServerLevel worldIn, EntityMaid owner) { + return super.checkExtraStartConditions(worldIn, owner) && owner.fishing == null; + } + + @Override + protected void start(ServerLevel worldIn, EntityMaid maid, long gameTimeIn) { + if (hasFishingRod(maid)) { + if (waterPos == null) { + this.searchForDestination(worldIn, maid); + return; + } + if (!waterPosCheck(maid)) { + waterPos = null; + return; + } + if (isWater(worldIn, waterPos)) { + ItemStack mainHandItem = maid.getMainHandItem(); + Vec3 centerPos = Vec3.atCenterOf(this.waterPos); + + worldIn.playSound(null, maid.getX(), maid.getY(), maid.getZ(), SoundEvents.FISHING_BOBBER_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (worldIn.getRandom().nextFloat() * 0.4F + 0.8F)); + int lureSpeedBonus = EnchantmentHelper.getFishingSpeedBonus(mainHandItem); + int luckBonus = EnchantmentHelper.getFishingLuckBonus(mainHandItem); + MaidFishingHook fishingHook = new MaidFishingHook(maid, worldIn, luckBonus, lureSpeedBonus); + fishingHook.moveTo(centerPos); + worldIn.addFreshEntity(fishingHook); + maid.getLookControl().setLookAt(centerPos); + maid.swing(InteractionHand.MAIN_HAND); + } else { + waterPos = null; + } + } + } + + protected final void searchForDestination(ServerLevel worldIn, EntityMaid maid) { + BlockPos centrePos = maid.getBrainSearchPos(); + BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); + for (int y = this.verticalSearchStart; y <= this.verticalSearchRange; y = y > 0 ? -y : 1 - y) { + for (int i = 0; i < searchRange; ++i) { + for (int x = 0; x <= i; x = x > 0 ? -x : 1 - x) { + for (int z = x < i && x > -i ? i : 0; z <= i; z = z > 0 ? -z : 1 - z) { + mutableBlockPos.setWithOffset(centrePos, x, y - 1, z); + if (maid.isWithinRestriction(mutableBlockPos) && isWater(worldIn, mutableBlockPos)) { + maid.getLookControl().setLookAt(mutableBlockPos.getX(), mutableBlockPos.getY(), mutableBlockPos.getZ()); + this.waterPos = mutableBlockPos; + this.setNextCheckTickCount(5); + return; + } + } + } + } + } + } + + private boolean hasFishingRod(EntityMaid entityMaid) { + ItemStack mainHandItem = entityMaid.getMainHandItem(); + return mainHandItem.is(Tags.Items.TOOLS_FISHING_RODS); + } + + private boolean isWater(ServerLevel worldIn, BlockPos basePos) { + BlockState blockState = worldIn.getBlockState(basePos); + return blockState.is(Blocks.WATER); + } + + private boolean waterPosCheck(EntityMaid maid) { + int distanceSqr = this.searchRange * this.searchRange; + Vec3 waterVec = new Vec3(this.waterPos.getX(), this.waterPos.getY(), this.waterPos.getZ()); + return maid.distanceToSqr(waterVec) < distanceSqr && maid.isWithinRestriction(this.waterPos); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/ai/brain/task/MaidFindChairTask.java b/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/ai/brain/task/MaidFindChairTask.java new file mode 100644 index 000000000..74f3eb91d --- /dev/null +++ b/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/ai/brain/task/MaidFindChairTask.java @@ -0,0 +1,54 @@ +package com.github.tartaricacid.touhoulittlemaid.entity.ai.brain.task; + +import com.github.tartaricacid.touhoulittlemaid.entity.item.EntityChair; +import com.github.tartaricacid.touhoulittlemaid.entity.passive.EntityMaid; +import com.google.common.collect.ImmutableMap; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.ai.behavior.BehaviorUtils; +import net.minecraft.world.entity.ai.memory.MemoryModuleType; +import net.minecraft.world.entity.ai.memory.MemoryStatus; +import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities; + +public class MaidFindChairTask extends MaidCheckRateTask { + private static final int MAX_DELAY_TIME = 12; + private final float speedModifier; + private EntityChair chairEntity = null; + + public MaidFindChairTask(float speedModifier) { + super(ImmutableMap.of(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryStatus.VALUE_PRESENT, + MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT)); + this.speedModifier = speedModifier; + this.setMaxCheckRate(MAX_DELAY_TIME); + } + + @Override + protected boolean checkExtraStartConditions(ServerLevel worldIn, EntityMaid owner) { + return super.checkExtraStartConditions(worldIn, owner) && owner.getVehicle() == null; + } + + @Override + protected void start(ServerLevel worldIn, EntityMaid maid, long gameTimeIn) { + this.chairEntity = null; + this.getEntities(maid) + .find(e -> maid.isWithinRestriction(e.blockPosition())) + .filter(Entity::isAlive) + .filter(e -> e instanceof EntityChair chair && chair.getPassengers().isEmpty()) + .findFirst() + .ifPresent(chairEntity -> { + this.chairEntity = (EntityChair) chairEntity; + BehaviorUtils.setWalkAndLookTargetMemories(maid, this.chairEntity, this.speedModifier, 0); + }); + + if (chairEntity != null && chairEntity.isAlive() && chairEntity.closerThan(maid, 2)) { + if (chairEntity.getPassengers().isEmpty()) { + maid.startRiding(this.chairEntity); + } + this.chairEntity = null; + } + } + + private NearestVisibleLivingEntities getEntities(EntityMaid maid) { + return maid.getBrain().getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES).orElse(NearestVisibleLivingEntities.empty()); + } +} diff --git a/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/passive/EntityMaid.java b/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/passive/EntityMaid.java index 3994be5cd..3e71e2e99 100644 --- a/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/passive/EntityMaid.java +++ b/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/passive/EntityMaid.java @@ -28,6 +28,7 @@ import com.github.tartaricacid.touhoulittlemaid.entity.info.ServerCustomPackLoader; import com.github.tartaricacid.touhoulittlemaid.entity.item.EntityPowerPoint; import com.github.tartaricacid.touhoulittlemaid.entity.item.EntityTombstone; +import com.github.tartaricacid.touhoulittlemaid.entity.projectile.MaidFishingHook; import com.github.tartaricacid.touhoulittlemaid.entity.task.TaskIdle; import com.github.tartaricacid.touhoulittlemaid.entity.task.TaskManager; import com.github.tartaricacid.touhoulittlemaid.init.InitEntities; @@ -198,6 +199,7 @@ public class EntityMaid extends TamableAnimal implements CrossbowAttackMob, IMai public final ItemStack[] handItemsForAnimation = new ItemStack[]{ItemStack.EMPTY, ItemStack.EMPTY}; public boolean guiOpening = false; + public MaidFishingHook fishing = null; private List effects = Lists.newArrayList(); private IMaidTask task = TaskManager.getIdleTask(); @@ -219,7 +221,7 @@ public EntityMaid(Level worldIn) { } public static AttributeSupplier.Builder createAttributes() { - return LivingEntity.createLivingAttributes().add(Attributes.FOLLOW_RANGE, 64).add(Attributes.ATTACK_KNOCKBACK).add(Attributes.ATTACK_DAMAGE); + return LivingEntity.createLivingAttributes().add(Attributes.FOLLOW_RANGE, 64).add(Attributes.ATTACK_KNOCKBACK).add(Attributes.ATTACK_DAMAGE).add(Attributes.LUCK); } public static boolean canInsertItem(ItemStack stack) { @@ -314,8 +316,8 @@ public void rideTick() { super.rideTick(); if (this.getVehicle() != null) { Entity vehicle = this.getVehicle(); - this.setYHeadRot(vehicle.getYRot()); - this.setYBodyRot(vehicle.getYRot()); + //this.setYHeadRot(vehicle.getYRot()); + //this.setYBodyRot(vehicle.getYRot()); } } @@ -1668,6 +1670,10 @@ public void setTaskData(CompoundTag compoundTag) { this.entityData.set(TASK_DATA_INFO, compoundTag, true); } + public float getLuck() { + return (float) this.getAttributeValue(Attributes.LUCK); + } + public List getEffects() { return effects; } diff --git a/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/projectile/MaidFishingHook.java b/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/projectile/MaidFishingHook.java new file mode 100644 index 000000000..7f010b164 --- /dev/null +++ b/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/projectile/MaidFishingHook.java @@ -0,0 +1,465 @@ +package com.github.tartaricacid.touhoulittlemaid.entity.projectile; + +import com.github.tartaricacid.touhoulittlemaid.TouhouLittleMaid; +import com.github.tartaricacid.touhoulittlemaid.entity.passive.EntityMaid; +import com.github.tartaricacid.touhoulittlemaid.entity.task.TaskFishing; +import net.minecraft.core.BlockPos; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.EntityDataSerializers; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.tags.FluidTags; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.*; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.projectile.Projectile; +import net.minecraft.world.entity.projectile.ProjectileUtil; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.storage.loot.BuiltInLootTables; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.common.ToolActions; +import net.minecraftforge.event.ForgeEventFactory; + +import javax.annotation.Nullable; +import java.util.List; + +public class MaidFishingHook extends Projectile { + public static final EntityType TYPE = EntityType.Builder.of(MaidFishingHook::new, MobCategory.MISC) + .noSave().noSummon().sized(0.25F, 0.25F) + .clientTrackingRange(4).updateInterval(5) + .build("fishing_hook"); + + private static final EntityDataAccessor DATA_BITING = SynchedEntityData.defineId(MaidFishingHook.class, EntityDataSerializers.BOOLEAN); + private final RandomSource syncronizedRandom = RandomSource.create(); + private boolean biting; + private final int luck; + private final int lureSpeed; + private float fishAngle; + private boolean openWater = true; + private int nibble; + private int timeUntilLured; + private int timeUntilHooked; + private int outOfWaterTime; + private static final int MAX_OUT_OF_WATER_TIME = 10; + private int life; + private MaidFishingHook.FishHookState currentState = MaidFishingHook.FishHookState.FLYING; + + private MaidFishingHook(EntityType entityType, Level level, int luck, int lureSpeed) { + super(entityType, level); + this.noCulling = true; + this.luck = Math.max(0, luck); + this.lureSpeed = Math.max(0, lureSpeed); + } + + public MaidFishingHook(EntityType entityType, Level level) { + this(entityType, level, 0, 0); + } + + public MaidFishingHook(EntityMaid maid, Level level, int luck, int lureSpeed) { + this(TYPE, level, luck, lureSpeed); + this.setOwner(maid); + } + + @Override + protected void defineSynchedData() { + this.getEntityData().define(DATA_BITING, false); + } + + @Override + public void onSyncedDataUpdated(EntityDataAccessor key) { + if (DATA_BITING.equals(key)) { + this.biting = this.getEntityData().get(DATA_BITING); + if (this.biting) { + this.setDeltaMovement(this.getDeltaMovement().x, -0.4 * Mth.nextFloat(this.syncronizedRandom, 0.6F, 1.0F), this.getDeltaMovement().z); + } + } + super.onSyncedDataUpdated(key); + } + + @Override + public boolean shouldRenderAtSqrDistance(double distance) { + return distance < 64 * 64; + } + + @Override + public void lerpTo(double pX, double pY, double pZ, float pYaw, float pPitch, int pPosRotationIncrements, boolean pTeleport) { + } + + @Override + public void tick() { + // 每个 tick 给予不同的 seed,保证随机不一致 + this.syncronizedRandom.setSeed(this.getUUID().getLeastSignificantBits() ^ this.level.getGameTime()); + // 父类调用 + super.tick(); + // 获取当前钓钩的女仆 + EntityMaid maid = this.getPlayerOwner(); + // 女仆为空,那么吊钩也不应当存在 + if (maid == null) { + this.discard(); + } + // 额外检查一下女仆是否满足条件 + else if (this.level.isClientSide || !this.shouldStopFishing(maid)) { + maid.getLookControl().setLookAt(this); + // 如果钓钩在地面,最多存在 1200 tick 就消失 + if (this.onGround()) { + ++this.life; + if (this.life >= 1200) { + this.discard(); + return; + } + } else { + this.life = 0; + } + + // 获取水面高度 + float fluidHeight = 0; + BlockPos blockPos = this.blockPosition(); + FluidState fluidState = this.level.getFluidState(blockPos); + if (fluidState.is(FluidTags.WATER)) { + fluidHeight = fluidState.getHeight(this.level(), blockPos); + } + + boolean onWaterSurface = fluidHeight > 0; + if (this.currentState == MaidFishingHook.FishHookState.FLYING) { + // 如果在水面,那么上下飘动即可 + if (onWaterSurface) { + this.setDeltaMovement(this.getDeltaMovement().multiply(0.3D, 0.2D, 0.3D)); + this.currentState = MaidFishingHook.FishHookState.BOBBING; + return; + } + // 否则,检查是否撞到方块 + this.checkCollision(); + } else { + // 如果已经处于上下飘动状态 + if (this.currentState == MaidFishingHook.FishHookState.BOBBING) { + // 继续上下飘动 + Vec3 movement = this.getDeltaMovement(); + double bobbingY = this.getY() + movement.y - (double) blockPos.getY() - (double) fluidHeight; + if (Math.abs(bobbingY) < 0.01D) { + bobbingY += Math.signum(bobbingY) * 0.1; + } + this.setDeltaMovement(movement.x * 0.9, movement.y - bobbingY * (double) this.random.nextFloat() * 0.2, movement.z * 0.9); + + // 检查是否为开放水域,这会加快钓鱼速度 + if (this.nibble <= 0 && this.timeUntilHooked <= 0) { + this.openWater = true; + } else { + this.openWater = this.openWater && this.outOfWaterTime < MAX_OUT_OF_WATER_TIME && this.calculateOpenWater(blockPos); + } + + // 计算咬钩时的运动和其他逻辑 + if (onWaterSurface) { + this.outOfWaterTime = Math.max(0, this.outOfWaterTime - 1); + if (this.biting) { + this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.1D * (double) this.syncronizedRandom.nextFloat() * (double) this.syncronizedRandom.nextFloat(), 0.0D)); + } + // 咬钩! + if (!this.level.isClientSide) { + this.catchingFish(blockPos, (ServerLevel) this.level); + } + } else { + this.outOfWaterTime = Math.min(MAX_OUT_OF_WATER_TIME, this.outOfWaterTime + 1); + } + } + } + + // 不在水面,那就下坠 + if (!fluidState.is(FluidTags.WATER)) { + this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.03D, 0.0D)); + } + + // 运动相关更新 + this.move(MoverType.SELF, this.getDeltaMovement()); + this.updateRotation(); + if (this.currentState == MaidFishingHook.FishHookState.FLYING && (this.onGround() || this.horizontalCollision)) { + this.setDeltaMovement(Vec3.ZERO); + } + this.setDeltaMovement(this.getDeltaMovement().scale(0.92)); + this.reapplyPosition(); + } + } + + private void catchingFish(BlockPos pos, ServerLevel level) { + int time = 1; + BlockPos abovePos = pos.above(); + // 如果下雨,随机加快钓鱼等待时间 + if (this.random.nextFloat() < 0.25F && level.isRainingAt(abovePos)) { + ++time; + } + // 如果没有露天,减少钓鱼等待时间 + if (this.random.nextFloat() < 0.5F && !level.canSeeSky(abovePos)) { + --time; + } + // 咬钩时间 + if (this.nibble > 0) { + --this.nibble; + // 咬钩时间到了,收杆 + EntityMaid maid = getPlayerOwner(); + if (this.nibble <= 5 && maid != null) { + this.retrieve(maid.getMainHandItem()); + maid.swing(InteractionHand.MAIN_HAND); + level.playSound(null, maid.getX(), maid.getY(), maid.getZ(), SoundEvents.FISHING_BOBBER_RETRIEVE, SoundSource.NEUTRAL, 1.0F, 0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)); + } + } + // 如果等待时间 + else if (this.timeUntilHooked > 0) { + this.timeUntilHooked -= time; + // 如果等待时间没结束,那么随机加一点粒子效果 + if (this.timeUntilHooked > 0) { + // 随机给予运动角度 + this.fishAngle += (float) this.random.triangle(0.0D, 9.188D); + float fishAngleRad = this.fishAngle * ((float) Math.PI / 180F); + float sin = Mth.sin(fishAngleRad); + float cos = Mth.cos(fishAngleRad); + double x = this.getX() + sin * this.timeUntilHooked * 0.1; + double y = Mth.floor(this.getY()) + 1.0; + double z = this.getZ() + cos * this.timeUntilHooked * 0.1; + // 随机给予钓鱼时出现的水花粒子 + BlockState blockState = level.getBlockState(BlockPos.containing(x, y - 1.0, z)); + if (blockState.is(Blocks.WATER)) { + if (this.random.nextFloat() < 0.15F) { + level.sendParticles(ParticleTypes.BUBBLE, x, y - 0.1, z, 1, sin, 0.1D, cos, 0.0D); + } + float sinOffset = sin * 0.04F; + float cosOffset = cos * 0.04F; + level.sendParticles(ParticleTypes.FISHING, x, y, z, 0, cosOffset, 0.01D, -sinOffset, 1.0D); + level.sendParticles(ParticleTypes.FISHING, x, y, z, 0, -cosOffset, 0.01D, sinOffset, 1.0D); + } + } else { + // 给予咬钩时的粒子效果和音效 + this.playSound(SoundEvents.FISHING_BOBBER_SPLASH, 0.25F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F); + double yOffset = this.getY() + 0.5D; + float bbWidth = this.getBbWidth(); + level.sendParticles(ParticleTypes.BUBBLE, this.getX(), yOffset, this.getZ(), (int) (1.0F + bbWidth * 20.0F), bbWidth, 0.0D, bbWidth, 0.2F); + level.sendParticles(ParticleTypes.FISHING, this.getX(), yOffset, this.getZ(), (int) (1.0F + bbWidth * 20.0F), bbWidth, 0.0D, bbWidth, 0.2F); + // 添加随机的咬钩时间 + this.nibble = Mth.nextInt(this.random, 20, 40); + this.getEntityData().set(DATA_BITING, true); + } + } + // 计算下一次饵钓时间 + else if (this.timeUntilLured > 0) { + this.timeUntilLured -= time; + float probability = 0.15F; + if (this.timeUntilLured < 20) { + probability += (float) (20 - this.timeUntilLured) * 0.05F; + } else if (this.timeUntilLured < 40) { + probability += (float) (40 - this.timeUntilLured) * 0.02F; + } else if (this.timeUntilLured < 60) { + probability += (float) (60 - this.timeUntilLured) * 0.01F; + } + // 随机给予粒子效果 + if (this.random.nextFloat() < probability) { + float randomRot = Mth.nextFloat(this.random, 0.0F, 360.0F) * ((float) Math.PI / 180F); + float randomNum = Mth.nextFloat(this.random, 25.0F, 60.0F); + double x = this.getX() + Mth.sin(randomRot) * randomNum * 0.1; + double y = Mth.floor(this.getY()) + 1.0; + double z = this.getZ() + Mth.cos(randomRot) * randomNum * 0.1; + BlockState blockState = level.getBlockState(BlockPos.containing(x, y - 1.0, z)); + if (blockState.is(Blocks.WATER)) { + level.sendParticles(ParticleTypes.SPLASH, x, y, z, 2 + this.random.nextInt(2), 0.1F, 0.0D, 0.1F, 0.0D); + } + } + // 饵钓时间到,开始随机赋予等待时间 + if (this.timeUntilLured <= 0) { + this.fishAngle = Mth.nextFloat(this.random, 0.0F, 360.0F); + this.timeUntilHooked = Mth.nextInt(this.random, 20, 80); + } + } else { + this.timeUntilLured = Mth.nextInt(this.random, 100, 600); + this.timeUntilLured -= this.lureSpeed * 20 * 5; + } + } + + public int retrieve(ItemStack stack) { + EntityMaid maid = this.getPlayerOwner(); + if (!this.level.isClientSide && maid != null && !this.shouldStopFishing(maid)) { + int rodDamage = 0; + // TODO: 添加女仆钓鱼事件 + MinecraftServer server = this.level.getServer(); + // 如果是咬钩时间 + if (this.nibble > 0 && server != null) { + ServerLevel serverLevel = (ServerLevel) this.level; + LootParams lootParams = new LootParams.Builder(serverLevel) + .withParameter(LootContextParams.ORIGIN, this.position()) + .withParameter(LootContextParams.TOOL, stack) + .withParameter(LootContextParams.THIS_ENTITY, this) + .withParameter(LootContextParams.KILLER_ENTITY, maid) + .withLuck(this.luck + maid.getLuck()) + .create(LootContextParamSets.FISHING); + LootTable lootTable = server.getLootData().getLootTable(BuiltInLootTables.FISHING); + List randomItems = lootTable.getRandomItems(lootParams); + for (ItemStack result : randomItems) { + ItemEntity itemEntity = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), result); + double xOffset = maid.getX() - this.getX(); + double yOffset = maid.getY() - this.getY(); + double zOffset = maid.getZ() - this.getZ(); + double sqrt = Math.sqrt(xOffset * xOffset + yOffset * yOffset + zOffset * zOffset); + itemEntity.setDeltaMovement(xOffset * 0.1D, yOffset * 0.1D + Math.sqrt(sqrt) * 0.08D, zOffset * 0.1D); + this.level.addFreshEntity(itemEntity); + maid.level.addFreshEntity(new ExperienceOrb(maid.level(), maid.getX(), maid.getY() + 0.5D, maid.getZ() + 0.5D, this.random.nextInt(6) + 1)); + } + rodDamage = 1; + } + if (this.onGround()) { + rodDamage = 2; + } + this.discard(); + return rodDamage; + } else { + return 0; + } + } + + @Override + protected Entity.MovementEmission getMovementEmission() { + return Entity.MovementEmission.NONE; + } + + @Override + public void remove(Entity.RemovalReason reason) { + this.updateOwnerInfo(null); + super.remove(reason); + } + + @Override + public void onClientRemoval() { + this.updateOwnerInfo(null); + } + + @Override + public void setOwner(@Nullable Entity owner) { + super.setOwner(owner); + this.updateOwnerInfo(this); + } + + private void updateOwnerInfo(@Nullable MaidFishingHook pFishingHook) { + EntityMaid maid = this.getPlayerOwner(); + if (maid != null) { + maid.fishing = pFishingHook; + } + } + + @Nullable + public EntityMaid getPlayerOwner() { + Entity entity = this.getOwner(); + return entity instanceof EntityMaid ? (EntityMaid) entity : null; + } + + private boolean shouldStopFishing(EntityMaid maid) { + ItemStack mainHandItem = maid.getMainHandItem(); + boolean hasFishingRod = mainHandItem.canPerformAction(ToolActions.FISHING_ROD_CAST); + boolean isFishingTask = maid.getTask() instanceof TaskFishing; + if (!maid.isRemoved() && maid.isAlive() && isFishingTask && hasFishingRod && this.distanceToSqr(maid) < 512) { + return false; + } else { + this.discard(); + return true; + } + } + + private void checkCollision() { + HitResult hitResult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); + if (hitResult.getType() == HitResult.Type.MISS || !ForgeEventFactory.onProjectileImpact(this, hitResult)) { + this.onHit(hitResult); + } + } + + private boolean calculateOpenWater(BlockPos blockPos) { + MaidFishingHook.OpenWaterType openWaterType = MaidFishingHook.OpenWaterType.INVALID; + for (int y = -1; y <= 2; ++y) { + MaidFishingHook.OpenWaterType openWaterTypeForArea = this.getOpenWaterTypeForArea(blockPos.offset(-2, y, -2), blockPos.offset(2, y, 2)); + switch (openWaterTypeForArea) { + case INVALID: + return false; + case ABOVE_WATER: + if (openWaterType == MaidFishingHook.OpenWaterType.INVALID) { + return false; + } + break; + case INSIDE_WATER: + if (openWaterType == MaidFishingHook.OpenWaterType.ABOVE_WATER) { + return false; + } + } + openWaterType = openWaterTypeForArea; + } + return true; + } + + private MaidFishingHook.OpenWaterType getOpenWaterTypeForArea(BlockPos firstPos, BlockPos secondPos) { + return BlockPos.betweenClosedStream(firstPos, secondPos) + .map(this::getOpenWaterTypeForBlock) + .reduce((waterType1, waterType2) -> waterType1 == waterType2 ? waterType1 : OpenWaterType.INVALID) + .orElse(MaidFishingHook.OpenWaterType.INVALID); + } + + private MaidFishingHook.OpenWaterType getOpenWaterTypeForBlock(BlockPos blockPos) { + BlockState state = this.level.getBlockState(blockPos); + if (!state.isAir() && !state.is(Blocks.LILY_PAD)) { + FluidState fluidState = state.getFluidState(); + return fluidState.is(FluidTags.WATER) && fluidState.isSource() && state.getCollisionShape(this.level(), blockPos).isEmpty() ? MaidFishingHook.OpenWaterType.INSIDE_WATER : MaidFishingHook.OpenWaterType.INVALID; + } else { + return MaidFishingHook.OpenWaterType.ABOVE_WATER; + } + } + + @Override + protected void addAdditionalSaveData(CompoundTag compound) { + } + + @Override + protected void readAdditionalSaveData(CompoundTag compound) { + } + + @Override + public boolean canChangeDimensions() { + return false; + } + + @Override + public Packet getAddEntityPacket() { + Entity entity = this.getOwner(); + return new ClientboundAddEntityPacket(this, entity == null ? this.getId() : entity.getId()); + } + + @Override + public void recreateFromPacket(ClientboundAddEntityPacket packet) { + super.recreateFromPacket(packet); + if (this.getPlayerOwner() == null) { + int dataId = packet.getData(); + TouhouLittleMaid.LOGGER.error("Failed to recreate fishing hook on client. {} (id: {}) is not a valid owner.", this.level.getEntity(dataId), dataId); + this.kill(); + } + } + + enum FishHookState { + FLYING, + BOBBING; + } + + enum OpenWaterType { + ABOVE_WATER, + INSIDE_WATER, + INVALID; + } +} diff --git a/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/task/TaskFishing.java b/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/task/TaskFishing.java new file mode 100644 index 000000000..1b3894168 --- /dev/null +++ b/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/task/TaskFishing.java @@ -0,0 +1,48 @@ +package com.github.tartaricacid.touhoulittlemaid.entity.task; + +import com.github.tartaricacid.touhoulittlemaid.TouhouLittleMaid; +import com.github.tartaricacid.touhoulittlemaid.api.task.IMaidTask; +import com.github.tartaricacid.touhoulittlemaid.entity.ai.brain.ride.MaidRideFindWaterTask; +import com.github.tartaricacid.touhoulittlemaid.entity.ai.brain.task.MaidFindChairTask; +import com.github.tartaricacid.touhoulittlemaid.entity.passive.EntityMaid; +import com.github.tartaricacid.touhoulittlemaid.init.InitSounds; +import com.google.common.collect.Lists; +import com.mojang.datafixers.util.Pair; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.world.entity.ai.behavior.BehaviorControl; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class TaskFishing implements IMaidTask { + public static final ResourceLocation UID = new ResourceLocation(TouhouLittleMaid.MOD_ID, "fishing"); + + @Override + public ResourceLocation getUid() { + return UID; + } + + @Override + public ItemStack getIcon() { + return Items.FISHING_ROD.getDefaultInstance(); + } + + @Nullable + @Override + public SoundEvent getAmbientSound(EntityMaid maid) { + return InitSounds.MAID_IDLE.get(); + } + + @Override + public List>> createBrainTasks(EntityMaid maid) { + return Lists.newArrayList(Pair.of(5, new MaidFindChairTask(0.6f))); + } + + @Override + public List>> createRideBrainTasks(EntityMaid maid) { + return Lists.newArrayList(Pair.of(5, new MaidRideFindWaterTask(6, 3))); + } +} diff --git a/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/task/TaskManager.java b/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/task/TaskManager.java index 7d758ade2..cfde21a7f 100644 --- a/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/task/TaskManager.java +++ b/src/main/java/com/github/tartaricacid/touhoulittlemaid/entity/task/TaskManager.java @@ -48,6 +48,7 @@ public static void init() { manager.add(new TaskMilk()); manager.add(new TaskTorch()); manager.add(new TaskFeedAnimal()); + manager.add(new TaskFishing()); manager.add(new TaskExtinguishing()); manager.add(new TaskBoardGames()); for (ILittleMaid littleMaid : TouhouLittleMaid.EXTENSIONS) { diff --git a/src/main/java/com/github/tartaricacid/touhoulittlemaid/init/InitEntities.java b/src/main/java/com/github/tartaricacid/touhoulittlemaid/init/InitEntities.java index 7fa67876c..3a953241e 100644 --- a/src/main/java/com/github/tartaricacid/touhoulittlemaid/init/InitEntities.java +++ b/src/main/java/com/github/tartaricacid/touhoulittlemaid/init/InitEntities.java @@ -11,6 +11,7 @@ import com.github.tartaricacid.touhoulittlemaid.entity.passive.EntityMaid; import com.github.tartaricacid.touhoulittlemaid.entity.projectile.EntityDanmaku; import com.github.tartaricacid.touhoulittlemaid.entity.projectile.EntityThrowPowerPoint; +import com.github.tartaricacid.touhoulittlemaid.entity.projectile.MaidFishingHook; import net.minecraft.network.syncher.EntityDataSerializer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; @@ -56,6 +57,7 @@ public final class InitEntities { public static RegistryObject> TOMBSTONE = ENTITY_TYPES.register("tombstone", () -> EntityTombstone.TYPE); public static RegistryObject> SIT = ENTITY_TYPES.register("sit", () -> EntitySit.TYPE); public static RegistryObject> BROOM = ENTITY_TYPES.register("broom", () -> EntityBroom.TYPE); + public static RegistryObject> FISHING_HOOK = ENTITY_TYPES.register("fishing_hook", () -> MaidFishingHook.TYPE); public static RegistryObject RIDE_IDLE = ACTIVITIES.register("ride_idle", () -> new Activity("tlm_ride_idle")); public static RegistryObject RIDE_WORK = ACTIVITIES.register("ride_work", () -> new Activity("tlm_ride_work"));