minecraft-src/net/minecraft/world/damagesource/CombatTracker.java
2025-07-04 03:45:38 +03:00

164 lines
6.5 KiB
Java

package net.minecraft.world.damagesource;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.Objects;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentUtils;
import net.minecraft.network.chat.HoverEvent;
import net.minecraft.network.chat.Style;
import net.minecraft.tags.DamageTypeTags;
import net.minecraft.util.CommonLinks;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
public class CombatTracker {
public static final int RESET_DAMAGE_STATUS_TIME = 100;
public static final int RESET_COMBAT_STATUS_TIME = 300;
private static final Style INTENTIONAL_GAME_DESIGN_STYLE = Style.EMPTY
.withClickEvent(new ClickEvent.OpenUrl(CommonLinks.INTENTIONAL_GAME_DESIGN_BUG))
.withHoverEvent(new HoverEvent.ShowText(Component.literal("MCPE-28723")));
private final List<CombatEntry> entries = Lists.<CombatEntry>newArrayList();
private final LivingEntity mob;
private int lastDamageTime;
private int combatStartTime;
private int combatEndTime;
private boolean inCombat;
private boolean takingDamage;
public CombatTracker(LivingEntity mob) {
this.mob = mob;
}
public void recordDamage(DamageSource source, float damage) {
this.recheckStatus();
FallLocation fallLocation = FallLocation.getCurrentFallLocation(this.mob);
CombatEntry combatEntry = new CombatEntry(source, damage, fallLocation, (float)this.mob.fallDistance);
this.entries.add(combatEntry);
this.lastDamageTime = this.mob.tickCount;
this.takingDamage = true;
if (!this.inCombat && this.mob.isAlive() && shouldEnterCombat(source)) {
this.inCombat = true;
this.combatStartTime = this.mob.tickCount;
this.combatEndTime = this.combatStartTime;
this.mob.onEnterCombat();
}
}
private static boolean shouldEnterCombat(DamageSource source) {
return source.getEntity() instanceof LivingEntity;
}
private Component getMessageForAssistedFall(Entity entity, Component entityDisplayName, String hasWeaponTranslationKey, String noWeaponTranslationKey) {
ItemStack itemStack = entity instanceof LivingEntity livingEntity ? livingEntity.getMainHandItem() : ItemStack.EMPTY;
return !itemStack.isEmpty() && itemStack.has(DataComponents.CUSTOM_NAME)
? Component.translatable(hasWeaponTranslationKey, this.mob.getDisplayName(), entityDisplayName, itemStack.getDisplayName())
: Component.translatable(noWeaponTranslationKey, this.mob.getDisplayName(), entityDisplayName);
}
private Component getFallMessage(CombatEntry combatEntry, @Nullable Entity entity) {
DamageSource damageSource = combatEntry.source();
if (!damageSource.is(DamageTypeTags.IS_FALL) && !damageSource.is(DamageTypeTags.ALWAYS_MOST_SIGNIFICANT_FALL)) {
Component component = getDisplayName(entity);
Entity entity2 = damageSource.getEntity();
Component component2 = getDisplayName(entity2);
if (component2 != null && !component2.equals(component)) {
return this.getMessageForAssistedFall(entity2, component2, "death.fell.assist.item", "death.fell.assist");
} else {
return (Component)(component != null
? this.getMessageForAssistedFall(entity, component, "death.fell.finish.item", "death.fell.finish")
: Component.translatable("death.fell.killer", this.mob.getDisplayName()));
}
} else {
FallLocation fallLocation = (FallLocation)Objects.requireNonNullElse(combatEntry.fallLocation(), FallLocation.GENERIC);
return Component.translatable(fallLocation.languageKey(), this.mob.getDisplayName());
}
}
@Nullable
private static Component getDisplayName(@Nullable Entity entity) {
return entity == null ? null : entity.getDisplayName();
}
public Component getDeathMessage() {
if (this.entries.isEmpty()) {
return Component.translatable("death.attack.generic", this.mob.getDisplayName());
} else {
CombatEntry combatEntry = (CombatEntry)this.entries.get(this.entries.size() - 1);
DamageSource damageSource = combatEntry.source();
CombatEntry combatEntry2 = this.getMostSignificantFall();
DeathMessageType deathMessageType = damageSource.type().deathMessageType();
if (deathMessageType == DeathMessageType.FALL_VARIANTS && combatEntry2 != null) {
return this.getFallMessage(combatEntry2, damageSource.getEntity());
} else if (deathMessageType == DeathMessageType.INTENTIONAL_GAME_DESIGN) {
String string = "death.attack." + damageSource.getMsgId();
Component component = ComponentUtils.wrapInSquareBrackets(Component.translatable(string + ".link")).withStyle(INTENTIONAL_GAME_DESIGN_STYLE);
return Component.translatable(string + ".message", this.mob.getDisplayName(), component);
} else {
return damageSource.getLocalizedDeathMessage(this.mob);
}
}
}
@Nullable
private CombatEntry getMostSignificantFall() {
CombatEntry combatEntry = null;
CombatEntry combatEntry2 = null;
float f = 0.0F;
float g = 0.0F;
for (int i = 0; i < this.entries.size(); i++) {
CombatEntry combatEntry3 = (CombatEntry)this.entries.get(i);
CombatEntry combatEntry4 = i > 0 ? (CombatEntry)this.entries.get(i - 1) : null;
DamageSource damageSource = combatEntry3.source();
boolean bl = damageSource.is(DamageTypeTags.ALWAYS_MOST_SIGNIFICANT_FALL);
float h = bl ? Float.MAX_VALUE : combatEntry3.fallDistance();
if ((damageSource.is(DamageTypeTags.IS_FALL) || bl) && h > 0.0F && (combatEntry == null || h > g)) {
if (i > 0) {
combatEntry = combatEntry4;
} else {
combatEntry = combatEntry3;
}
g = h;
}
if (combatEntry3.fallLocation() != null && (combatEntry2 == null || combatEntry3.damage() > f)) {
combatEntry2 = combatEntry3;
f = combatEntry3.damage();
}
}
if (g > 5.0F && combatEntry != null) {
return combatEntry;
} else {
return f > 5.0F && combatEntry2 != null ? combatEntry2 : null;
}
}
public int getCombatDuration() {
return this.inCombat ? this.mob.tickCount - this.combatStartTime : this.combatEndTime - this.combatStartTime;
}
/**
* Resets this trackers list of combat entries
*/
public void recheckStatus() {
int i = this.inCombat ? 300 : 100;
if (this.takingDamage && (!this.mob.isAlive() || this.mob.tickCount - this.lastDamageTime > i)) {
boolean bl = this.inCombat;
this.takingDamage = false;
this.inCombat = false;
this.combatEndTime = this.mob.tickCount;
if (bl) {
this.mob.onLeaveCombat();
}
this.entries.clear();
}
}
}