Commit 437a3695 by Jonathan Thomas

Added `PlayerBaseGoal` class to allow **goals/behaviors** to **continue** after…

Added `PlayerBaseGoal` class to allow **goals/behaviors** to **continue** after a player **respawns** / logs out / logs in. Fixed teleport issue to be closer to player position, and fixed a regression caused by teleport that stopped following entities that were too far away.
parent 13979b85
Pipeline #12563 passed with stages
in 2 minutes 21 seconds
...@@ -7,15 +7,17 @@ All notable changes to **CreatureChat** are documented in this file. The format ...@@ -7,15 +7,17 @@ All notable changes to **CreatureChat** are documented in this file. The format
## [Unreleased] ## [Unreleased]
### Added ### Added
- New **PROTECT** behavior: defend a player from attacks! - New **PROTECT** behavior: defend a player from attacks
- **Native attack abilities** (when using the ATTACK or PROTECT behaviors) for hostile mob types - New **UNPROTECT** behavior: stop defending a player from attacks
- **Native ATTACK abilities** (when using the attack or protect behaviors) for hostile mob types
- **End of Game** triggered by max friendship with the **EnderDragon**! - **End of Game** triggered by max friendship with the **EnderDragon**!
### Changed ### Changed
- Improved **FLEE** behavior, to make it more reliable and more random. - Improved **FLEE** behavior, to make it more reliable and more random.
- Improved **FOLLOW** behavior, supporting teleporting entities (Enderman, Endermite, and Shulker) - Improved **FOLLOW** behavior, support **teleporting** entities (*Enderman, Endermite, and Shulker*)
- Refactored **ATTACK** behavior to allow more flexibility (in order to support PROTECT behavior) - Refactored **ATTACK** behavior to allow more flexibility (in order to support PROTECT behavior)
- Updated ServerEntityFinder::getEntityByUUID to be more generic and so it can find players and mobs. - Updated `ServerEntityFinder::getEntityByUUID` to be more generic and so it can find players and mobs.
- Added `PlayerBaseGoal` class to allow **goals/behaviors** to **continue** after a player **respawns** / logs out / logs in
## [1.0.6] - 2024-06-17 ## [1.0.6] - 2024-06-17
......
...@@ -2,7 +2,6 @@ package com.owlmaddie.goals; ...@@ -2,7 +2,6 @@ package com.owlmaddie.goals;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.RangedAttackMob; import net.minecraft.entity.ai.RangedAttackMob;
import net.minecraft.entity.ai.goal.Goal;
import net.minecraft.entity.mob.Angerable; import net.minecraft.entity.mob.Angerable;
import net.minecraft.entity.mob.HostileEntity; import net.minecraft.entity.mob.HostileEntity;
import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.mob.MobEntity;
...@@ -18,9 +17,8 @@ import java.util.EnumSet; ...@@ -18,9 +17,8 @@ import java.util.EnumSet;
* The {@code AttackPlayerGoal} class instructs a Mob Entity to show aggression towards a target Entity. * The {@code AttackPlayerGoal} class instructs a Mob Entity to show aggression towards a target Entity.
* For passive entities like chickens (or hostile entities in creative mode), damage is simulated with particles. * For passive entities like chickens (or hostile entities in creative mode), damage is simulated with particles.
*/ */
public class AttackPlayerGoal extends Goal { public class AttackPlayerGoal extends PlayerBaseGoal {
protected final MobEntity attackerEntity; protected final MobEntity attackerEntity;
protected LivingEntity targetEntity;
protected final double speed; protected final double speed;
protected enum EntityState { MOVING_TOWARDS_PLAYER, IDLE, CHARGING, ATTACKING, LEAPING } protected enum EntityState { MOVING_TOWARDS_PLAYER, IDLE, CHARGING, ATTACKING, LEAPING }
protected EntityState currentState = EntityState.IDLE; protected EntityState currentState = EntityState.IDLE;
...@@ -31,7 +29,7 @@ public class AttackPlayerGoal extends Goal { ...@@ -31,7 +29,7 @@ public class AttackPlayerGoal extends Goal {
protected final double ATTACK_DISTANCE = 4D; // 2 blocks away protected final double ATTACK_DISTANCE = 4D; // 2 blocks away
public AttackPlayerGoal(LivingEntity targetEntity, MobEntity attackerEntity, double speed) { public AttackPlayerGoal(LivingEntity targetEntity, MobEntity attackerEntity, double speed) {
this.targetEntity = targetEntity; super(targetEntity);
this.attackerEntity = attackerEntity; this.attackerEntity = attackerEntity;
this.speed = speed; this.speed = speed;
this.setControls(EnumSet.of(Control.MOVE, Control.LOOK, Control.TARGET)); this.setControls(EnumSet.of(Control.MOVE, Control.LOOK, Control.TARGET));
...@@ -44,12 +42,12 @@ public class AttackPlayerGoal extends Goal { ...@@ -44,12 +42,12 @@ public class AttackPlayerGoal extends Goal {
@Override @Override
public boolean canStart() { public boolean canStart() {
return isGoalActive(); return super.canStart() && isGoalActive();
} }
@Override @Override
public boolean shouldContinue() { public boolean shouldContinue() {
return isGoalActive(); return super.canStart() && isGoalActive();
} }
@Override @Override
......
package com.owlmaddie.goals; package com.owlmaddie.goals;
import net.minecraft.entity.ai.FuzzyTargeting; import net.minecraft.entity.ai.FuzzyTargeting;
import net.minecraft.entity.ai.goal.Goal;
import net.minecraft.entity.ai.pathing.Path; import net.minecraft.entity.ai.pathing.Path;
import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.mob.PathAwareEntity; import net.minecraft.entity.mob.PathAwareEntity;
...@@ -14,14 +13,13 @@ import java.util.EnumSet; ...@@ -14,14 +13,13 @@ import java.util.EnumSet;
* The {@code FleePlayerGoal} class instructs a Mob Entity to flee from the current player * The {@code FleePlayerGoal} class instructs a Mob Entity to flee from the current player
* and only recalculates path when it has reached its destination and the player is close again. * and only recalculates path when it has reached its destination and the player is close again.
*/ */
public class FleePlayerGoal extends Goal { public class FleePlayerGoal extends PlayerBaseGoal {
private final MobEntity entity; private final MobEntity entity;
private ServerPlayerEntity targetPlayer;
private final double speed; private final double speed;
private final float fleeDistance; private final float fleeDistance;
public FleePlayerGoal(ServerPlayerEntity player, MobEntity entity, double speed, float fleeDistance) { public FleePlayerGoal(ServerPlayerEntity player, MobEntity entity, double speed, float fleeDistance) {
this.targetPlayer = player; super(player);
this.entity = entity; this.entity = entity;
this.speed = speed; this.speed = speed;
this.fleeDistance = fleeDistance; this.fleeDistance = fleeDistance;
...@@ -30,12 +28,12 @@ public class FleePlayerGoal extends Goal { ...@@ -30,12 +28,12 @@ public class FleePlayerGoal extends Goal {
@Override @Override
public boolean canStart() { public boolean canStart() {
return this.targetPlayer != null && this.entity.squaredDistanceTo(this.targetPlayer) < fleeDistance * fleeDistance; return super.canStart() && this.entity.squaredDistanceTo(this.targetEntity) < fleeDistance * fleeDistance;
} }
@Override @Override
public boolean shouldContinue() { public boolean shouldContinue() {
return this.targetPlayer != null && this.entity.squaredDistanceTo(this.targetPlayer) < fleeDistance * fleeDistance; return super.canStart() && this.entity.squaredDistanceTo(this.targetEntity) < fleeDistance * fleeDistance;
} }
@Override @Override
...@@ -46,7 +44,7 @@ public class FleePlayerGoal extends Goal { ...@@ -46,7 +44,7 @@ public class FleePlayerGoal extends Goal {
private void fleeFromPlayer() { private void fleeFromPlayer() {
int roundedFleeDistance = Math.round(fleeDistance); int roundedFleeDistance = Math.round(fleeDistance);
Vec3d fleeTarget = FuzzyTargeting.findFrom((PathAwareEntity)this.entity, roundedFleeDistance, Vec3d fleeTarget = FuzzyTargeting.findFrom((PathAwareEntity)this.entity, roundedFleeDistance,
roundedFleeDistance, this.entity.getPos()); roundedFleeDistance, this.targetEntity.getPos());
if (fleeTarget != null) { if (fleeTarget != null) {
Path path = this.entity.getNavigation().findPathTo(fleeTarget.x, fleeTarget.y, fleeTarget.z, 0); Path path = this.entity.getNavigation().findPathTo(fleeTarget.x, fleeTarget.y, fleeTarget.z, 0);
......
...@@ -2,7 +2,6 @@ package com.owlmaddie.goals; ...@@ -2,7 +2,6 @@ package com.owlmaddie.goals;
import com.owlmaddie.controls.LookControls; import com.owlmaddie.controls.LookControls;
import net.minecraft.entity.ai.FuzzyTargeting; import net.minecraft.entity.ai.FuzzyTargeting;
import net.minecraft.entity.ai.goal.Goal;
import net.minecraft.entity.ai.pathing.EntityNavigation; import net.minecraft.entity.ai.pathing.EntityNavigation;
import net.minecraft.entity.mob.*; import net.minecraft.entity.mob.*;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
...@@ -11,16 +10,15 @@ import net.minecraft.util.math.Vec3d; ...@@ -11,16 +10,15 @@ import net.minecraft.util.math.Vec3d;
import java.util.EnumSet; import java.util.EnumSet;
/** /**
* The {@code FollowPlayerGoal} class instructs a Mob Entity to follow the current player. * The {@code FollowPlayerGoal} class instructs a Mob Entity to follow the current target entity.
*/ */
public class FollowPlayerGoal extends Goal { public class FollowPlayerGoal extends PlayerBaseGoal {
private final MobEntity entity; private final MobEntity entity;
private ServerPlayerEntity targetPlayer;
private final EntityNavigation navigation; private final EntityNavigation navigation;
private final double speed; private final double speed;
public FollowPlayerGoal(ServerPlayerEntity player, MobEntity entity, double speed) { public FollowPlayerGoal(ServerPlayerEntity player, MobEntity entity, double speed) {
this.targetPlayer = player; super(player);
this.entity = entity; this.entity = entity;
this.speed = speed; this.speed = speed;
this.navigation = entity.getNavigation(); this.navigation = entity.getNavigation();
...@@ -30,13 +28,13 @@ public class FollowPlayerGoal extends Goal { ...@@ -30,13 +28,13 @@ public class FollowPlayerGoal extends Goal {
@Override @Override
public boolean canStart() { public boolean canStart() {
// Start only if the target player is more than 8 blocks away // Start only if the target player is more than 8 blocks away
return this.targetPlayer != null && this.targetPlayer.isAlive() && this.entity.squaredDistanceTo(this.targetPlayer) > 64; return super.canStart() && this.entity.squaredDistanceTo(this.targetEntity) > 64;
} }
@Override @Override
public boolean shouldContinue() { public boolean shouldContinue() {
// Continue unless the entity gets within 3 blocks of the player // Continue unless the entity gets within 3 blocks of the player
return this.targetPlayer != null && this.targetPlayer.isAlive() && this.entity.squaredDistanceTo(this.targetPlayer) > 9; return super.canStart() && this.entity.squaredDistanceTo(this.targetEntity) > 9;
} }
@Override @Override
...@@ -47,22 +45,24 @@ public class FollowPlayerGoal extends Goal { ...@@ -47,22 +45,24 @@ public class FollowPlayerGoal extends Goal {
@Override @Override
public void tick() { public void tick() {
if (this.entity.squaredDistanceTo(this.targetPlayer) > 256) {
// If the entity is too far away (more than 16 blocks), teleport it within 8 blocks of the player
if (this.entity instanceof EndermanEntity || this.entity instanceof EndermiteEntity || this.entity instanceof ShulkerEntity) { if (this.entity instanceof EndermanEntity || this.entity instanceof EndermiteEntity || this.entity instanceof ShulkerEntity) {
Vec3d targetPos = findTeleportPosition(8); // Certain entities should teleport to the player if they get too far
if (this.entity.squaredDistanceTo(this.targetEntity) > 256) {
Vec3d targetPos = findTeleportPosition(12);
if (targetPos != null) { if (targetPos != null) {
this.entity.teleport(targetPos.x, targetPos.y, targetPos.z); this.entity.teleport(targetPos.x, targetPos.y, targetPos.z);
} }
} }
} else { } else {
// Look at the player and start moving towards them // Look at the player and start moving towards them
LookControls.lookAtPlayer(this.targetPlayer, this.entity); if (this.targetEntity instanceof ServerPlayerEntity) {
this.navigation.startMovingTo(this.targetPlayer, this.speed); LookControls.lookAtPlayer((ServerPlayerEntity)this.targetEntity, this.entity);
}
this.navigation.startMovingTo(this.targetEntity, this.speed);
} }
} }
private Vec3d findTeleportPosition(int distance) { private Vec3d findTeleportPosition(int distance) {
return FuzzyTargeting.findTo((PathAwareEntity)this.entity, distance, distance, this.targetPlayer.getPos()); return FuzzyTargeting.findTo((PathAwareEntity)this.entity, distance, distance, this.targetEntity.getPos());
} }
} }
package com.owlmaddie.goals;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.goal.Goal;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
/**
* The {@code PlayerBaseGoal} class sets a targetEntity, and will automatically update the targetEntity
* when a player die's and respawns, or logs back in, etc... Other types of targetEntity classes will
* be set to null after they die.
*/
public abstract class PlayerBaseGoal extends Goal {
protected LivingEntity targetEntity;
private final int updateInterval = 20;
private int tickCounter = 0;
public PlayerBaseGoal(LivingEntity targetEntity) {
this.targetEntity = targetEntity;
}
@Override
public boolean canStart() {
if (++tickCounter >= updateInterval) {
tickCounter = 0;
updateTargetEntity();
}
return targetEntity != null && targetEntity.isAlive();
}
private void updateTargetEntity() {
if (targetEntity != null && !targetEntity.isAlive()) {
if (targetEntity instanceof ServerPlayerEntity) {
ServerWorld world = (ServerWorld) targetEntity.getWorld();
ServerPlayerEntity lookupPlayer = (ServerPlayerEntity)world.getPlayerByUuid(targetEntity.getUuid());
if (lookupPlayer != null && lookupPlayer.isAlive()) {
// Update player to alive player with same UUID
targetEntity = lookupPlayer;
}
} else {
targetEntity = null;
}
}
}
}
...@@ -29,7 +29,7 @@ public class ProtectPlayerGoal extends AttackPlayerGoal { ...@@ -29,7 +29,7 @@ public class ProtectPlayerGoal extends AttackPlayerGoal {
} }
if (this.targetEntity != null && !this.targetEntity.isAlive()) { if (this.targetEntity != null && !this.targetEntity.isAlive()) {
// clear target // clear dead target
this.targetEntity = null; this.targetEntity = null;
} }
......
...@@ -47,7 +47,7 @@ Include as many behaviors as needed at the end of the message. These are the ONL ...@@ -47,7 +47,7 @@ Include as many behaviors as needed at the end of the message. These are the ONL
<UNFOLLOW> Stop following the player location. If the player asks you to stay, wait, or stop following them, please output this behavior. <UNFOLLOW> Stop following the player location. If the player asks you to stay, wait, or stop following them, please output this behavior.
<FLEE> Flee from the player (if you are weak or timid). If the player threatens or scares you, please output this behavior to stay away from the player. <FLEE> Flee from the player (if you are weak or timid). If the player threatens or scares you, please output this behavior to stay away from the player.
<ATTACK> Attack the player (if you are strong and brave). If the player threatens or scares you, please output this behavior to attack the player and defend yourself. <ATTACK> Attack the player (if you are strong and brave). If the player threatens or scares you, please output this behavior to attack the player and defend yourself.
<PROTECT> Protect the player when they are attacked (if you are strong and brave). <PROTECT> Protect the player when they are attacked (if you are strong and brave). This only protects the player.
<UNPROTECT> Stop protecting the player <UNPROTECT> Stop protecting the player
Output Syntax: Output Syntax:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment