mirror of
https://github.com/FabricMC/fabric.git
synced 2025-03-24 22:11:18 -04:00
Add ServerPlayerEvents.ALLOW_DEATH event (#1394)
Co-authored-by: NinjaPhenix <5214513+NinjaPhenix@users.noreply.github.com> Co-authored-by: liach <7806504+liach@users.noreply.github.com>
This commit is contained in:
parent
4b24d382c1
commit
6e4bae3683
3 changed files with 70 additions and 0 deletions
fabric-entity-events-v1/src
main/java/net/fabricmc/fabric
testmod/java/net/fabricmc/fabric/test/entity/event
|
@ -16,6 +16,8 @@
|
|||
|
||||
package net.fabricmc.fabric.api.entity.event.v1;
|
||||
|
||||
import net.minecraft.entity.LivingEntity;
|
||||
import net.minecraft.entity.damage.DamageSource;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
|
||||
import net.fabricmc.fabric.api.event.Event;
|
||||
|
@ -45,6 +47,31 @@ public final class ServerPlayerEvents {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* An event that is called when a player takes fatal damage.
|
||||
*
|
||||
* <p>Mods can cancel this to keep the player alive.
|
||||
*
|
||||
* <p>Vanilla checks for player health {@code <= 0} each tick (with {@link LivingEntity#isDead()}), and kills if true -
|
||||
* so the player will still die next tick if this event is cancelled. It's assumed that the listener will do
|
||||
* something to prevent this, for example:
|
||||
*
|
||||
* <ul>
|
||||
* <li>a minigame mod teleporting the player into a 'respawn room' and setting their health to 20.0</li>
|
||||
* <li>a mod that changes death mechanics switching the player over to the mod's play-mode, where death doesn't
|
||||
* apply</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static final Event<AllowDeath> ALLOW_DEATH = EventFactory.createArrayBacked(AllowDeath.class, callbacks -> (player, damageSource, damageAmount) -> {
|
||||
for (AllowDeath callback : callbacks) {
|
||||
if (!callback.allowDeath(player, damageSource, damageAmount)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CopyFrom {
|
||||
/**
|
||||
|
@ -69,6 +96,19 @@ public final class ServerPlayerEvents {
|
|||
void afterRespawn(ServerPlayerEntity oldPlayer, ServerPlayerEntity newPlayer, boolean alive);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface AllowDeath {
|
||||
/**
|
||||
* Called when a player takes fatal damage (before totems of undying can take effect).
|
||||
*
|
||||
* @param player the player
|
||||
* @param damageSource the fatal damage damageSource
|
||||
* @param damageAmount the damageAmount of damage that has killed the player
|
||||
* @return true if the death should go ahead, false otherwise.
|
||||
*/
|
||||
boolean allowDeath(ServerPlayerEntity player, DamageSource damageSource, float damageAmount);
|
||||
}
|
||||
|
||||
private ServerPlayerEvents() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,24 +17,40 @@
|
|||
package net.fabricmc.fabric.mixin.entity.event;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.LivingEntity;
|
||||
import net.minecraft.entity.damage.DamageSource;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
|
||||
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityCombatEvents;
|
||||
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
|
||||
|
||||
@Mixin(LivingEntity.class)
|
||||
abstract class LivingEntityMixin extends EntityMixin {
|
||||
@Shadow
|
||||
public abstract boolean isDead();
|
||||
|
||||
@Inject(method = "onDeath", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;onKilledOther(Lnet/minecraft/server/world/ServerWorld;Lnet/minecraft/entity/LivingEntity;)V", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILEXCEPTION)
|
||||
private void onEntityKilledOther(DamageSource source, CallbackInfo ci, Entity attacker) {
|
||||
// FIXME: Cannot use shadowed fields from supermixins - needs a fix so people can use fabric api in a dev environment even though this is fine in this repo and prod.
|
||||
// A temporary fix is to just cast the mixin to LivingEntity and access the world field with a few ugly casts.
|
||||
ServerEntityCombatEvents.AFTER_KILLED_OTHER_ENTITY.invoker().afterKilledOtherEntity((ServerWorld) ((LivingEntity) (Object) this).world, attacker, (LivingEntity) (Object) this);
|
||||
}
|
||||
|
||||
@Redirect(method = "damage", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;isDead()Z", ordinal = 1))
|
||||
boolean beforePlayerKilled(LivingEntity livingEntity, DamageSource source, float amount) {
|
||||
if (livingEntity instanceof ServerPlayerEntity) {
|
||||
return isDead() && ServerPlayerEvents.ALLOW_DEATH.invoker().allowDeath((ServerPlayerEntity) livingEntity, source, amount);
|
||||
}
|
||||
|
||||
return isDead();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,9 @@ package net.fabricmc.fabric.test.entity.event;
|
|||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import net.minecraft.item.Items;
|
||||
import net.minecraft.util.Hand;
|
||||
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityCombatEvents;
|
||||
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
|
||||
|
@ -48,5 +51,16 @@ public final class EntityEventTests implements ModInitializer {
|
|||
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> {
|
||||
LOGGER.info("Respawned {}, [{}, {}]", oldPlayer.getGameProfile().getName(), oldPlayer.getServerWorld().getRegistryKey().getValue(), newPlayer.getServerWorld().getRegistryKey().getValue());
|
||||
});
|
||||
|
||||
ServerPlayerEvents.ALLOW_DEATH.register((player, source, amount) -> {
|
||||
LOGGER.info("{} is going to die to {} damage from {} damage source", player.getGameProfile().getName(), amount, source.getName());
|
||||
|
||||
if (player.getStackInHand(Hand.MAIN_HAND).getItem() == Items.APPLE) {
|
||||
player.setHealth(3.0f);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue