From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Samsuik Date: Fri, 23 Feb 2024 01:48:08 +0000 Subject: [PATCH] Legacy player combat mechanics diff --git a/src/main/java/me/samsuik/sakura/player/combat/CombatUtil.java b/src/main/java/me/samsuik/sakura/player/combat/CombatUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..e0aa768656081fac2c87ff573b61584dc4c1a9a3 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/player/combat/CombatUtil.java @@ -0,0 +1,64 @@ +package me.samsuik.sakura.player.combat; + +import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.*; +import net.minecraft.world.item.component.ItemAttributeModifiers; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.item.enchantment.ItemEnchantments; +import org.apache.commons.lang3.mutable.MutableFloat; + +import java.util.OptionalDouble; + +public final class CombatUtil { + public static double getLegacyAttackDifference(ItemStack itemstack) { + ItemAttributeModifiers defaultModifiers = itemstack.getItem().components().get(DataComponents.ATTRIBUTE_MODIFIERS); + if (defaultModifiers != null && !defaultModifiers.modifiers().isEmpty()) { // exists + double baseAttack = 0.0; + for (ItemAttributeModifiers.Entry entry : defaultModifiers.modifiers()) { + if (!entry.slot().test(EquipmentSlot.MAINHAND) || !entry.attribute().is(Attributes.ATTACK_DAMAGE)) + continue; + if (entry.modifier().operation() != AttributeModifier.Operation.ADD_VALUE) + return 0; + baseAttack += entry.modifier().amount(); + } + + OptionalDouble legacyAttack = LegacyDamageMapping.itemAttackDamage(itemstack.getItem()); + if (baseAttack != 0.0 && legacyAttack.isPresent()) { + return legacyAttack.getAsDouble() - baseAttack; + } + } + return 0; + } + + public static float calculateLegacySharpnessDamage(LivingEntity entity, ItemStack itemstack, DamageSource damageSource) { + Holder enchantment = getEnchantmentHolder(Enchantments.SHARPNESS); + ItemEnchantments itemEnchantments = itemstack.getEnchantments(); + int enchantmentLevel = itemEnchantments.getLevel(enchantment); + MutableFloat damage = new MutableFloat(); + + if (entity.level() instanceof ServerLevel level) { + enchantment.value().modifyDamage(level, enchantmentLevel, itemstack, entity, damageSource, damage); + } + // legacy - modern + return enchantmentLevel * 1.25F - damage.getValue(); + } + + private static Holder getEnchantmentHolder(ResourceKey enchantmentKey) { + RegistryAccess registryAccess = MinecraftServer.getServer().registryAccess(); + HolderLookup.RegistryLookup enchantments = registryAccess.lookupOrThrow(Registries.ENCHANTMENT); + return enchantments.getOrThrow(enchantmentKey); + } +} diff --git a/src/main/java/me/samsuik/sakura/player/combat/LegacyDamageMapping.java b/src/main/java/me/samsuik/sakura/player/combat/LegacyDamageMapping.java new file mode 100644 index 0000000000000000000000000000000000000000..b4ab8f172d713204bb9c1ebf575dcc28bd7dd73e --- /dev/null +++ b/src/main/java/me/samsuik/sakura/player/combat/LegacyDamageMapping.java @@ -0,0 +1,64 @@ +package me.samsuik.sakura.player.combat; + +import it.unimi.dsi.fastutil.objects.Reference2DoubleMap; +import it.unimi.dsi.fastutil.objects.Reference2DoubleOpenHashMap; +import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.*; +import net.minecraft.world.item.component.ItemAttributeModifiers; + +import java.util.OptionalDouble; + +public final class LegacyDamageMapping { + private static final Reference2DoubleMap LEGACY_ITEM_DAMAGE_MAP = new Reference2DoubleOpenHashMap<>(); + + public static OptionalDouble itemAttackDamage(Item item) { + double result = LEGACY_ITEM_DAMAGE_MAP.getDouble(item); + return result == Double.MIN_VALUE ? OptionalDouble.empty() : OptionalDouble.of(result); + } + + private static double adjustDamageForItem(Item item, double attackDamage) { + return switch (item) { + case SwordItem i -> 1.0; + case PickaxeItem i -> 1.0; + case ShovelItem i -> -0.5; + case HoeItem i -> -attackDamage; + case null, default -> 0.0; + }; + } + + static { + LEGACY_ITEM_DAMAGE_MAP.defaultReturnValue(Double.MIN_VALUE); + + // tool material is no longer exposed + LEGACY_ITEM_DAMAGE_MAP.put(Items.WOODEN_AXE, 3.0); + LEGACY_ITEM_DAMAGE_MAP.put(Items.GOLDEN_AXE, 3.0); + LEGACY_ITEM_DAMAGE_MAP.put(Items.STONE_AXE, 4.0); + LEGACY_ITEM_DAMAGE_MAP.put(Items.IRON_AXE, 5.0); + LEGACY_ITEM_DAMAGE_MAP.put(Items.DIAMOND_AXE, 6.0); + LEGACY_ITEM_DAMAGE_MAP.put(Items.NETHERITE_AXE, 7.0); + + for (Item item : BuiltInRegistries.ITEM) { + ItemAttributeModifiers modifiers = item.components().get(DataComponents.ATTRIBUTE_MODIFIERS); + + if (modifiers == null || LEGACY_ITEM_DAMAGE_MAP.containsKey(item)) { + continue; + } + + assert item instanceof AxeItem : "missing axe mapping"; + + double attackDamage = modifiers.modifiers().stream() + .filter(e -> e.attribute().is(Attributes.ATTACK_DAMAGE)) + .mapToDouble(e -> e.modifier().amount()) + .sum(); + + if (attackDamage > 0.0) { + double adjustment = adjustDamageForItem(item, attackDamage); + LEGACY_ITEM_DAMAGE_MAP.put(item, attackDamage + adjustment); + } + } + } + + private LegacyDamageMapping() {} +} diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java index 1bd9f31da1ec66259dc6391448e2b8ce69ddb817..16e6bd46e54cba9e0ef39a488cefcc5e49476403 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -308,6 +308,43 @@ public abstract class LivingEntity extends Entity implements Attackable { ++this.noActionTime; // Above all the floats } // Spigot end + // Sakura start - legacy combat mechanics + private static final ResourceLocation LEGACY_COMBAT_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("sakura", "legacy_combat"); + private static final AttributeModifier LEGACY_ATTACK_SPEED_MODIFIER = new AttributeModifier(LEGACY_COMBAT_MODIFIER_ID, 100.0, AttributeModifier.Operation.ADD_VALUE); + + private void updateAttackSpeedModifier() { + AttributeInstance attackSpeed = this.getAttribute(Attributes.ATTACK_SPEED); + if (attackSpeed != null) { + attackSpeed.removeModifier(LEGACY_ATTACK_SPEED_MODIFIER); + + if (this.level().sakuraConfig().players.combat.legacyCombatMechanics) { + attackSpeed.addTransientModifier(LEGACY_ATTACK_SPEED_MODIFIER); + } + } + } + + protected final float getAttackDamageFromAttributes() { + AttributeInstance attackDamage = this.getAttribute(Attributes.ATTACK_DAMAGE); + AttributeModifier legacyModifier = null; + + if (this.level().sakuraConfig().players.combat.legacyCombatMechanics) { + ItemStack heldItem = this.getLastHandItem(EquipmentSlot.MAINHAND); + double attackDifference = me.samsuik.sakura.player.combat.CombatUtil.getLegacyAttackDifference(heldItem); + legacyModifier = new AttributeModifier(LEGACY_COMBAT_MODIFIER_ID, attackDifference, AttributeModifier.Operation.ADD_VALUE); + } + + final double damage; + if (attackDamage == null || legacyModifier == null) { + damage = this.getAttributeValue(Attributes.ATTACK_DAMAGE); + } else { + attackDamage.addTransientModifier(legacyModifier); + damage = this.getAttributeValue(Attributes.ATTACK_DAMAGE); + attackDamage.removeModifier(legacyModifier); + } + + return (float) damage; + } + // Sakura end - legacy combat mechanics protected LivingEntity(EntityType type, Level world) { super(type, world); @@ -2302,7 +2339,16 @@ public abstract class LivingEntity extends Entity implements Attackable { protected float getDamageAfterArmorAbsorb(DamageSource source, float amount) { if (!source.is(DamageTypeTags.BYPASSES_ARMOR)) { // this.hurtArmor(damagesource, f); // CraftBukkit - actuallyHurt(DamageSource, float, EntityDamageEvent) for handle damage + // Sakura start - legacy combat mechanics + if (!this.level().sakuraConfig().players.combat.legacyCombatMechanics) { amount = CombatRules.getDamageAfterAbsorb(this, amount, source, (float) this.getArmorValue(), (float) this.getAttributeValue(Attributes.ARMOR_TOUGHNESS)); + } else { + // See: applyArmorModifier(DamageSource, float) + int i = 25 - this.getArmorValue(); + float f1 = amount * (float) i; + amount = f1 / 25.0F; + } + // Sakura end - legacy combat mechanics } return amount; @@ -3492,6 +3538,11 @@ public abstract class LivingEntity extends Entity implements Attackable { EnchantmentHelper.runLocationChangedEffects(worldserver, itemstack, this, enumitemslot1); } + // Sakura start - legacy combat mechanics + if (this instanceof ServerPlayer && enumitemslot1 == EquipmentSlot.MAINHAND) { + this.updateAttackSpeedModifier(); + } + // Sakura end - legacy combat mechanics } } } diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java index f14759c18f760165dfad670049c880c01adb96d4..c6e728aec98c1abb55abc7bb31ae3604bd187374 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java @@ -1243,14 +1243,20 @@ public abstract class Player extends LivingEntity { if (playerAttackEntityEvent.callEvent() && willAttack) { // Logic moved to willAttack local variable. { // Paper end - PlayerAttackEntityEvent - float f = this.isAutoSpinAttack() ? this.autoSpinAttackDmg : (float) this.getAttributeValue(Attributes.ATTACK_DAMAGE); + float f = this.isAutoSpinAttack() ? this.autoSpinAttackDmg : this.getAttackDamageFromAttributes(); // Sakura - legacy combat mechanics ItemStack itemstack = this.getWeaponItem(); DamageSource damagesource = (DamageSource) Optional.ofNullable(itemstack.getItem().getDamageSource(this)).orElse(this.damageSources().playerAttack(this)); float f1 = this.getEnchantedDamage(target, f, damagesource) - f; float f2 = this.getAttackStrengthScale(0.5F); + // Sakura start - legacy combat mechanics + if (!this.level().sakuraConfig().players.combat.legacyCombatMechanics) { f *= 0.2F + f2 * f2 * 0.8F; f1 *= f2; + } else if (f1 != 0.0) { + f1 += me.samsuik.sakura.player.combat.CombatUtil.calculateLegacySharpnessDamage(this, itemstack, damagesource); + } + // Sakura end - legacy combat mechanics // this.resetAttackStrengthTicker(); // CraftBukkit - Moved to EntityLiving to reset the cooldown after the damage is dealt if (target.getType().is(EntityTypeTags.REDIRECTABLE_PROJECTILE) && target instanceof Projectile) { Projectile iprojectile = (Projectile) target; @@ -1278,7 +1284,7 @@ public abstract class Player extends LivingEntity { } f += itemstack.getItem().getAttackDamageBonus(target, f, damagesource); - boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround() && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity && !this.isSprinting(); + boolean flag2 = flag && this.fallDistance > 0.0F && !this.onGround() && !this.onClimbable() && !this.isInWater() && !this.hasEffect(MobEffects.BLINDNESS) && !this.isPassenger() && target instanceof LivingEntity && (this.level().sakuraConfig().players.combat.legacyCombatMechanics || !this.isSprinting()); // Sakura - legacy combat mechanics flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits if (flag2) {