Commit 6f885653 by Jonathan Thomas

Improved Attack AI to run towards the player, slow down, leap and attack. The…

Improved Attack AI to run towards the player, slow down, leap and attack. The player can now sprint to outrun the attacking entity.
parent 5dd19219
Pipeline #11966 passed with stage
in 24 seconds
...@@ -235,7 +235,7 @@ public class ChatDataManager { ...@@ -235,7 +235,7 @@ public class ChatDataManager {
EntityBehaviorManager.removeGoal(entity, AttackPlayerGoal.class); EntityBehaviorManager.removeGoal(entity, AttackPlayerGoal.class);
EntityBehaviorManager.addGoal(entity, fleeGoal, GoalPriority.FLEE_PLAYER); EntityBehaviorManager.addGoal(entity, fleeGoal, GoalPriority.FLEE_PLAYER);
} else if (behavior.getName().equals("ATTACK")) { } else if (behavior.getName().equals("ATTACK")) {
AttackPlayerGoal attackGoal = new AttackPlayerGoal(player, entity, 1.5F); AttackPlayerGoal attackGoal = new AttackPlayerGoal(player, entity, 1.1F);
EntityBehaviorManager.removeGoal(entity, TalkPlayerGoal.class); EntityBehaviorManager.removeGoal(entity, TalkPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, FollowPlayerGoal.class); EntityBehaviorManager.removeGoal(entity, FollowPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class); EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class);
......
...@@ -9,20 +9,28 @@ import net.minecraft.entity.mob.MobEntity; ...@@ -9,20 +9,28 @@ import net.minecraft.entity.mob.MobEntity;
import net.minecraft.particle.ParticleTypes; import net.minecraft.particle.ParticleTypes;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import java.util.EnumSet; import java.util.EnumSet;
/** /**
* The {@code AttackPlayerGoal} class instructs a Mob Entity to show aggression towards the current player. * The {@code AttackPlayerGoal} class instructs a Mob Entity to show aggression towards the current player.
* For passive entities like chickens, this could manifest as humorous behaviors rather than actual damage. * For passive entities like chickens, damage is simulated with particles. But all MobEntity instances can damage
* the player.
*/ */
public class AttackPlayerGoal extends Goal { public class AttackPlayerGoal extends Goal {
private final MobEntity entity; private final MobEntity entity;
private ServerPlayerEntity targetPlayer; private ServerPlayerEntity targetPlayer;
private final EntityNavigation navigation; private final EntityNavigation navigation;
private final double speed; private final double speed;
private int aggressionTimer; enum EntityState { MOVING_TOWARDS_PLAYER, IDLE, CHARGING, ATTACKING, LEAPING }
private EntityState currentState = EntityState.IDLE;
private int cooldownTimer = 0;
private final int CHARGE_TIME = 15; // Time before leaping / attacking
private final double MOVE_DISTANCE = 200D; // 20 blocks away
private final double CHARGE_DISTANCE = 25D; // 5 blocks away
private final double ATTACK_DISTANCE = 4D; // 2 blocks away
public AttackPlayerGoal(ServerPlayerEntity player, MobEntity entity, double speed) { public AttackPlayerGoal(ServerPlayerEntity player, MobEntity entity, double speed) {
this.targetPlayer = player; this.targetPlayer = player;
...@@ -30,54 +38,38 @@ public class AttackPlayerGoal extends Goal { ...@@ -30,54 +38,38 @@ public class AttackPlayerGoal extends Goal {
this.speed = speed; this.speed = speed;
this.navigation = entity.getNavigation(); this.navigation = entity.getNavigation();
this.setControls(EnumSet.of(Control.MOVE, Control.LOOK)); this.setControls(EnumSet.of(Control.MOVE, Control.LOOK));
this.aggressionTimer = 0; // Initialize the timer used to control aggression intervals.
} }
@Override @Override
public boolean canStart() { public boolean canStart() {
// Can start showing aggression if the player is within a certain range. // Can start showing aggression if the player is within a certain range.
double squaredDistanceToPlayer = this.entity.squaredDistanceTo(this.targetPlayer); return this.entity.squaredDistanceTo(this.targetPlayer) < MOVE_DISTANCE;
return squaredDistanceToPlayer < 25; // Example range: within 5 blocks.
} }
@Override @Override
public boolean shouldContinue() { public boolean shouldContinue() {
// Continue showing aggression as long as the player is alive and within range. // Continue showing aggression as long as the player is alive and within range.
return this.targetPlayer.isAlive() && this.entity.squaredDistanceTo(this.targetPlayer) < 25; return this.targetPlayer.isAlive() && this.entity.squaredDistanceTo(this.targetPlayer) < MOVE_DISTANCE;
} }
@Override @Override
public void stop() { public void stop() {
this.navigation.stop();
this.aggressionTimer = 0; // Reset the aggression timer.
} }
@Override private void performAttack() {
public void tick() { // Check if the entity is a type that is capable of attacking
this.entity.getLookControl().lookAt(this.targetPlayer, 30.0F, 30.0F); // Make the entity face the player if (this.entity instanceof HostileEntity || this.entity instanceof Angerable || this.entity instanceof RangedAttackMob) {
double squaredDistanceToPlayer = this.entity.squaredDistanceTo(this.targetPlayer); // Entity attacks the player
// Check if the entity is close enough to 'attack'
if (squaredDistanceToPlayer < 3.0D) { // Using a smaller range for direct attacks
if (--this.aggressionTimer <= 0) {
this.aggressionTimer = 20; // Reset the timer; 'attack' every second
if (this.entity instanceof HostileEntity ||
this.entity instanceof Angerable ||
this.entity instanceof RangedAttackMob) {
// Entity is capable of attacking
this.entity.tryAttack(this.targetPlayer); this.entity.tryAttack(this.targetPlayer);
} else { } else {
// For passive entities, apply minimal damage and simulate a 'leap' // For passive entities, apply minimal damage to simulate a 'leap' attack
this.targetPlayer.damage(this.entity.getDamageSources().generic(), 1.0F); this.targetPlayer.damage(this.entity.getDamageSources().generic(), 1.0F);
// Leap towards the player to simulate the 'attack' // Play damage sound
Vec3d leapDirection = new Vec3d(this.targetPlayer.getX() - this.entity.getX(), 0.0D, this.targetPlayer.getZ() - this.entity.getZ()).normalize().multiply(0.5); this.targetPlayer.playSound(SoundEvents.ENTITY_PLAYER_HURT, 1F, 1F);
this.entity.setVelocity(leapDirection);
this.entity.velocityModified = true;
// Spawn red particles to simulate 'injury' // Spawn red particles to simulate 'injury'
((ServerWorld)this.entity.getWorld()).spawnParticles(ParticleTypes.DAMAGE_INDICATOR, ((ServerWorld) this.entity.getWorld()).spawnParticles(ParticleTypes.DAMAGE_INDICATOR,
this.targetPlayer.getX(), this.targetPlayer.getX(),
this.targetPlayer.getBodyY(0.5D), this.targetPlayer.getBodyY(0.5D),
this.targetPlayer.getZ(), this.targetPlayer.getZ(),
...@@ -85,10 +77,64 @@ public class AttackPlayerGoal extends Goal { ...@@ -85,10 +77,64 @@ public class AttackPlayerGoal extends Goal {
0.1, 0.1, 0.1, 0.2); // speed and randomness 0.1, 0.1, 0.1, 0.2); // speed and randomness
} }
} }
@Override
public void tick() {
double squaredDistanceToPlayer = this.entity.squaredDistanceTo(this.targetPlayer);
this.entity.getLookControl().lookAt(this.targetPlayer, 30.0F, 30.0F); // Entity faces the player
// State transitions and actions
switch (currentState) {
case IDLE:
cooldownTimer = CHARGE_TIME;
if (squaredDistanceToPlayer < ATTACK_DISTANCE) {
currentState = EntityState.ATTACKING;
} else if (squaredDistanceToPlayer < CHARGE_DISTANCE) {
currentState = EntityState.CHARGING;
} else if (squaredDistanceToPlayer < MOVE_DISTANCE) {
currentState = EntityState.MOVING_TOWARDS_PLAYER;
}
break;
case MOVING_TOWARDS_PLAYER:
this.entity.getNavigation().startMovingTo(this.targetPlayer, this.speed);
if (squaredDistanceToPlayer < CHARGE_DISTANCE) {
currentState = EntityState.CHARGING;
} else { } else {
// Move towards the player if not in attack range currentState = EntityState.IDLE;
this.navigation.startMovingTo(this.targetPlayer, this.speed); }
break;
case CHARGING:
this.entity.getNavigation().startMovingTo(this.targetPlayer, this.speed / 2D);
if (cooldownTimer <= 0) {
currentState = EntityState.LEAPING;
}
break;
case LEAPING:
// Leap towards the player
Vec3d leapDirection = new Vec3d(this.targetPlayer.getX() - this.entity.getX(), 0.1D, this.targetPlayer.getZ() - this.entity.getZ()).normalize().multiply(1.0);
this.entity.setVelocity(leapDirection);
this.entity.velocityModified = true;
currentState = EntityState.ATTACKING;
break;
case ATTACKING:
// Attack player
this.entity.getNavigation().startMovingTo(this.targetPlayer, this.speed / 2D);
if (squaredDistanceToPlayer < ATTACK_DISTANCE && cooldownTimer <= 0) {
this.performAttack();
currentState = EntityState.IDLE;
} else if (cooldownTimer <= 0) {
currentState = EntityState.IDLE;
}
break;
} }
// decrement cool down
cooldownTimer--;
} }
} }
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