Skip to content
This repository has been archived by the owner on Feb 11, 2024. It is now read-only.

Commit

Permalink
improvements
Browse files Browse the repository at this point in the history
- check for directly looking at again
- climbing behaviour
- chase behaviour in general (crawling etc.)
  • Loading branch information
SiverDX committed Sep 25, 2023
1 parent 9b22a15 commit e49e032
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 126 deletions.
13 changes: 1 addition & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,4 @@ Requires GeckoLib: https://www.curseforge.com/minecraft/mc-mods/geckolib/files?v
# Other
Recommend using https://www.curseforge.com/minecraft/texture-packs/creepier-cave-dwellers
* You will need to rename `assets\cavenoise` to `assets\cave_dweller`
* Delete everything except the `geo` and `textures` directories

# To Consider

## Bugs
* Climbing and Crawling don't like each other
* Crawling animation (need to split between crawling and crawling finished)

## Ideas
* Add chance to flee when its on fire
* Extinguish light sources in a certain radius around the mob
* Trigger chase as retaliation
* Delete everything except the `geo` and `textures` directories
10 changes: 10 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Need to fix
- If the Cave Dweller would need to crawl but it's facing the player through a corner (which contains no solid block) it won't crawl
- Sometimes gets stuck in walls for a bit
- Animations
- Reduce the amount of blockstate gets

# Ideas
- Add chance to flee when its on fire
- Extinguish light sources in a certain radius around the mob
- Trigger chase as retaliation
107 changes: 58 additions & 49 deletions src/main/java/de/cadentem/cave_dweller/entities/CaveDwellerEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import net.minecraft.world.entity.ai.navigation.PathNavigation;
import net.minecraft.world.entity.ai.navigation.WallClimberNavigation;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.block.state.BlockState;
Expand Down Expand Up @@ -67,19 +68,15 @@ public class CaveDwellerEntity extends Monster implements IAnimatable {
public static final EntityDataAccessor<Boolean> SPOTTED_ACCESSOR = SynchedEntityData.defineId(CaveDwellerEntity.class, EntityDataSerializers.BOOLEAN);
public static final EntityDataAccessor<Boolean> CLIMBING_ACCESSOR = SynchedEntityData.defineId(CaveDwellerEntity.class, EntityDataSerializers.BOOLEAN);

private final float twoBlockSpaceCooldown;

public Roll currentRoll = Roll.STROLL;
public boolean isFleeing;
/** To be able to create a path while spawning */
public boolean hasSpawned;
public boolean pleaseStopMoving;
public boolean targetIsLookingAtMe;
public boolean targetIsFacingMe;

private float twoBlockSpaceTimer;
private int ticksTillRemove;
private int chaseSoundClock;
private boolean inTwoBlockSpace;
private boolean alreadyPlayedFleeSound;
private boolean alreadyPlayedSpottedSound;
private boolean startedPlayingChaseSound;
Expand All @@ -88,7 +85,6 @@ public class CaveDwellerEntity extends Monster implements IAnimatable {
public CaveDwellerEntity(final EntityType<? extends CaveDwellerEntity> entityType, final Level level) {
super(entityType, level);
this.refreshDimensions();
this.twoBlockSpaceCooldown = 5.0F;
this.ticksTillRemove = Utils.secondsToTicks(ServerConfig.TIME_UNTIL_LEAVE.get());
}

Expand Down Expand Up @@ -197,65 +193,57 @@ public void tick() {
}

if (getTarget() != null) {
targetIsLookingAtMe = isLookingAtMe(getTarget());
targetIsFacingMe = isLookingAtMe(getTarget(), false);
}

boolean shouldCrouch = false;

if (!getEntityData().get(CRAWLING_ACCESSOR)) {
boolean isTwoAboveSolid = level.getBlockState(blockPosition().above().above()).getMaterial().isSolid();
boolean isAboveSolid = level.getBlockState(blockPosition().above()).getMaterial().isSolid();
boolean isTwoAboveSolid = level.getBlockState(blockPosition().above(2)).getMaterial().isSolid();

/* [x : blocks | o : cave dweller]
To handle these two variants:
o
xxxxo xxxxo
o o
xxxxx xxxxo
*/
Vec3i offset = new Vec3i(getDirection().getStepX(), getDirection().getStepY(), getDirection().getStepZ());
boolean isFacingSolid = level.getBlockState(blockPosition().relative(getDirection())).getMaterial().isSolid();

if (isFacingSolid) {
offset = offset.offset(0, 1, 0);
}

boolean isOffsetFacingSolid = level.getBlockState(blockPosition().offset(offset)).getMaterial().isSolid();
boolean isOffsetFacingTwoAboveSolid = level.getBlockState(blockPosition().offset(offset).above().above()).getMaterial().isSolid();
boolean isOffsetFacingAboveSolid = level.getBlockState(blockPosition().relative(getDirection()).above()).getMaterial().isSolid();
/* [- : blocks | o : cave dweller]
To handle these two variants:
o
----o ----o
o o
----- ----o
*/
Vec3i offset = new Vec3i(getDirection().getStepX(), getDirection().getStepY(), getDirection().getStepZ());
boolean isFacingSolid = level.getBlockState(blockPosition().relative(getDirection())).getMaterial().isSolid();

shouldCrouch = isTwoAboveSolid || (!isOffsetFacingSolid && !isOffsetFacingAboveSolid && isOffsetFacingTwoAboveSolid);
if (isFacingSolid) {
offset = offset.offset(0, 1, 0);
}

if (shouldCrouch) {
twoBlockSpaceTimer = twoBlockSpaceCooldown;
inTwoBlockSpace = true;
} else {
// Don't immediately stop crouching
--twoBlockSpaceTimer;
boolean isOffsetFacingSolid = level.getBlockState(blockPosition().offset(offset)).getMaterial().isSolid();
boolean isOffsetFacingTwoAboveSolid = level.getBlockState(blockPosition().offset(offset).above(2)).getMaterial().isSolid();
boolean isOffsetFacingAboveSolid = level.getBlockState(blockPosition().relative(getDirection()).above()).getMaterial().isSolid();

if (twoBlockSpaceTimer <= 0.0F) {
inTwoBlockSpace = false;
}
}
boolean shouldCrouch = isTwoAboveSolid || (!isOffsetFacingSolid && !isOffsetFacingAboveSolid && isOffsetFacingTwoAboveSolid);
boolean shouldCrawl = isAboveSolid || (!isOffsetFacingSolid && isOffsetFacingAboveSolid);

if (level instanceof ServerLevel) {
if (isAggressive() || isFleeing) {
entityData.set(SPOTTED_ACCESSOR, false);
}

setClimbing(horizontalCollision);
entityData.set(CROUCHING_ACCESSOR, inTwoBlockSpace);
entityData.set(CROUCHING_ACCESSOR, shouldCrouch);
setCrawling(shouldCrawl);
}

if (entityData.get(SPOTTED_ACCESSOR)) {
playSpottedSound();
}

refreshDimensions();
getNavigation().setSpeedModifier(getSpeedModifier());

super.tick();
}

public double getSpeedModifier() {
return isCrawling() ? 0.35 : isCrouching() ? 0.6 : 0.85;
}

@Override
public @NotNull EntityDimensions getDimensions(@NotNull final Pose pose) {
if (entityData.get(CRAWLING_ACCESSOR)) { // TODO :: Allow config (for crawling through half-block space)?
Expand Down Expand Up @@ -296,8 +284,9 @@ public boolean isClimbing() {
return false;
}

if (getTarget() != null /*&& getTarget().getPosition(1).y > getY()*/) {
return entityData.get(CLIMBING_ACCESSOR);
if (getTarget() != null) {
// TODO :: Not sure if the initial two checks are needed
return !isCrawling() && !isCrouching() && entityData.get(CLIMBING_ACCESSOR);
}

return false;
Expand Down Expand Up @@ -467,6 +456,27 @@ protected void playHurtSound(@NotNull final DamageSource pSource) {
playEntitySound(soundevent, 2.0F, 1.0F);
}

public void setCrawling(boolean shouldCrawl) {
if (shouldCrawl) {
getEntityData().set(CROUCHING_ACCESSOR, false);
}

getEntityData().set(CRAWLING_ACCESSOR, shouldCrawl);
refreshDimensions();
}

public boolean isCrawling() {
return entityData.get(CRAWLING_ACCESSOR);
}


/* TODO :: Check
@Override
public boolean isVisuallyCrawling() {
return super.isVisuallyCrawling();
}
*/

@Override
protected void tickDeath() {
super.tickDeath();
Expand All @@ -477,7 +487,7 @@ protected void tickDeath() {
}
}

public boolean isLookingAtMe(final Entity target) {
public boolean isLookingAtMe(final Entity target, boolean directlyLooking) {
if (!Utils.isValidPlayer(target)) {
return false;
}
Expand All @@ -486,17 +496,16 @@ public boolean isLookingAtMe(final Entity target) {
return false;
}

return isLooking(target);
}

private boolean isLooking(final Entity target) {
Vec3 viewVector = target.getViewVector(1.0F).normalize();
Vec3 difference = new Vec3(getX() - target.getX(), getEyeY() - target.getEyeY(), getZ() - target.getZ());
difference = difference.normalize();
double dot = viewVector.dot(difference);

// FIXME :: The line of sight method is very unreliable
return dot > 0 /*&& target.hasLineOfSight(this)*/;
if (directlyLooking && target instanceof Player player) {
return dot > 0.99 && player.hasLineOfSight(this);
}

return dot > 0;
}

public boolean teleportToTarget() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public CaveDwellerBreakInvisGoal(final CaveDwellerEntity caveDweller) {

@Override
public boolean canUse() {
return caveDweller.isInvisible() && !caveDweller.targetIsLookingAtMe;
return caveDweller.isInvisible() && !caveDweller.targetIsFacingMe;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public boolean canUse() {
return false;
}

if (!caveDweller.targetIsLookingAtMe) {
if (!caveDweller.targetIsFacingMe) {
return false;
}

Expand Down Expand Up @@ -68,9 +68,6 @@ public boolean canUse() {
return true;
}

// Try path with smaller size
caveDweller.getEntityData().set(CaveDwellerEntity.CRAWLING_ACCESSOR, true);
caveDweller.refreshDimensions();
path = caveDweller.getNavigation().createPath(target, 0);

return path != null;
Expand Down Expand Up @@ -122,7 +119,7 @@ public boolean requiresUpdateEveryTick() {

@Override
public void tick() {
if (ticksUntilLeave <= 0 && !caveDweller.targetIsLookingAtMe) {
if (ticksUntilLeave <= 0 && !caveDweller.targetIsFacingMe) {
caveDweller.disappear();
}

Expand All @@ -133,56 +130,24 @@ public void tick() {
}

Path path = caveDweller.getNavigation().getPath();
fixPath(path);

if (path == null || path.isDone() || path.getEndNode() == null || path.getEndNode().distanceToSqr(target.blockPosition()) > 0.5) {
if (!caveDweller.isCrouching()) {
// To avoid detours if it could reach its target faster by just crouching
caveDweller.getEntityData().set(CaveDwellerEntity.CROUCHING_ACCESSOR, true);
caveDweller.refreshDimensions();
}

path = caveDweller.getNavigation().createPath(target, 0);
}

boolean isCrawling = false;
boolean shouldClimb = target.getY() > caveDweller.getY(); // TODO :: Add `path != null` and a try-climb logic if it does not get closer to target within x seconds?
boolean targetMoved = path != null && !path.isDone() && path.getNextNode().distanceTo(target.blockPosition()) > 1.5;

// When the node count is 1 it usually means that no actual path can be found (and the node point is just the target location)
if (!shouldClimb && (caveDweller.distanceToSqr(target) > 0.3 && (path == null || path.isDone() || path.getNodeCount() == 1))) {
// No path could be found, try with smaller size
isCrawling = true;
caveDweller.getEntityData().set(CaveDwellerEntity.CRAWLING_ACCESSOR, true);
caveDweller.refreshDimensions();
if (path == null || targetMoved || path.isDone() && !shouldClimb(path) || caveDweller.getNavigation().shouldRecomputePath(target.blockPosition()) && caveDweller.tickCount % 20 == 0) {
path = caveDweller.getNavigation().createPath(target, 0);
fixPath(path);
}

if (path != null && !path.isDone()) {
if (caveDweller.hasLineOfSight(target)) {
caveDweller.playChaseSound();
}

boolean isAboveSolid = caveDweller.level.getBlockState(caveDweller.blockPosition().above()).getMaterial().isSolid();
boolean isNextAboveSolid = caveDweller.level.getBlockState(path.getNextNodePos().above()).getMaterial().isSolid();

/* [x = blocks | o = cave dweller]
xxxx
o x
x o x
xxxxx
*/
boolean isFacingSolid = caveDweller.level.getBlockState(caveDweller.blockPosition().relative(caveDweller.getDirection())).getMaterial().isSolid();
boolean isFacingAboveSolid = caveDweller.level.getBlockState(caveDweller.blockPosition().relative(caveDweller.getDirection()).above()).getMaterial().isSolid();
boolean extraCheck = isFacingSolid && !isFacingAboveSolid;

isCrawling = isAboveSolid || isNextAboveSolid || (caveDweller.getEntityData().get(CaveDwellerEntity.CROUCHING_ACCESSOR) && extraCheck);
caveDweller.getEntityData().set(CaveDwellerEntity.CRAWLING_ACCESSOR, isCrawling);
caveDweller.refreshDimensions();
}

double speedModifier = (0.85 / maxSpeedReached) * speedUp; // FIXME :: Makes animation / movement wonky
caveDweller.getNavigation().moveTo(path, isCrawling ? 0.3 : 0.85);
caveDweller.getNavigation().moveTo(path, caveDweller.getSpeedModifier());

if (!isCrawling) {
if (!caveDweller.isCrawling()) {
if (caveDweller.isAggressive()) {
caveDweller.getLookControl().setLookAt(target, 90.0F, 90.0F);
} else {
Expand All @@ -201,6 +166,29 @@ public void tick() {
}
}

private boolean shouldClimb(final Path path) {
if (caveDweller.getTarget() == null) {
return false;
}

// TODO :: Is it safe to check coordinates of the node instead of the target?
return path != null && path.getNodeCount() == 1 && caveDweller.getTarget().blockPosition().getY() > caveDweller.blockPosition().getY() + caveDweller.getStepHeight();
}

private void fixPath(final Path path) {
LivingEntity target = caveDweller.getTarget();

if (target == null) {
return;
}

if (shouldClimb(path)) {
if (path.getNode(0).distanceTo(caveDweller.getTarget().blockPosition()) > 0.1) {
path.replaceNode(0, path.getNode(0).cloneAndMove(target.blockPosition().getX(), target.blockPosition().getY(), target.blockPosition().getZ()));
}
}
}

private void checkAndPerformAttack(final LivingEntity target, double distanceToTarget) {
// TODO :: Check if there is a wall between the player and the cave dweller?
double attackReach = getAttackReachSqr(target);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public void start() {
public void tick() {
LivingEntity target = caveDweller.getTarget();

if (shouldLeave && !caveDweller.targetIsLookingAtMe) {
if (shouldLeave && !caveDweller.targetIsFacingMe) {
caveDweller.disappear();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public void tick() {
return;
}

boolean actuallyLooking = caveDweller.targetIsLookingAtMe && target.hasLineOfSight(caveDweller);
boolean actuallyLooking = caveDweller.targetIsFacingMe && target.hasLineOfSight(caveDweller);

if (wasNotLookingPreviously && actuallyLooking) {
lookedAtCount++;
Expand Down
Loading

0 comments on commit e49e032

Please sign in to comment.