Add combat tracker API (#11853)

This commit is contained in:
Illia Bondar
2025-04-30 20:24:33 +03:00
committed by GitHub
parent 646b80ca53
commit e663f99982
12 changed files with 546 additions and 0 deletions

View File

@@ -1,10 +1,21 @@
package io.papermc.paper;
import io.papermc.paper.world.damagesource.CombatEntry;
import io.papermc.paper.world.damagesource.FallLocationType;
import io.papermc.paper.world.damagesource.PaperCombatEntryWrapper;
import io.papermc.paper.world.damagesource.PaperCombatTrackerWrapper;
import net.minecraft.Optionull;
import net.minecraft.world.damagesource.FallLocation;
import org.bukkit.block.Biome;
import org.bukkit.craftbukkit.block.CraftBiome;
import org.bukkit.craftbukkit.damage.CraftDamageEffect;
import org.bukkit.craftbukkit.damage.CraftDamageSource;
import org.bukkit.craftbukkit.entity.CraftLivingEntity;
import org.bukkit.damage.DamageEffect;
import org.bukkit.damage.DamageSource;
import org.bukkit.entity.LivingEntity;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class PaperServerInternalAPIBridge implements InternalAPIBridge {
@@ -22,4 +33,42 @@ public class PaperServerInternalAPIBridge implements InternalAPIBridge {
}
return Holder.LEGACY_CUSTOM;
}
@Override
public CombatEntry createCombatEntry(final LivingEntity entity, final DamageSource damageSource, final float damage) {
final net.minecraft.world.entity.LivingEntity mob = ((CraftLivingEntity) entity).getHandle();
final FallLocation fallLocation = FallLocation.getCurrentFallLocation(mob);
return createCombatEntry(
((CraftDamageSource) damageSource).getHandle(),
damage,
fallLocation,
(float) mob.fallDistance
);
}
@Override
public CombatEntry createCombatEntry(
final DamageSource damageSource,
final float damage,
@Nullable final FallLocationType fallLocationType,
final float fallDistance
) {
return createCombatEntry(
((CraftDamageSource) damageSource).getHandle(),
damage,
Optionull.map(fallLocationType, PaperCombatTrackerWrapper::paperToMinecraft),
fallDistance
);
}
private CombatEntry createCombatEntry(
final net.minecraft.world.damagesource.DamageSource damageSource,
final float damage,
final net.minecraft.world.damagesource.@Nullable FallLocation fallLocation,
final float fallDistance
) {
return new PaperCombatEntryWrapper(new net.minecraft.world.damagesource.CombatEntry(
damageSource, damage, fallLocation, fallDistance
));
}
}

View File

@@ -0,0 +1,32 @@
package io.papermc.paper.world.damagesource;
import net.minecraft.Optionull;
import org.bukkit.craftbukkit.damage.CraftDamageSource;
import org.bukkit.damage.DamageSource;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public record PaperCombatEntryWrapper(net.minecraft.world.damagesource.CombatEntry handle) implements CombatEntry {
@Override
public DamageSource getDamageSource() {
return new CraftDamageSource(this.handle.source());
}
@Override
public float getDamage() {
return this.handle.damage();
}
@Override
public @Nullable FallLocationType getFallLocationType() {
return Optionull.map(this.handle.fallLocation(), PaperCombatTrackerWrapper::minecraftToPaper);
}
@Override
public float getFallDistance() {
return this.handle.fallDistance();
}
}

View File

@@ -0,0 +1,110 @@
package io.papermc.paper.world.damagesource;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import io.papermc.paper.adventure.PaperAdventure;
import java.util.ArrayList;
import java.util.List;
import net.kyori.adventure.text.Component;
import net.minecraft.Util;
import net.minecraft.world.damagesource.FallLocation;
import org.bukkit.entity.LivingEntity;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public record PaperCombatTrackerWrapper(
net.minecraft.world.damagesource.CombatTracker handle
) implements CombatTracker {
@Override
public LivingEntity getEntity() {
return this.handle.mob.getBukkitLivingEntity();
}
@Override
public List<CombatEntry> getCombatEntries() {
final List<CombatEntry> combatEntries = new ArrayList<>(this.handle.entries.size());
this.handle.entries.forEach(combatEntry -> combatEntries.add(new PaperCombatEntryWrapper(combatEntry)));
return combatEntries;
}
@Override
public void setCombatEntries(final List<CombatEntry> combatEntries) {
this.handle.entries.clear();
combatEntries.forEach(combatEntry -> this.handle.entries.add(((PaperCombatEntryWrapper) combatEntry).handle()));
}
@Override
public @Nullable CombatEntry computeMostSignificantFall() {
final net.minecraft.world.damagesource.CombatEntry combatEntry = this.handle.getMostSignificantFall();
return combatEntry == null ? null : new PaperCombatEntryWrapper(combatEntry);
}
@Override
public boolean isInCombat() {
return this.handle.inCombat;
}
@Override
public boolean isTakingDamage() {
return this.handle.takingDamage;
}
@Override
public int getCombatDuration() {
return this.handle.getCombatDuration();
}
@Override
public void addCombatEntry(final CombatEntry combatEntry) {
final net.minecraft.world.damagesource.CombatEntry entry = ((PaperCombatEntryWrapper) combatEntry).handle();
this.handle.recordDamageAndCheckCombatState(entry);
}
@Override
public Component getDeathMessage() {
return PaperAdventure.asAdventure(this.handle.getDeathMessage());
}
@Override
public void resetCombatState() {
this.handle.resetCombatState();
}
@Override
public FallLocationType calculateFallLocationType() {
final FallLocation fallLocation = FallLocation.getCurrentFallLocation(this.handle().mob);
return fallLocation == null ? FallLocationType.GENERIC : PaperCombatTrackerWrapper.minecraftToPaper(fallLocation);
}
private static final BiMap<FallLocation, FallLocationType> FALL_LOCATION_MAPPING = Util.make(() -> {
final BiMap<FallLocation, FallLocationType> map = HashBiMap.create(8);
map.put(FallLocation.GENERIC, FallLocationType.GENERIC);
map.put(FallLocation.LADDER, FallLocationType.LADDER);
map.put(FallLocation.VINES, FallLocationType.VINES);
map.put(FallLocation.WEEPING_VINES, FallLocationType.WEEPING_VINES);
map.put(FallLocation.TWISTING_VINES, FallLocationType.TWISTING_VINES);
map.put(FallLocation.SCAFFOLDING, FallLocationType.SCAFFOLDING);
map.put(FallLocation.OTHER_CLIMBABLE, FallLocationType.OTHER_CLIMBABLE);
map.put(FallLocation.WATER, FallLocationType.WATER);
return map;
});
public static FallLocation paperToMinecraft(final FallLocationType fallLocationType) {
final FallLocation fallLocation = FALL_LOCATION_MAPPING.inverse().get(fallLocationType);
if (fallLocation == null) {
throw new IllegalArgumentException("Unknown fall location type: " + fallLocationType.id());
}
return fallLocation;
}
public static FallLocationType minecraftToPaper(final FallLocation fallLocation) {
final FallLocationType fallLocationType = FALL_LOCATION_MAPPING.get(fallLocation);
if (fallLocationType == null) {
throw new IllegalArgumentException("Unknown fall location: " + fallLocation.id());
}
return fallLocationType;
}
}

View File

@@ -9,6 +9,9 @@ import java.util.List;
import java.util.Set;
import java.util.UUID;
import net.minecraft.Optionull;
import io.papermc.paper.world.damagesource.CombatTracker;
import io.papermc.paper.world.damagesource.PaperCombatTrackerWrapper;
import io.papermc.paper.world.damagesource.FallLocationType;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.protocol.game.ClientboundHurtAnimationPacket;
import net.minecraft.server.level.ServerLevel;
@@ -90,11 +93,15 @@ import org.bukkit.util.RayTraceResult;
import org.bukkit.util.Vector;
public class CraftLivingEntity extends CraftEntity implements LivingEntity {
private final PaperCombatTrackerWrapper combatTracker;
private CraftEntityEquipment equipment;
public CraftLivingEntity(final CraftServer server, final net.minecraft.world.entity.LivingEntity entity) {
super(server, entity);
this.combatTracker = new PaperCombatTrackerWrapper(entity.getCombatTracker());
if (entity instanceof Mob || entity instanceof ArmorStand) {
this.equipment = new CraftEntityEquipment(this);
}
@@ -1167,4 +1174,9 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
public boolean canUseEquipmentSlot(org.bukkit.inventory.EquipmentSlot slot) {
return this.getHandle().canUseSlot(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot));
}
@Override
public CombatTracker getCombatTracker() {
return this.combatTracker;
}
}