mirror of
https://github.com/Minestom/Minestom.git
synced 2025-02-16 04:11:39 +01:00
feat: LivingEntity fire overhaul (#2122)
* feat: LivingEntity fire overhaul * chore: missed method change
This commit is contained in:
parent
1ce17664c0
commit
da0950329f
@ -1062,18 +1062,6 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
||||
return this.entityMeta.isOnFire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the entity in fire visually.
|
||||
* <p>
|
||||
* WARNING: if you want to apply damage or specify a duration,
|
||||
* see {@link LivingEntity#setFireForDuration(int, TemporalUnit)}.
|
||||
*
|
||||
* @param fire should the entity be set in fire
|
||||
*/
|
||||
public void setOnFire(boolean fire) {
|
||||
this.entityMeta.setOnFire(fire);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if the entity is sneaking.
|
||||
* <p>
|
||||
|
@ -12,7 +12,8 @@ import net.minestom.server.entity.metadata.LivingEntityMeta;
|
||||
import net.minestom.server.event.EventDispatcher;
|
||||
import net.minestom.server.event.entity.EntityDamageEvent;
|
||||
import net.minestom.server.event.entity.EntityDeathEvent;
|
||||
import net.minestom.server.event.entity.EntityFireEvent;
|
||||
import net.minestom.server.event.entity.EntityFireExtinguishEvent;
|
||||
import net.minestom.server.event.entity.EntitySetFireEvent;
|
||||
import net.minestom.server.event.item.EntityEquipEvent;
|
||||
import net.minestom.server.event.item.PickupItemEvent;
|
||||
import net.minestom.server.instance.EntityTracker;
|
||||
@ -61,14 +62,9 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
||||
protected boolean invulnerable;
|
||||
|
||||
/**
|
||||
* Time at which this entity must be extinguished
|
||||
* Ticks until this entity must be extinguished
|
||||
*/
|
||||
private long fireExtinguishTime;
|
||||
|
||||
/**
|
||||
* Period, in ms, between two fire damage applications
|
||||
*/
|
||||
private long fireDamagePeriod = 1000L;
|
||||
private int remainingFireTicks;
|
||||
|
||||
private Team team;
|
||||
|
||||
@ -186,8 +182,9 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
||||
|
||||
@Override
|
||||
public void update(long time) {
|
||||
if (isOnFire() && time > fireExtinguishTime) {
|
||||
setOnFire(false);
|
||||
// Fire
|
||||
if (remainingFireTicks > 0 && --remainingFireTicks == 0) {
|
||||
EventDispatcher.callCancellable(new EntityFireExtinguishEvent(this, true), () -> entityMeta.setOnFire(false));
|
||||
}
|
||||
|
||||
// Items picking
|
||||
@ -272,44 +269,40 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets fire to this entity for a given duration.
|
||||
* Gets the amount of ticks this entity is on fire for.
|
||||
*
|
||||
* @param duration duration in ticks of the effect
|
||||
* @return the remaining duration of fire in ticks, 0 if not on fire
|
||||
*/
|
||||
public void setFireForDuration(int duration) {
|
||||
setFireForDuration(duration, TimeUnit.SERVER_TICK);
|
||||
public int getFireTicks() {
|
||||
return remainingFireTicks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets fire to this entity for a given duration.
|
||||
* Sets this entity on fire for the given ticks.
|
||||
*
|
||||
* @param duration duration of the effect
|
||||
* @param temporalUnit unit used to express the duration
|
||||
* @see #setOnFire(boolean) if you want it to be permanent without any event callback
|
||||
* @param ticks duration of fire in ticks
|
||||
*/
|
||||
public void setFireForDuration(int duration, TemporalUnit temporalUnit) {
|
||||
setFireForDuration(Duration.of(duration, temporalUnit));
|
||||
}
|
||||
public void setFireTicks(int ticks) {
|
||||
int fireTicks = Math.max(0, ticks);
|
||||
if (fireTicks > 0) {
|
||||
EntitySetFireEvent entitySetFireEvent = new EntitySetFireEvent(this, ticks);
|
||||
EventDispatcher.call(entitySetFireEvent);
|
||||
if (entitySetFireEvent.isCancelled()) return;
|
||||
|
||||
/**
|
||||
* Sets fire to this entity for a given duration.
|
||||
*
|
||||
* @param duration duration of the effect
|
||||
* @see #setOnFire(boolean) if you want it to be permanent without any event callback
|
||||
*/
|
||||
public void setFireForDuration(Duration duration) {
|
||||
EntityFireEvent entityFireEvent = new EntityFireEvent(this, duration);
|
||||
|
||||
// Do not start fire event if the fire needs to be removed (< 0 duration)
|
||||
if (duration.toMillis() > 0) {
|
||||
EventDispatcher.callCancellable(entityFireEvent, () -> {
|
||||
final long fireTime = entityFireEvent.getFireTime(TimeUnit.MILLISECOND);
|
||||
setOnFire(true);
|
||||
fireExtinguishTime = System.currentTimeMillis() + fireTime;
|
||||
});
|
||||
} else {
|
||||
fireExtinguishTime = System.currentTimeMillis();
|
||||
fireTicks = Math.max(0, entitySetFireEvent.getFireTicks());
|
||||
if (fireTicks > 0) {
|
||||
remainingFireTicks = fireTicks;
|
||||
entityMeta.setOnFire(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (remainingFireTicks != 0) {
|
||||
EntityFireExtinguishEvent entityFireExtinguishEvent = new EntityFireExtinguishEvent(this, false);
|
||||
EventDispatcher.callCancellable(entityFireExtinguishEvent, () -> entityMeta.setOnFire(false));
|
||||
}
|
||||
|
||||
remainingFireTicks = fireTicks;
|
||||
}
|
||||
|
||||
public boolean damage(@NotNull DynamicRegistry.Key<DamageType> type, float amount) {
|
||||
@ -568,35 +561,6 @@ public class LivingEntity extends Entity implements EquipmentHandler {
|
||||
return new EntityAttributesPacket(getEntityId(), List.copyOf(attributeModifiers.values()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time in ms between two fire damage applications.
|
||||
*
|
||||
* @return the time in ms
|
||||
* @see #setFireDamagePeriod(Duration)
|
||||
*/
|
||||
public long getFireDamagePeriod() {
|
||||
return fireDamagePeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the delay between two fire damage applications.
|
||||
*
|
||||
* @param fireDamagePeriod the delay
|
||||
* @param temporalUnit the time unit
|
||||
*/
|
||||
public void setFireDamagePeriod(long fireDamagePeriod, @NotNull TemporalUnit temporalUnit) {
|
||||
setFireDamagePeriod(Duration.of(fireDamagePeriod, temporalUnit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the delay between two fire damage applications.
|
||||
*
|
||||
* @param fireDamagePeriod the delay
|
||||
*/
|
||||
public void setFireDamagePeriod(Duration fireDamagePeriod) {
|
||||
this.fireDamagePeriod = fireDamagePeriod.toMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the {@link Team} for the entity.
|
||||
*
|
||||
|
@ -519,8 +519,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
||||
if (!isDead())
|
||||
return;
|
||||
|
||||
setFireForDuration(0);
|
||||
setOnFire(false);
|
||||
setFireTicks(0);
|
||||
entityMeta.setOnFire(false);
|
||||
refreshHealth();
|
||||
|
||||
sendPacket(new RespawnPacket(DIMENSION_TYPE_REGISTRY.getId(getDimensionType().namespace()), instance.getDimensionName(),
|
||||
|
@ -1,53 +0,0 @@
|
||||
package net.minestom.server.event.entity;
|
||||
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.event.trait.CancellableEvent;
|
||||
import net.minestom.server.event.trait.EntityInstanceEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.TemporalUnit;
|
||||
|
||||
public class EntityFireEvent implements EntityInstanceEvent, CancellableEvent {
|
||||
|
||||
private final Entity entity;
|
||||
private Duration duration;
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
public EntityFireEvent(Entity entity, int duration, TemporalUnit temporalUnit) {
|
||||
this(entity, Duration.of(duration, temporalUnit));
|
||||
}
|
||||
|
||||
public EntityFireEvent(Entity entity, Duration duration) {
|
||||
this.entity = entity;
|
||||
setFireTime(duration);
|
||||
}
|
||||
|
||||
public long getFireTime(TemporalUnit temporalUnit) {
|
||||
return duration.toNanos() / temporalUnit.getDuration().toNanos();
|
||||
}
|
||||
|
||||
public void setFireTime(int duration, TemporalUnit temporalUnit) {
|
||||
setFireTime(Duration.of(duration, temporalUnit));
|
||||
}
|
||||
|
||||
public void setFireTime(Duration duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Entity getEntity() {
|
||||
return entity;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package net.minestom.server.event.entity;
|
||||
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.event.trait.CancellableEvent;
|
||||
import net.minestom.server.event.trait.EntityInstanceEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class EntityFireExtinguishEvent implements EntityInstanceEvent, CancellableEvent {
|
||||
|
||||
private final Entity entity;
|
||||
private boolean natural;
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
public EntityFireExtinguishEvent(Entity entity, boolean natural) {
|
||||
this.entity = entity;
|
||||
this.natural = natural;
|
||||
}
|
||||
|
||||
public boolean isNatural() {
|
||||
return natural;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Entity getEntity() {
|
||||
return entity;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package net.minestom.server.event.entity;
|
||||
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.event.trait.CancellableEvent;
|
||||
import net.minestom.server.event.trait.EntityInstanceEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class EntitySetFireEvent implements EntityInstanceEvent, CancellableEvent {
|
||||
|
||||
private final Entity entity;
|
||||
private int ticks;
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
public EntitySetFireEvent(Entity entity, int ticks) {
|
||||
this.entity = entity;
|
||||
this.ticks = ticks;
|
||||
}
|
||||
|
||||
public int getFireTicks() {
|
||||
return ticks;
|
||||
}
|
||||
|
||||
public void setFireTicks(int ticks) {
|
||||
this.ticks = ticks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Entity getEntity() {
|
||||
return entity;
|
||||
}
|
||||
}
|
125
src/test/java/net/minestom/server/entity/EntityFireTest.java
Normal file
125
src/test/java/net/minestom/server/entity/EntityFireTest.java
Normal file
@ -0,0 +1,125 @@
|
||||
package net.minestom.server.entity;
|
||||
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.event.entity.EntityFireExtinguishEvent;
|
||||
import net.minestom.server.event.entity.EntitySetFireEvent;
|
||||
import net.minestom.testing.Env;
|
||||
import net.minestom.testing.EnvTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@EnvTest
|
||||
public class EntityFireTest
|
||||
{
|
||||
@Test
|
||||
public void duration(Env env) {
|
||||
var instance = env.createFlatInstance();
|
||||
instance.loadChunk(0, 0).join();
|
||||
|
||||
final int fireTicks = 10;
|
||||
LivingEntity entity = new LivingEntity(EntityType.ZOMBIE);
|
||||
entity.setInstance(instance, new Vec(0, 0, 0));
|
||||
|
||||
entity.setFireTicks(fireTicks);
|
||||
assertTrue(entity.getEntityMeta().isOnFire());
|
||||
|
||||
for (int i = 0; i < fireTicks; i++) {
|
||||
assertTrue(entity.getEntityMeta().isOnFire());
|
||||
assertEquals(fireTicks - i, entity.getFireTicks());
|
||||
entity.tick(0);
|
||||
}
|
||||
|
||||
assertFalse(entity.getEntityMeta().isOnFire());
|
||||
assertEquals(entity.getFireTicks(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonNegativeFireDuration(Env env) {
|
||||
var instance = env.createFlatInstance();
|
||||
instance.loadChunk(0, 0).join();
|
||||
|
||||
LivingEntity entity = new LivingEntity(EntityType.ZOMBIE);
|
||||
entity.setInstance(instance, new Vec(0, 0, 0));
|
||||
|
||||
// Natural fire decay
|
||||
entity.setFireTicks(5);
|
||||
for (int i = 0; i < 20; i++) {
|
||||
assertTrue(entity.getFireTicks() >= 0);
|
||||
}
|
||||
|
||||
// Explicit negative
|
||||
entity.setFireTicks(-1);
|
||||
assertEquals(0, entity.getFireTicks());
|
||||
|
||||
// Explicit negative in event
|
||||
env.listen(EntitySetFireEvent.class).followup(e -> {
|
||||
e.setFireTicks(-1);
|
||||
});
|
||||
|
||||
entity.setFireTicks(1);
|
||||
assertEquals(entity.getFireTicks(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setFireMetadata(Env env) {
|
||||
var instance = env.createFlatInstance();
|
||||
instance.loadChunk(0, 0).join();
|
||||
|
||||
LivingEntity entity = new LivingEntity(EntityType.ZOMBIE);
|
||||
entity.setInstance(instance, new Vec(0, 0, 0));
|
||||
|
||||
// Do not extinguish an entity when they're set on fire explicitly
|
||||
entity.getEntityMeta().setOnFire(true);
|
||||
for (int i = 0; i < 40; i++) {
|
||||
entity.tick(0);
|
||||
assertTrue(entity.getEntityMeta().isOnFire());
|
||||
}
|
||||
|
||||
// Unless setFireTicks has been called to activate the internal remainingFireTicks timer
|
||||
entity.setFireTicks(1);
|
||||
entity.tick(0);
|
||||
assertFalse(entity.isOnFire());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extinguishEvent(Env env) {
|
||||
var instance = env.createFlatInstance();
|
||||
instance.loadChunk(0, 0).join();
|
||||
|
||||
LivingEntity entity = new LivingEntity(EntityType.ZOMBIE);
|
||||
entity.setInstance(instance, new Vec(0, 0, 0));
|
||||
|
||||
AtomicInteger callCount = new AtomicInteger();
|
||||
env.listen(EntityFireExtinguishEvent.class).followup(e -> {
|
||||
callCount.getAndIncrement();
|
||||
if (callCount.get() == 2) assertTrue(e.isNatural());
|
||||
else assertFalse(e.isNatural());
|
||||
});
|
||||
|
||||
// Don't call when the entity is already on fire
|
||||
entity.setFireTicks(0);
|
||||
assertEquals(0, callCount.get());
|
||||
|
||||
// Call now, the entity is set on fire
|
||||
entity.setFireTicks(1);
|
||||
entity.setFireTicks(-1);
|
||||
assertEquals(1, callCount.get());
|
||||
|
||||
// Call naturally
|
||||
entity.setFireTicks(3);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
entity.tick(0);
|
||||
}
|
||||
assertEquals(2, callCount.get());
|
||||
|
||||
// Don't call if cancelled EntitySetFireEvent
|
||||
env.listen(EntitySetFireEvent.class).followup(e -> {
|
||||
e.setCancelled(true);
|
||||
});
|
||||
entity.setFireTicks(5);
|
||||
assertEquals(2, callCount.get());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user