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 extends SeaOtter> 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
}
}