package com.mysticsbiomes.common.entity; import com.mysticsbiomes.init.MysticEntities; import io.netty.buffer.ByteBuf; import net.minecraft.client.model.geom.ModelPart; import net.minecraft.client.model.geom.PartPose; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializer; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.FluidTags; import net.minecraft.tags.ItemTags; import net.minecraft.util.ByIdMap; import net.minecraft.util.Mth; import net.minecraft.util.StringRepresentable; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.*; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.control.BodyRotationControl; import net.minecraft.world.entity.ai.control.LookControl; import net.minecraft.world.entity.ai.control.MoveControl; import net.minecraft.world.entity.ai.goal.*; import net.minecraft.world.entity.ai.navigation.AmphibiousPathNavigation; import net.minecraft.world.entity.ai.navigation.PathNavigation; import net.minecraft.world.entity.animal.Animal; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.pathfinder.*; import net.minecraft.world.phys.Vec3; import javax.annotation.Nullable; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.function.IntFunction; import java.util.function.Predicate; /** * can be tamed as pets to defend you in the water. * they can help you find treasures, or collect fish. * they can help you swim faster like dolphins. * they mostly float around, unless they are feeling playful or want to get food. * they will fall asleep at night, holding another sea otters hand nearby. * their babies will rest on their belly until they age up. * * todo: * swim around * drift around * hold hands * breed * search for food * * sleeping * hold hands while sleeping * babies sleeping on parent * babies behavior * search for fish * search for seashells * break open shells * bring items to players * eat */ public class SeaOtter extends Animal { public static final EntityDataSerializer SEA_OTTER_STATE = EntityDataSerializer.forValueType(State.STREAM_CODEC); private static final EntityDataAccessor DATA_STATE_ID = SynchedEntityData.defineId(SeaOtter.class, SEA_OTTER_STATE); private static final EntityDataAccessor DATA_FLAGS_ID = SynchedEntityData.defineId(SeaOtter.class, EntityDataSerializers.BYTE); private int ticksSinceLastSwam; private int ticksSpentSwimming; @Nullable private BlockPos surfacePos; public final Map previousPose = new HashMap<>(); public final Map targetPose = new HashMap<>(); public final AnimationState swimmingIdleAnimationState = new AnimationState(); public final AnimationState swimmingAnimationState = new AnimationState(); public final AnimationState twirlWhileSwimmingAnimationState = new AnimationState(); public final AnimationState floatingAnimationState = new AnimationState(); public final AnimationState startFloatingAnimationState = new AnimationState(); public final AnimationState stopFloatingAnimationState = new AnimationState(); public final AnimationState sleepingWhileFloatingAnimationState = new AnimationState(); public final AnimationState eatingWhileFloatingAnimationState = new AnimationState(); public final AnimationState sitDownAnimationState = new AnimationState(); public final AnimationState sittingIdleAnimationState = new AnimationState(); public final AnimationState standUpAnimationState = new AnimationState(); public final AnimationState standingIdleAnimationState = new AnimationState(); public final AnimationState walkingAnimationState = new AnimationState(); private long inStateTicks = 0L; public SeaOtter(EntityType type, Level level) { super(type, level); this.setPathfindingMalus(PathType.WATER, 0.0F); this.moveControl = new SeaOtter.SeaOtterMoveControl(this); this.lookControl = new SeaOtter.SeaOtterLookControl(this); } public static AttributeSupplier.Builder createAttributes() { return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 16.0F).add(Attributes.MOVEMENT_SPEED, 0.5D).add(Attributes.STEP_HEIGHT, 1.0); } @Override protected void registerGoals() { this.goalSelector.addGoal(0, new SeaOtter.SwimToSurfaceGoal(this)); this.goalSelector.addGoal(1, new SeaOtter.SeaOtterLookAtPlayerGoal(this, 6.0F)); this.goalSelector.addGoal(2, new SeaOtter.FloatingGoal(this)); this.goalSelector.addGoal(3, new SeaOtter.SeaOtterTemptGoal(this, 0.2D, (stack) -> stack.is(ItemTags.FISHES))); this.goalSelector.addGoal(4, new SeaOtter.SwimAroundGoal(this, 1.0D, 3)); this.goalSelector.addGoal(5, new SeaOtter.SeaOtterRandomLookAroundGoal(this)); } @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); builder.define(DATA_STATE_ID, State.NOTHING); builder.define(DATA_FLAGS_ID, (byte)0); } @Override public void addAdditionalSaveData(CompoundTag tag) { super.addAdditionalSaveData(tag); tag.putString("State", this.getState().getSerializedName()); tag.putBoolean("Floating", this.isFloating()); tag.putBoolean("Swimming", this.isSwimming()); tag.putBoolean("Sitting", this.isSitting()); tag.putInt("TicksSinceLastSwam", this.ticksSinceLastSwam); tag.putInt("TicksSpentSwimming", this.ticksSpentSwimming); if (this.surfacePos != null) { tag.put("SurfacePos", NbtUtils.writeBlockPos(this.surfacePos)); } } @Override public void readAdditionalSaveData(CompoundTag tag) { super.readAdditionalSaveData(tag); this.switchToState(State.fromName(tag.getString("State"))); this.setFloating(tag.getBoolean("Floating")); this.setSwimming(tag.getBoolean("Swimming")); this.setSitting(tag.getBoolean("Sitting")); this.ticksSinceLastSwam = tag.getInt("TicksSinceLastSwam"); this.ticksSpentSwimming = tag.getInt("TicksSpentSwimming"); this.surfacePos = null; if (tag.contains("SurfacePos") && NbtUtils.readBlockPos(tag, "SurfacePos").isPresent()) { this.surfacePos = NbtUtils.readBlockPos(tag, "SurfacePos").get(); } } @Override public AgeableMob getBreedOffspring(ServerLevel level, AgeableMob mob) { return MysticEntities.SEA_OTTER.get().create(level); } @Override public int getMaxAirSupply() { return 6000; /// 5 minutes } @Override public void tick() { super.tick(); if (!this.level().isClientSide()) { if (this.isFloating()) { this.ticksSinceLastSwam++; this.wasTouchingWater = true; this.setDeltaMovement(this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D)); this.setSpeed(0.0F); this.setYya(0.0F); if (this.getAirSupply() < this.getMaxAirSupply()) { this.setAirSupply(this.increaseAirSupply(this.getAirSupply())); } } if (this.isUnderWater()) { if (!this.needsToSurface() && (this.getAirSupply() < 600 || !this.wantsToSwimAround() || this.hasBeenSwimmingLongEnough())) { this.setNeedsToSurface(true); } } } if (this.level().isClientSide()) { this.setupSwimmingAnimationStates(); this.setupFloatingAnimationStates(); this.setupSittingAnimationStates(); } this.inStateTicks++; } private void setupSwimmingAnimationStates() { switch (this.getState()) { case SWIMMING_IDLE: this.swimmingIdleAnimationState.startIfStopped(this.tickCount); this.swimmingAnimationState.stop(); this.twirlWhileSwimmingAnimationState.stop(); this.floatingAnimationState.stop(); this.startFloatingAnimationState.stop(); this.stopFloatingAnimationState.stop(); this.sleepingWhileFloatingAnimationState.stop(); this.eatingWhileFloatingAnimationState.stop(); this.sitDownAnimationState.stop(); this.sittingIdleAnimationState.stop(); this.standUpAnimationState.stop(); this.standingIdleAnimationState.stop(); this.walkingAnimationState.stop(); break; case SWIMMING: this.swimmingAnimationState.startIfStopped(this.tickCount); this.swimmingIdleAnimationState.stop(); this.twirlWhileSwimmingAnimationState.stop(); this.floatingAnimationState.stop(); this.startFloatingAnimationState.stop(); this.stopFloatingAnimationState.stop(); this.sleepingWhileFloatingAnimationState.stop(); this.eatingWhileFloatingAnimationState.stop(); this.sitDownAnimationState.stop(); this.sittingIdleAnimationState.stop(); this.standUpAnimationState.stop(); this.standingIdleAnimationState.stop(); this.walkingAnimationState.stop(); break; case TWIRL_WHILE_SWIMMING: this.twirlWhileSwimmingAnimationState.startIfStopped(this.tickCount); break; } } private void setupFloatingAnimationStates() { switch (this.getState()) { case START_FLOATING: this.startFloatingAnimationState.startIfStopped(this.tickCount); this.swimmingIdleAnimationState.stop(); this.swimmingAnimationState.stop(); this.twirlWhileSwimmingAnimationState.stop(); this.floatingAnimationState.stop(); this.stopFloatingAnimationState.stop(); this.sleepingWhileFloatingAnimationState.stop(); this.eatingWhileFloatingAnimationState.stop(); this.sitDownAnimationState.stop(); this.sittingIdleAnimationState.stop(); this.standUpAnimationState.stop(); this.standingIdleAnimationState.stop(); this.walkingAnimationState.stop(); break; case FLOATING: this.floatingAnimationState.startIfStopped(this.tickCount); this.swimmingIdleAnimationState.stop(); this.swimmingAnimationState.stop(); this.twirlWhileSwimmingAnimationState.stop(); this.startFloatingAnimationState.stop(); this.stopFloatingAnimationState.stop(); this.sleepingWhileFloatingAnimationState.stop(); this.eatingWhileFloatingAnimationState.stop(); this.sitDownAnimationState.stop(); this.sittingIdleAnimationState.stop(); this.standUpAnimationState.stop(); this.standingIdleAnimationState.stop(); this.walkingAnimationState.stop(); break; case STOP_FLOATING: this.stopFloatingAnimationState.startIfStopped(this.tickCount); this.swimmingIdleAnimationState.stop(); this.swimmingAnimationState.stop(); this.twirlWhileSwimmingAnimationState.stop(); this.floatingAnimationState.stop(); this.startFloatingAnimationState.stop(); this.sleepingWhileFloatingAnimationState.stop(); this.eatingWhileFloatingAnimationState.stop(); this.sitDownAnimationState.stop(); this.sittingIdleAnimationState.stop(); this.standUpAnimationState.stop(); this.standingIdleAnimationState.stop(); this.walkingAnimationState.stop(); break; case SLEEPING_WHILE_FLOATING: this.sleepingWhileFloatingAnimationState.startIfStopped(this.tickCount); break; case EATING_WHILE_FLOATING: this.eatingWhileFloatingAnimationState.startIfStopped(this.tickCount); break; } } private void setupSittingAnimationStates() { switch (this.getState()) { case SIT_DOWN: this.sitDownAnimationState.startIfStopped(this.tickCount); this.swimmingIdleAnimationState.stop(); this.swimmingAnimationState.stop(); this.twirlWhileSwimmingAnimationState.stop(); this.floatingAnimationState.stop(); this.startFloatingAnimationState.stop(); this.stopFloatingAnimationState.stop(); this.sleepingWhileFloatingAnimationState.stop(); this.eatingWhileFloatingAnimationState.stop(); this.sittingIdleAnimationState.stop(); this.standUpAnimationState.stop(); this.standingIdleAnimationState.stop(); this.walkingAnimationState.stop(); break; case SITTING_IDLE: this.sittingIdleAnimationState.startIfStopped(this.tickCount); this.swimmingIdleAnimationState.stop(); this.swimmingAnimationState.stop(); this.twirlWhileSwimmingAnimationState.stop(); this.floatingAnimationState.stop(); this.startFloatingAnimationState.stop(); this.stopFloatingAnimationState.stop(); this.sleepingWhileFloatingAnimationState.stop(); this.eatingWhileFloatingAnimationState.stop(); this.sitDownAnimationState.stop(); this.standUpAnimationState.stop(); this.standingIdleAnimationState.stop(); this.walkingAnimationState.stop(); break; case STAND_UP: this.standUpAnimationState.startIfStopped(this.tickCount); this.swimmingIdleAnimationState.stop(); this.swimmingAnimationState.stop(); this.twirlWhileSwimmingAnimationState.stop(); this.floatingAnimationState.stop(); this.startFloatingAnimationState.stop(); this.stopFloatingAnimationState.stop(); this.sleepingWhileFloatingAnimationState.stop(); this.eatingWhileFloatingAnimationState.stop(); this.sitDownAnimationState.stop(); this.sittingIdleAnimationState.stop(); this.standingIdleAnimationState.stop(); this.walkingAnimationState.stop(); break; case WALKING: this.walkingAnimationState.startIfStopped(this.tickCount); this.swimmingIdleAnimationState.stop(); this.swimmingAnimationState.stop(); this.twirlWhileSwimmingAnimationState.stop(); this.floatingAnimationState.stop(); this.startFloatingAnimationState.stop(); this.stopFloatingAnimationState.stop(); this.sleepingWhileFloatingAnimationState.stop(); this.eatingWhileFloatingAnimationState.stop(); this.sitDownAnimationState.stop(); this.sittingIdleAnimationState.stop(); this.standUpAnimationState.stop(); this.standingIdleAnimationState.stop(); break; } } @Override public void aiStep() { super.aiStep(); if (!this.level().isClientSide()) { if (this.onGround() && !this.isInWater() && !this.isFloating()) { if (this.wantsToMove()) { if (this.isSitting() || this.getState() == State.SIT_DOWN) { if (this.getState() != State.STAND_UP) { this.switchToState(State.STAND_UP); this.setSitting(false); } } if (this.getState() == State.STAND_UP && this.inStateTicks >= State.STAND_UP.animationDuration()) { this.switchToState(State.WALKING); } if (!this.isSitting() && this.getState() != State.STAND_UP && this.getState() != State.WALKING) { this.switchToState(State.WALKING); } } else { if (!this.isSitting() && this.getState() != State.SIT_DOWN) { this.switchToState(State.SIT_DOWN); this.setSitting(true); } else if (this.getState() == State.SIT_DOWN && this.inStateTicks >= State.SIT_DOWN.animationDuration()) { this.switchToState(State.SITTING_IDLE); this.setSitting(true); } if (this.getState() == State.STAND_UP && this.inStateTicks >= State.STAND_UP.animationDuration()) { this.switchToState(State.SIT_DOWN); this.setSitting(true); } } } } } @Override public void onSyncedDataUpdated(EntityDataAccessor accessor) { if (DATA_STATE_ID.equals(accessor)) { this.inStateTicks = 0L; } super.onSyncedDataUpdated(accessor); } public State getState() { return this.entityData.get(DATA_STATE_ID); } public void switchToState(State state) { if (this.getState() != state) { this.entityData.set(DATA_STATE_ID, state); this.inStateTicks = 0L; } } public boolean isActivelyInFloatTransition() { return switch (this.getState()) { case START_FLOATING, STOP_FLOATING, FLOATING -> this.inStateTicks < this.getState().animationDuration(); default -> false; }; } public boolean isActivelySittingTransition() { return switch (this.getState()) { case SIT_DOWN, STAND_UP -> this.inStateTicks < this.getState().animationDuration(); default -> false; }; } private boolean getFlag(int id) { return (this.entityData.get(DATA_FLAGS_ID) & id) != 0; } private void setFlag(int id, boolean value) { byte flag = this.entityData.get(DATA_FLAGS_ID); this.entityData.set(DATA_FLAGS_ID, value ? (byte)(flag | id) : (byte)(flag & ~id)); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public boolean isFloating() { return this.getFlag(1 << 1); } public void setFloating(boolean value) { this.setFlag(1 << 1, value); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public boolean isSwimming() { return this.getFlag(1 << 2); } public void setSwimming(boolean value) { this.setFlag(1 << 2, value); } /// once it's been long enough since it last swam around the sea otter will stop floating and start swimming. public boolean wantsToSwimAround() { return this.hasBeenLongEnoughSinceLastSwam(); } public boolean hasBeenLongEnoughSinceLastSwam() { return this.ticksSinceLastSwam >= 400; } public void resetTicksSinceLastSwam() { this.ticksSinceLastSwam = 0; } public boolean hasBeenSwimmingLongEnough() { return this.ticksSpentSwimming >= 200; } public void resetTicksSpentSwimming() { this.ticksSpentSwimming = 0; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public boolean isSitting() { return this.getFlag(1 << 3); } public void setSitting(boolean value) { this.setFlag(1 << 3, value); } public void sitDown() { if (!this.isSitting() && this.onGround() && !this.isInWater()) { this.setSitting(true); this.switchToState(State.SIT_DOWN); } } public void standUp() { if (this.isSitting()) { this.setSitting(false); this.switchToState(State.STAND_UP); } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @Nullable public BlockPos getSurfacePos() { return this.surfacePos; } public void setSurfacePos(BlockPos pos) { this.surfacePos = pos; } public boolean needsToSurface() { return this.getFlag(1 << 4); } public void setNeedsToSurface(boolean value) { this.setFlag(1 << 4, value); } public boolean hasReachedSurface() { return this.getFlag(1 << 5); } public void setReachedSurface(boolean value) { this.setFlag(1 << 5, value); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @Override protected PathNavigation createNavigation(Level level) { return new SeaOtter.SeaOtterPathNavigation(this, level); } @Override protected BodyRotationControl createBodyControl() { return new SeaOtter.SeaOtterBodyRotationControl(this); } @Override public void travel(Vec3 pos) { if (this.refuseToMove()) { this.setDeltaMovement(this.getDeltaMovement().multiply(0.0, 1.0, 0.0)); pos = pos.multiply(0.0, 1.0, 0.0); } super.travel(pos); } public void pathfindDirectlyTowards(BlockPos pos, double speed) { this.navigation.setMaxVisitedNodesMultiplier(10.0F); this.navigation.moveTo(pos.getX() + 0.5D, pos.getY(), pos.getZ() + 0.5D, speed); } public boolean refuseToMove() { return (this.isSitting() || this.isActivelySittingTransition()) || (this.getState() == State.FLOATING); } public boolean wantsToMove() { return !this.getNavigation().isDone() || this.getTarget() != null || this.moveControl.hasWanted(); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @Override public Vec3 getViewVector(float ticks) { Vec3 view = super.getViewVector(ticks); if (this.isFloating()) { return view.reverse(); } return view; } @Override public int getMaxHeadYRot() { if (this.isFloating()) { return 2; } else if (this.isSitting()) { return 20; } else { return super.getMaxHeadYRot(); } } @Override public int getMaxHeadXRot() { if (this.isFloating()) { return 20; } else if (this.isSitting()) { return 20; } else { return super.getMaxHeadXRot(); } } @Override public boolean isFood(ItemStack stack) { return stack.is(ItemTags.FISHES); } @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { if (!this.level().isClientSide()) { if (player.getItemInHand(hand).isEmpty()) { this.setSitting(false); this.setFloating(true); this.switchToState(State.START_FLOATING); System.out.println("ticksSinceLastSwam: " + this.ticksSinceLastSwam); System.out.println("ticksSpentSwimming: " + this.ticksSpentSwimming); } } return super.mobInteract(player, hand); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public static class SwimToSurfaceGoal extends Goal { private final SeaOtter seaOtter; @Nullable private BlockPos blockPos; public SwimToSurfaceGoal(SeaOtter mob) { this.seaOtter = mob; this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK)); } @Override public boolean canUse() { if (this.seaOtter.isFloating()) { return false; } else if (this.seaOtter.hasBeenLongEnoughSinceLastSwam()) { /// meaning the sea otter should be swimming. return false; } return this.seaOtter.needsToSurface(); } @Override public boolean canContinueToUse() { return !this.seaOtter.hasReachedSurface(); } @Override public void start() { if (this.findNearestPos().isPresent()) { this.blockPos = this.findNearestPos().get(); this.seaOtter.setSurfacePos(this.blockPos); System.out.println("swimming to surface at : " + this.seaOtter.getSurfacePos()); } } @Override public void stop() { this.seaOtter.setNeedsToSurface(false); this.seaOtter.getNavigation().stop(); System.out.println("reached surface"); } @Override public void tick() { this.seaOtter.setReachedSurface(false); if (this.blockPos == null) { if (this.findNearestPos().isPresent()) { this.blockPos = this.findNearestPos().get(); } } else { this.seaOtter.pathfindDirectlyTowards(this.blockPos, 2.5F); this.seaOtter.getLookControl().setLookAt(Vec3.atLowerCornerOf(this.blockPos)); double distanceToSurface = this.blockPos.getY() - this.seaOtter.getEyePosition().y(); if (this.seaOtter.getEyeY() < this.blockPos.getY() && this.blockPos.distToCenterSqr(this.seaOtter.position()) <= 5.0D) { this.seaOtter.push(0.0D, Math.min(0.03D, distanceToSurface * 0.05D), 0.0D); this.seaOtter.setSpeed(2.5F * (float) Math.max(0.3D, Math.min(1.0D, distanceToSurface))); } if (Math.abs(distanceToSurface) <= 0.2D) { this.seaOtter.setDeltaMovement(Vec3.ZERO); this.seaOtter.setSpeed(0.0F); this.seaOtter.setYya(0.0F); /// set that the sea otter has reached the surface, ending the goal and starting the float goal. this.seaOtter.setReachedSurface(true); } } } protected Optional findNearestPos() { BlockPos pos = this.seaOtter.blockPosition(); BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); for (int i = 0; (double)i <= 24; i = i > 0 ? -i : 1 - i) { for (int j = 0; (double)j < 24; ++j) { for (int k = 0; k <= j; k = k > 0 ? -k : 1 - k) { for (int l = k < j && k > -j ? j : 0; l <= j; l = l > 0 ? -l : 1 - l) { mutablePos.setWithOffset(pos, k, i - 1, l); if (this.seaOtter.isWithinRestriction(mutablePos) && this.isValidTarget(this.seaOtter.level(), mutablePos)) { return Optional.of(mutablePos); } } } } } return Optional.empty(); } protected boolean isValidTarget(LevelReader level, BlockPos pos) { return level.getBlockState(pos.below()).is(Blocks.WATER) && level.getBlockState(pos).isAir(); } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public static class FloatingGoal extends Goal { private final SeaOtter seaOtter; private Phase phase; public FloatingGoal(SeaOtter mob) { this.seaOtter = mob; this.setFlags(EnumSet.of(Flag.MOVE)); } @Override public boolean canUse() { return this.seaOtter.hasReachedSurface() && !this.seaOtter.wantsToSwimAround(); } @Override public boolean canContinueToUse() { return this.phase != Phase.SWIMMING; } @Override public void start() { if (!this.seaOtter.isFloating()) { this.seaOtter.setReachedSurface(false); this.seaOtter.resetTicksSinceLastSwam(); this.seaOtter.setFloating(true); this.seaOtter.switchToState(State.START_FLOATING); this.phase = Phase.START_FLOATING; System.out.println("started floating"); } } @Override public void stop() { this.seaOtter.setReachedSurface(false); this.seaOtter.setNeedsToSurface(false); this.seaOtter.setFloating(false); this.seaOtter.switchToState(State.SWIMMING); System.out.println("finished floating"); } @Override public void tick() { switch (this.phase) { case START_FLOATING -> { if (this.seaOtter.inStateTicks >= State.START_FLOATING.animationDuration()) { this.seaOtter.switchToState(State.FLOATING); this.phase = Phase.FLOATING; } } case FLOATING -> { if (this.seaOtter.inStateTicks >= State.FLOATING.animationDuration() && this.wantsToBeDone()) { System.out.println("stopping floating"); this.seaOtter.switchToState(State.STOP_FLOATING); this.phase = Phase.STOP_FLOATING; } } case STOP_FLOATING -> { if (this.seaOtter.inStateTicks >= State.STOP_FLOATING.animationDuration()) { this.seaOtter.switchToState(State.SWIMMING); this.phase = Phase.SWIMMING; } } } } private boolean wantsToBeDone() { return this.seaOtter.wantsToSwimAround(); } enum Phase { SWIMMING, START_FLOATING, FLOATING, STOP_FLOATING } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public static class SwimAroundGoal extends RandomSwimmingGoal { private final SeaOtter seaOtter; public SwimAroundGoal(SeaOtter mob, double speed, int interval) { super(mob, speed, interval); this.seaOtter = mob; this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK)); } @Override public boolean canUse() { if (this.seaOtter.needsToSurface()) { return false; } else if (this.seaOtter.isFloating()) { return false; } return super.canUse(); } @Override public boolean canContinueToUse() { if (this.seaOtter.needsToSurface()) { return false; } else if (this.seaOtter.hasBeenSwimmingLongEnough()) { return false; } return super.canContinueToUse(); } @Override public void start() { super.start(); this.seaOtter.setSwimming(true); System.out.println("started swimming"); } @Override public void stop() { super.stop(); if (this.seaOtter.hasBeenSwimmingLongEnough()) { this.seaOtter.setNeedsToSurface(true); this.seaOtter.resetTicksSinceLastSwam(); this.seaOtter.resetTicksSpentSwimming(); System.out.println("stopped swimming"); } } @Override public void tick() { this.seaOtter.ticksSpentSwimming++; } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public static class SeaOtterLookAtPlayerGoal extends LookAtPlayerGoal { private final SeaOtter seaOtter; public SeaOtterLookAtPlayerGoal(SeaOtter mob, float distance) { super(mob, Player.class, distance); this.seaOtter = mob; } @Override public boolean canUse() { return super.canUse() && !this.seaOtter.isSwimming() && !this.seaOtter.isActivelyInFloatTransition(); } } public static class SeaOtterTemptGoal extends TemptGoal { private final SeaOtter seaOtter; public SeaOtterTemptGoal(SeaOtter mob, double speed, Predicate items) { super(mob, speed, items, false); this.seaOtter = mob; } } public static class SeaOtterRandomLookAroundGoal extends RandomLookAroundGoal { private final SeaOtter seaOtter; public SeaOtterRandomLookAroundGoal(SeaOtter mob) { super(mob); this.seaOtter = mob; } @Override public boolean canUse() { return super.canUse() && !this.seaOtter.isSwimming(); } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public static class SeaOtterMoveControl extends MoveControl { private final SeaOtter seaOtter; public SeaOtterMoveControl(SeaOtter mob) { super(mob); this.seaOtter = mob; } @Override public void tick() { if (this.seaOtter.isSleeping()) return; if (this.seaOtter.isInWater() && !this.seaOtter.isFloating()) { this.seaOtter.setDeltaMovement(this.seaOtter.getDeltaMovement().add(this.seaOtter.getLookAngle().scale(this.seaOtter.isFloating() ? 0.002F : 0.005F))); if (this.operation == MoveControl.Operation.MOVE_TO && !this.seaOtter.getNavigation().isDone()) { double d0 = this.wantedX - this.seaOtter.getX(); double d1 = this.wantedY - this.seaOtter.getY(); double d2 = this.wantedZ - this.seaOtter.getZ(); double d3 = d0 * d0 + d1 * d1 + d2 * d2; if (d3 < (double) 2.5000003E-7F) { this.seaOtter.setZza(0.0F); } else { float f = (float) (Mth.atan2(d2, d0) * (double) (180F / (float) Math.PI)) - 90.0F; this.seaOtter.setYRot(this.rotlerp(this.seaOtter.getYRot(), f, (float) 10)); this.seaOtter.yBodyRot = this.seaOtter.getYRot(); this.seaOtter.yHeadRot = this.seaOtter.getYRot(); float speed = (float) (this.speedModifier * this.seaOtter.getAttributeValue(Attributes.MOVEMENT_SPEED)); this.seaOtter.setSpeed(speed * 0.02F); double d4 = Math.sqrt(d0 * d0 + d2 * d2); if (Math.abs(d1) > (double) 1.0E-5F || Math.abs(d4) > (double) 1.0E-5F) { float f3 = -((float) (Mth.atan2(d1, d4) * (double) (180F / (float) Math.PI))); f3 = Mth.clamp(Mth.wrapDegrees(f3), (float) (-85), (float) 85); this.seaOtter.setXRot(this.rotlerp(this.seaOtter.getXRot(), f3, 5.0F)); } if (d1 > 0.0D && d0 * d0 + d2 * d2 < 4.0F && d1 <= 1.0D && this.seaOtter.level().getBlockState(BlockPos.containing(this.wantedX, this.wantedY, this.wantedZ)).getFluidState().isEmpty()) { this.seaOtter.getJumpControl().jump(); this.seaOtter.setSpeed(speed); } float f6 = Mth.cos(this.seaOtter.getXRot() * ((float) Math.PI / 180F)); float f4 = Mth.sin(this.seaOtter.getXRot() * ((float) Math.PI / 180F)); this.seaOtter.zza = f6 * speed; this.seaOtter.yya = -f4 * speed; } } else { this.seaOtter.setSpeed(0.0F); this.seaOtter.setXxa(0.0F); this.seaOtter.setYya(0.0F); this.seaOtter.setZza(0.0F); } } else { if (this.hasWanted() && this.seaOtter.isSitting()) { this.seaOtter.standUp(); } if (!this.seaOtter.isFloating()) { super.tick(); } } } } public static class SeaOtterLookControl extends LookControl { private final SeaOtter seaOtter; public SeaOtterLookControl(SeaOtter mob) { super(mob); this.seaOtter = mob; } @Override protected Optional getXRotD() { if (this.seaOtter.isFloating() && super.getXRotD().isPresent()) { return Optional.of(-super.getXRotD().get()); } return super.getXRotD(); } @Override protected Optional getYRotD() { if (this.seaOtter.isFloating() && super.getYRotD().isPresent()) { return Optional.of(Mth.wrapDegrees(super.getYRotD().get() + 160.0F)); } return super.getYRotD(); } @Override protected boolean resetXRotOnTick() { return this.seaOtter.isActivelyInFloatTransition(); } @Override public void tick() { if (this.resetXRotOnTick()) { this.seaOtter.setXRot(0.0F); return; } if (this.seaOtter.isInWater() && this.seaOtter.isUnderWater()) { if (this.lookAtCooldown > 0) { this.lookAtCooldown--; this.getYRotD().ifPresent(i -> this.seaOtter.yHeadRot = this.rotateTowards(this.seaOtter.yHeadRot, i + 20.0F, this.yMaxRotSpeed)); this.getXRotD().ifPresent(i -> this.seaOtter.setXRot(this.rotateTowards(this.seaOtter.getXRot(), i + 10.0F, this.xMaxRotAngle))); } else { if (this.seaOtter.getNavigation().isDone()) { this.seaOtter.setXRot(this.rotateTowards(this.seaOtter.getXRot(), 0.0F, 5.0F)); } this.seaOtter.yHeadRot = this.rotateTowards(this.seaOtter.yHeadRot, this.seaOtter.yBodyRot, this.yMaxRotSpeed); } float f = Mth.wrapDegrees(this.seaOtter.yHeadRot - this.seaOtter.yBodyRot); if (f < (float)(-10)) { this.seaOtter.yBodyRot -= 4.0F; } else if (f > (float)10) { this.seaOtter.yBodyRot += 4.0F; } } else { super.tick(); } } } public static class SeaOtterBodyRotationControl extends BodyRotationControl { private final SeaOtter seaOtter; public SeaOtterBodyRotationControl(SeaOtter mob) { super(mob); this.seaOtter = mob; } @Override public void clientTick() { if (!this.seaOtter.refuseToMove() && !this.seaOtter.isFloating()) { super.clientTick(); } } } public static class SeaOtterPathNavigation extends AmphibiousPathNavigation { private final SeaOtter seaOtter; public SeaOtterPathNavigation(SeaOtter mob, Level level) { super(mob, level); this.seaOtter = mob; this.setCanFloat(true); } @Override protected PathFinder createPathFinder(int nodes) { this.nodeEvaluator = new AmphibiousNodeEvaluator(true); return new PathFinder(this.nodeEvaluator, nodes); } @Override public boolean isStableDestination(BlockPos pos) { BlockState belowState = this.level.getBlockState(pos.below()); if (this.seaOtter.isInWater()) { return !(belowState.isAir() || belowState.getFluidState().is(FluidTags.WATER)); } else { return !belowState.isAir(); } } } public enum State implements StringRepresentable { NOTHING("nothing", 0, 0), SWIMMING_IDLE("swimming_idle", 1, 50), SWIMMING("swimming", 2, 40), TWIRL_WHILE_SWIMMING("twirl_while_swimming", 3, 30), FLOATING("floating", 4, 50), START_FLOATING("start_floating", 5, 30), STOP_FLOATING("stop_floating", 6, 25), SLEEPING_WHILE_FLOATING("sleeping_while_floating", 7, 0), EATING_WHILE_FLOATING("eating_while_floating", 8, 50), SIT_DOWN("sit_down", 9, 30), SITTING_IDLE("sitting_idle", 10, 60), STAND_UP("stand_up", 11, 30), STANDING_IDLE("standing_idle", 12, 60), WALKING("walking", 13, 45); private static final IntFunction BY_ID = ByIdMap.continuous(SeaOtter.State::id, values(), ByIdMap.OutOfBoundsStrategy.ZERO); public static final StreamCodec STREAM_CODEC = ByteBufCodecs.idMapper(BY_ID, SeaOtter.State::id); private final String name; private final int id; private final int animationDuration; State(final String name, final int id, final int animationDuration) { this.name = name; this.id = id; this.animationDuration = animationDuration; } public static SeaOtter.State fromName(String name) { return StringRepresentable.fromEnum(SeaOtter.State::values).byName(name, NOTHING); } @Override public String getSerializedName() { return this.name; } private int id() { return this.id; } public int animationDuration() { return this.animationDuration; } } /** * todo: Current Actions * * actions decide what the entity wants to do. * - i.e. a sea otter who wants to swim, if they're currently floating, once that is finished they will start swimming. * * que actions so when the current one is finished they will start the next one. * * actions will be paired with a reasoning. * - i.e. the action "wants to swim" can have the reasoning for why it's doing that, being able to control what "wants to swim" will do exactly. * * each action has a base length, so like they can only start the next one if they're done with their current one. this is overridden in the case of * an emergency, like if they need to run away, or are gonna drown and need to stop looking for fish. */ private enum CoreStates { ON_LAND, FLOATING_ON_SURFACE, SURFACE_SWIMMING, DEEP_SEA_SWIMMING } private enum ActionReasons { NEEDS_AIR, WANTS_TO_FLOAT, IS_BEDTIME } }