J'essaie de faire fonctionner une animation d'attaque pour cette entité, j'ai suivi des tutoriels sur YouTube, consulté la documentation de Geckolib mais je ne trouve pas pourquoi ça ne fonctionne pas. L'animation de marche fonctionne, le mob reconnaît le joueur et l'attaque. Le modèle et les animations ont été réalisés dans Blockbench.
public class RedSlimeEntity extends TensuraTamableEntity implements IAnimatable {
private final AnimationFactory factory = GeckoLibUtil.createFactory(this);
private boolean swinging;
private long lastAttackTime;
public RedSlimeEntity(EntityType<? extends RedSlimeEntity> type, Level worldIn) {
super(type, worldIn);
this.xpReward = 20;
}
public static AttributeSupplier.Builder createAttributes() {
AttributeSupplier.Builder builder = Mob.createMobAttributes();
builder = builder.add(Attributes.MOVEMENT_SPEED, 0.1);
builder = builder.add(Attributes.MAX_HEALTH, 50);
builder = builder.add(Attributes.ARMOR, 0);
builder = builder.add(Attributes.ATTACK_DAMAGE, 25);
builder = builder.add(Attributes.FOLLOW_RANGE, 16);
return builder;
}
public static void init() {
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(3, new FloatGoal(this));
this.goalSelector.addGoal(1, new RedSlimeAttackGoal(this, 1.2D, false));
this.goalSelector.addGoal(4, new WaterAvoidingRandomStrollGoal(this, 1.0D));
this.goalSelector.addGoal(5, new RandomLookAroundGoal(this));
this.goalSelector.addGoal(2, new RedSlimeAttackGoal.StopNearPlayerGoal(this, 1));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
}
private <E extends IAnimatable> PlayState predicate(AnimationEvent<E> event) {
if (event.isMoving()) {
event.getController().setAnimation(new AnimationBuilder().addAnimation("animation.model.walk", true));
return PlayState.CONTINUE;
}
event.getController().setAnimation(new AnimationBuilder().addAnimation("animation.model.idle", true));
return PlayState.CONTINUE;
}
private <E extends IAnimatable> PlayState attackPredicate(AnimationEvent<E> event) {
if (this.swinging && event.getController().getAnimationState() == AnimationState.Stopped) {
event.getController().setAnimation(new AnimationBuilder().addAnimation("animation.model.attack", false));
this.swinging = false;
return PlayState.CONTINUE;
}
return PlayState.STOP;
}
@Override public void swing(InteractionHand hand, boolean updateSelf) {
super.swing(hand, updateSelf);
this.swinging = true;
}
@Override
public void registerControllers(AnimationData data) {
data.addAnimationController(new AnimationController<>(this, "controller", 0, this:redicate));
data.addAnimationController(new AnimationController<>(this, "attackController", 0, this::attackPredicate));
}
@Override
public AnimationFactory getFactory() {
return factory;
}
class RedSlimeAttackGoal extends MeleeAttackGoal {
private final RedSlimeEntity entity;
public RedSlimeAttackGoal(RedSlimeEntity entity, double speedModifier, boolean longMemory) {
super(entity, speedModifier, longMemory);
this.entity = entity;
if (this.mob.getTarget() != null && this.mob.getTarget().isAlive()) {
long currentTime = this.entity.level.getGameTime();
if (!this.entity.swinging && currentTime - this.entity.lastAttackTime > 20) { // 20 ticks = 1 second
this.entity.swinging = true;
this.entity.lastAttackTime = currentTime;
}
}
}
protected double getAttackReach(LivingEntity target) {
return this.mob.getBbWidth() * 2.0F * this.mob.getBbWidth() * 2.0F + target.getBbWidth();
}
@Override
protected void checkAndPerformAttack(LivingEntity target, double distToEnt) {
double reach = this.getAttackReach(target);
if (distToEnt <= reach && this.getTicksUntilNextAttack() <= 0) {
this.resetAttackCooldown();
this.entity.swinging = true;
this.mob.doHurtTarget(target);
}
}
public static class StopNearPlayerGoal extends Goal {
private final Mob mob;
private final double stopDistance;
public StopNearPlayerGoal(Mob mob, double stopDistance) {
this.mob = mob;
this.stopDistance = stopDistance;
}
@Override
public boolean canUse() {
Player nearestPlayer = this.mob.level.getNearestPlayer(this.mob, stopDistance);
if (nearestPlayer != null) {
double distanceSquared = this.mob.distanceToSqr(nearestPlayer);
return distanceSquared < (stopDistance * stopDistance);
}
return false;
}
@Override
public void tick() {
// Stop movement
this.mob.getNavigation().stop();
}
@Override
public boolean canContinueToUse() {
Player nearestPlayer = this.mob.level.getNearestPlayer(this.mob, stopDistance);
if (nearestPlayer != null) {
double distanceSquared = this.mob.distanceToSqr(nearestPlayer);
return distanceSquared < (stopDistance * stopDistance);
}
return false;
}
}
@Override
public void tick() {
super.tick();
if (this.mob.getTarget() != null && this.mob.getTarget().isAlive()) {
if (!this.entity.swinging) {
this.entity.swinging = true;
}
}
}
}
@Override
public @Nullable AgeableMob getBreedOffspring(ServerLevel serverLevel, AgeableMob ageableMob) {
return null;
}
@Override
public int getRemainingPersistentAngerTime() {
return 0;
}
@Override
public void setRemainingPersistentAngerTime(int i) {
}
@Override
public @Nullable UUID getPersistentAngerTarget() {
return null;
}
@Override
public void setPersistentAngerTarget(@Nullable UUID uuid) {
}
@Override
public void startPersistentAngerTimer() {
}
protected void playStepSound(BlockPos pos, BlockState blockIn) {
this.playSound(SoundEvents.SLIME_SQUISH, 0.15F, 1.0F);
}
protected SoundEvent getAmbientSound() {
return SoundEvents.SLIME_SQUISH;
}
protected SoundEvent getHurtSound(DamageSource damageSourceIn) {
return SoundEvents.SLIME_HURT;
}
protected SoundEvent getDeathSound() {
return SoundEvents.SLIME_DEATH;
}
protected float getSoundVolume() {
return 0.2F;
}
}