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 {
EntityBehaviorManager.removeGoal(entity, AttackPlayerGoal.class);
EntityBehaviorManager.addGoal(entity, fleeGoal, GoalPriority.FLEE_PLAYER);
} 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, FollowPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class);
......
......@@ -9,20 +9,28 @@ import net.minecraft.entity.mob.MobEntity;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.math.Vec3d;
import java.util.EnumSet;
/**
* 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 {
private final MobEntity entity;
private ServerPlayerEntity targetPlayer;
private final EntityNavigation navigation;
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) {
this.targetPlayer = player;
......@@ -30,65 +38,103 @@ public class AttackPlayerGoal extends Goal {
this.speed = speed;
this.navigation = entity.getNavigation();
this.setControls(EnumSet.of(Control.MOVE, Control.LOOK));
this.aggressionTimer = 0; // Initialize the timer used to control aggression intervals.
}
@Override
public boolean canStart() {
// Can start showing aggression if the player is within a certain range.
double squaredDistanceToPlayer = this.entity.squaredDistanceTo(this.targetPlayer);
return squaredDistanceToPlayer < 25; // Example range: within 5 blocks.
return this.entity.squaredDistanceTo(this.targetPlayer) < MOVE_DISTANCE;
}
@Override
public boolean shouldContinue() {
// 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
public void stop() {
this.navigation.stop();
this.aggressionTimer = 0; // Reset the aggression timer.
}
private void performAttack() {
// Check if the entity is a type that is capable of attacking
if (this.entity instanceof HostileEntity || this.entity instanceof Angerable || this.entity instanceof RangedAttackMob) {
// Entity attacks the player
this.entity.tryAttack(this.targetPlayer);
} else {
// For passive entities, apply minimal damage to simulate a 'leap' attack
this.targetPlayer.damage(this.entity.getDamageSources().generic(), 1.0F);
// Play damage sound
this.targetPlayer.playSound(SoundEvents.ENTITY_PLAYER_HURT, 1F, 1F);
// Spawn red particles to simulate 'injury'
((ServerWorld) this.entity.getWorld()).spawnParticles(ParticleTypes.DAMAGE_INDICATOR,
this.targetPlayer.getX(),
this.targetPlayer.getBodyY(0.5D),
this.targetPlayer.getZ(),
10, // number of particles
0.1, 0.1, 0.1, 0.2); // speed and randomness
}
}
@Override
public void tick() {
this.entity.getLookControl().lookAt(this.targetPlayer, 30.0F, 30.0F); // Make the entity face the player
double squaredDistanceToPlayer = this.entity.squaredDistanceTo(this.targetPlayer);
this.entity.getLookControl().lookAt(this.targetPlayer, 30.0F, 30.0F); // Entity faces 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
// 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;
if (this.entity instanceof HostileEntity ||
this.entity instanceof Angerable ||
this.entity instanceof RangedAttackMob) {
// Entity is capable of attacking
this.entity.tryAttack(this.targetPlayer);
case MOVING_TOWARDS_PLAYER:
this.entity.getNavigation().startMovingTo(this.targetPlayer, this.speed);
if (squaredDistanceToPlayer < CHARGE_DISTANCE) {
currentState = EntityState.CHARGING;
} else {
// For passive entities, apply minimal damage and simulate a 'leap'
this.targetPlayer.damage(this.entity.getDamageSources().generic(), 1.0F);
// Leap towards the player to simulate the 'attack'
Vec3d leapDirection = new Vec3d(this.targetPlayer.getX() - this.entity.getX(), 0.0D, this.targetPlayer.getZ() - this.entity.getZ()).normalize().multiply(0.5);
this.entity.setVelocity(leapDirection);
this.entity.velocityModified = true;
// Spawn red particles to simulate 'injury'
((ServerWorld)this.entity.getWorld()).spawnParticles(ParticleTypes.DAMAGE_INDICATOR,
this.targetPlayer.getX(),
this.targetPlayer.getBodyY(0.5D),
this.targetPlayer.getZ(),
10, // number of particles
0.1, 0.1, 0.1, 0.2); // speed and randomness
currentState = EntityState.IDLE;
}
}
} else {
// Move towards the player if not in attack range
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