diff --git a/src/main/java/com/bibireden/playerex/mixin/ItemStackMixin.java b/src/main/java/com/bibireden/playerex/mixin/ItemStackMixin.java index b0258fec..a7364e35 100644 --- a/src/main/java/com/bibireden/playerex/mixin/ItemStackMixin.java +++ b/src/main/java/com/bibireden/playerex/mixin/ItemStackMixin.java @@ -19,10 +19,7 @@ import net.minecraft.world.InteractionResultHolder; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.ai.attributes.Attribute; -import net.minecraft.world.entity.ai.attributes.AttributeInstance; -import net.minecraft.world.entity.ai.attributes.AttributeModifier; -import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.ai.attributes.*; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; @@ -39,10 +36,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.function.Consumer; @Mixin(ItemStack.class) @@ -50,11 +44,14 @@ abstract class ItemStackMixin { @Shadow public abstract boolean hurt(int amount, RandomSource random, @Nullable ServerPlayer serverPlayer); + @Shadow + public abstract CompoundTag getOrCreateTag(); + @Inject(method = "use(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/InteractionHand;)Lnet/minecraft/world/InteractionResultHolder;", at = @At(value = "HEAD"), cancellable = true) public void preventUse(Level level, Player player, InteractionHand usedHand, CallbackInfoReturnable> cir) { if (!PlayerEX.CONFIG.getFeatureSettings().getItemBreakingEnabled()) return; - ItemStack stack = (ItemStack)(Object)this; + ItemStack stack = (ItemStack) (Object) this; if (PlayerEXUtil.isBroken(stack)) { cir.setReturnValue(InteractionResultHolder.fail(stack)); } @@ -64,7 +61,7 @@ public void preventUse(Level level, Player player, InteractionHand usedHand, Cal public void preventUseOnBlock(UseOnContext context, CallbackInfoReturnable cir) { if (!PlayerEX.CONFIG.getFeatureSettings().getItemBreakingEnabled()) return; - ItemStack stack = (ItemStack)(Object)this; + ItemStack stack = (ItemStack) (Object) this; if (PlayerEXUtil.isBroken(stack)) { cir.setReturnValue(InteractionResult.FAIL); } @@ -74,7 +71,7 @@ public void preventUseOnBlock(UseOnContext context, CallbackInfoReturnable cir) { if (!PlayerEX.CONFIG.getFeatureSettings().getItemBreakingEnabled()) return; - ItemStack stack = (ItemStack)(Object)this; + ItemStack stack = (ItemStack) (Object) this; if (PlayerEXUtil.isBroken(stack)) { cir.setReturnValue(true); } @@ -83,10 +80,10 @@ public void preventDamage(int amount, RandomSource random, ServerPlayer user, Ca @Inject(method = "hurtAndBreak(ILnet/minecraft/world/entity/LivingEntity;Ljava/util/function/Consumer;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;shrink(I)V"), cancellable = true) public void preventBreak(int amount, T entity, Consumer onBroken, CallbackInfo ci) { if (!PlayerEX.CONFIG.getFeatureSettings().getItemBreakingEnabled()) return; - ItemStack stack = (ItemStack)(Object)this; - if (stack.getItem().builtInRegistryHolder().is(PlayerEXTags.UNBREAKABLE_ITEMS)) { + ItemStack stack = (ItemStack) (Object) this; + if (stack.getItemHolder().is(PlayerEXTags.UNBREAKABLE_ITEMS)) { if (!PlayerEXUtil.isBroken(stack)) { - CompoundTag tag = stack.getTag(); + CompoundTag tag = stack.getOrCreateTag(); tag.putBoolean("broken", true); stack.setTag(tag); } @@ -98,20 +95,26 @@ public void preventBreak(int amount, T entity, Consumer public void removeBrokenOnRepair(int damage, CallbackInfo ci) { if (!PlayerEX.CONFIG.getFeatureSettings().getItemBreakingEnabled()) return; - ItemStack stack = (ItemStack)(Object)this; + ItemStack stack = (ItemStack) (Object) this; if (PlayerEXUtil.isBroken(stack) && damage < stack.getDamageValue()) { - CompoundTag tag = stack.getTag(); + CompoundTag tag = stack.getOrCreateTag(); tag.putBoolean("broken", false); stack.setTag(tag); } } @Inject(method = "getAttributeModifiers(Lnet/minecraft/world/entity/EquipmentSlot;)Lcom/google/common/collect/Multimap;", at = @At(value = "RETURN"), cancellable = true) - public void preventArmour(EquipmentSlot slot, CallbackInfoReturnable> cir) { + public void modifyAttributeModifiers(EquipmentSlot slot, CallbackInfoReturnable> cir) { if (!PlayerEX.CONFIG.getFeatureSettings().getItemBreakingEnabled()) return; - ItemStack stack = (ItemStack)(Object)this; + ItemStack stack = (ItemStack) (Object) this; HashMultimap hashmap = HashMultimap.create(cir.getReturnValue()); + if (PlayerEX.CONFIG.getWeaponLevelingSettings().getEnabled() && PlayerEXUtil.isWeapon(stack)) { + PlayerEXUtil.addToModifier(hashmap, Attributes.ATTACK_DAMAGE, getLevel() * PlayerEX.CONFIG.getWeaponLevelingSettings().getDamagePerLevel()); + } + if (PlayerEX.CONFIG.getArmorLevelingSettings().getEnabled() && PlayerEXUtil.isArmor(stack)) { + PlayerEXUtil.addToModifier(hashmap, Attributes.ARMOR, getLevel() * PlayerEX.CONFIG.getArmorLevelingSettings().getArmorPerLevel()); + } if (PlayerEXUtil.isBroken(stack)) { PlayerEXUtil.removeModifier(hashmap, Attributes.ARMOR); PlayerEXUtil.removeModifier(hashmap, Attributes.ARMOR_TOUGHNESS); @@ -166,7 +169,9 @@ public void preventArmour(EquipmentSlot slot, CallbackInfoReturnable> info, + @Local List list + ) { + ItemStack itemStack = (ItemStack) (Object) this; + if (PlayerEXUtil.isLevelable(itemStack)) { + list.add(Component.translatable("playerex.item.level", getLevel(), PlayerEXUtil.getMaxLevel(itemStack))); + list.add(Component.translatable("playerex.item.experience", getXp(), PlayerEXUtil.getRequiredXpForNextLevel(itemStack))); + } + } + + @Inject(method = "getTooltipLines", at = @At(value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z", ordinal = 6, shift = At.Shift.AFTER)) + private void playerex$insertReductionTooltip( + Player player, TooltipFlag context, + CallbackInfoReturnable> info, + @Local List list + ) { + ItemStack itemStack = (ItemStack) (Object) this; + if (PlayerEXUtil.isArmor(itemStack)) { + list.add(Component.translatable("playerex.item.reduction", String.format("%.2f", getReduction()))); + } + } } diff --git a/src/main/java/com/bibireden/playerex/mixin/LivingEntityMixin.java b/src/main/java/com/bibireden/playerex/mixin/LivingEntityMixin.java index 21c39417..bfb24989 100644 --- a/src/main/java/com/bibireden/playerex/mixin/LivingEntityMixin.java +++ b/src/main/java/com/bibireden/playerex/mixin/LivingEntityMixin.java @@ -7,6 +7,7 @@ import net.minecraft.world.InteractionHand; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -66,4 +67,19 @@ public void preventAttack(InteractionHand hand, CallbackInfo ci) { private boolean playerex$damage(boolean original, DamageSource source, float damage) { return LivingEntityEvents.SHOULD_DAMAGE.invoker().shouldDamage((LivingEntity) (Object) this, source, damage); } + + @ModifyReturnValue( + method = "getDamageAfterArmorAbsorb(Lnet/minecraft/world/damagesource/DamageSource;F)F", + at = @At("RETURN") + ) + public float applyDamageReduction(float original) { + LivingEntity self = (LivingEntity) (Object) this; + var total = 0d; + for (ItemStack slot : self.getArmorSlots()) { + total += Math.min(slot.getOrCreateTag().getInt("Level") * PlayerEX.CONFIG.getArmorLevelingSettings().getReductionPerLevel(), PlayerEX.CONFIG.getArmorLevelingSettings().getMaxReduction()); + } + // 400 because 4 slots then percentage to decimal + total /= 400; + return (float) (original * (1 - total)); + } } diff --git a/src/main/kotlin/com/bibireden/playerex/PlayerEX.kt b/src/main/kotlin/com/bibireden/playerex/PlayerEX.kt index 5f8a2e9e..53522cac 100644 --- a/src/main/kotlin/com/bibireden/playerex/PlayerEX.kt +++ b/src/main/kotlin/com/bibireden/playerex/PlayerEX.kt @@ -12,7 +12,6 @@ import com.bibireden.playerex.api.event.LivingEntityEvents import com.bibireden.playerex.api.event.PlayerEXSoundEvents import com.bibireden.playerex.api.event.PlayerEntityEvents import com.bibireden.playerex.config.PlayerEXConfig -import com.bibireden.playerex.config.PlayerEXConfigModel import com.bibireden.playerex.config.PlayerEXConfigModel.Lifecycle import com.bibireden.playerex.ext.component import com.bibireden.playerex.factory.* @@ -20,11 +19,14 @@ import com.bibireden.playerex.networking.NetworkingChannels import com.bibireden.playerex.networking.NetworkingPackets import com.bibireden.playerex.networking.registerServerbound import com.bibireden.playerex.networking.types.UpdatePacketType +import com.bibireden.playerex.predicate.FilterCheck import de.dafuqs.additionalentityattributes.AdditionalEntityAttributes import eu.pb4.placeholders.api.Placeholders import net.fabricmc.api.ModInitializer import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback +import net.fabricmc.fabric.api.entity.event.v1.ServerEntityCombatEvents import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents +import net.fabricmc.fabric.api.loot.v2.LootTableEvents import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents import net.minecraft.core.Registry import net.minecraft.core.registries.BuiltInRegistries @@ -82,18 +84,24 @@ object PlayerEX : ModInitializer { LivingEntityEvents.ON_DAMAGE.register(EventFactory::onDamage) LivingEntityEvents.SHOULD_DAMAGE.register(EventFactory::shouldDamage) + LootTableEvents.MODIFY.register(EventFactory::onModifyLootTable) + PlayerEntityEvents.ON_CRITICAL.register(EventFactory::onCritAttack) PlayerEntityEvents.SHOULD_CRITICAL.register(EventFactory::attackIsCrit) DamageFactory.forEach(PlayerEXAPI::registerDamageModification) RefundFactory.forEach(PlayerEXAPI::registerRefundCondition) + ServerEntityCombatEvents.AFTER_KILLED_OTHER_ENTITY.register(EventFactory::entityWasKilled) + PlaceholderFactory.STORE.forEach(Placeholders::register) Registry.register(BuiltInRegistries.SOUND_EVENT, PlayerEXSoundEvents.LEVEL_UP_SOUND.location, PlayerEXSoundEvents.LEVEL_UP_SOUND) Registry.register(BuiltInRegistries.SOUND_EVENT, PlayerEXSoundEvents.SPEND_SOUND.location, PlayerEXSoundEvents.SPEND_SOUND) Registry.register(BuiltInRegistries.SOUND_EVENT, PlayerEXSoundEvents.REFUND_SOUND.location, PlayerEXSoundEvents.REFUND_SOUND) + Registry.register(BuiltInRegistries.LOOT_FUNCTION_TYPE, id("filtered"), FilterCheck.type()) + EntityAttributeModifiedEvents.MODIFIED.register { attribute, entity, _, _, _ -> if (entity?.level() == null) return@register // no entity & no world, skip diff --git a/src/main/kotlin/com/bibireden/playerex/api/PlayerEXTags.kt b/src/main/kotlin/com/bibireden/playerex/api/PlayerEXTags.kt index 1742af33..7e109687 100644 --- a/src/main/kotlin/com/bibireden/playerex/api/PlayerEXTags.kt +++ b/src/main/kotlin/com/bibireden/playerex/api/PlayerEXTags.kt @@ -8,4 +8,10 @@ import net.minecraft.world.item.Item object PlayerEXTags { @JvmField val UNBREAKABLE_ITEMS: TagKey = TagKey.create(Registries.ITEM, PlayerEX.id("unbreakable")) -} \ No newline at end of file + + @JvmField + val WEAPONS: TagKey = TagKey.create(Registries.ITEM, PlayerEX.id("weapons")) + + @JvmField + val ARMOR: TagKey = TagKey.create(Registries.ITEM, PlayerEX.id("armor")) +} diff --git a/src/main/kotlin/com/bibireden/playerex/config/PlayerEXConfigModel.kt b/src/main/kotlin/com/bibireden/playerex/config/PlayerEXConfigModel.kt index 00e3294c..e4724215 100644 --- a/src/main/kotlin/com/bibireden/playerex/config/PlayerEXConfigModel.kt +++ b/src/main/kotlin/com/bibireden/playerex/config/PlayerEXConfigModel.kt @@ -16,6 +16,8 @@ class PlayerEXConfigModel { @JvmField @Nest @Expanded var featureSettings = FeatureSettings() @JvmField @Nest @Expanded var lifecycleSettings = LifecycleSettings() @JvmField @Nest @Expanded var advancedSettings = AdvancedSettings() + @JvmField @Nest @Expanded var weaponLevelingSettings = WeaponXpSettings() + @JvmField @Nest @Expanded var armorLevelingSettings = ArmorXpSettings() @SectionHeader("client_options") @@ -23,6 +25,84 @@ class PlayerEXConfigModel { @JvmField @Nest @Expanded var soundSettings = SoundSettings() // SERVER + + data class ArmorXpSettings( + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + var enabled: Boolean = true, + + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + var armorPerLevel: Double = 0.1, + + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + var reductionPerLevel: Double = 0.1, + + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + @RangeConstraint(min = 0.0, max = 25.0) + var maxReduction: Double = 25.0, + + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + var formula: String = "5x^(1.1)", + + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + var xpFromPassive: Int = 10, + + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + var xpFromHostile: Int = 20, + + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + var xpFromMiniboss: Int = 50, + + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + var xpFromBoss: Int = 100, + + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + var maxLevel: Int = 500, + ) + + data class WeaponXpSettings( + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + var enabled: Boolean = true, + + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + var damagePerLevel: Double = 0.1, + + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + var formula: String = "5x^(1.1)", + + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + var xpFromPassive: Int = 10, + + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + var xpFromHostile: Int = 20, + + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + var xpFromMiniboss: Int = 50, + + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + var xpFromBoss: Int = 100, + + @Sync(SyncMode.OVERRIDE_CLIENT) + @JvmField + var maxLevel: Int = 500, + ) + data class LevelingSettings( @Sync(SyncMode.OVERRIDE_CLIENT) @JvmField diff --git a/src/main/kotlin/com/bibireden/playerex/ext/ItemStack.kt b/src/main/kotlin/com/bibireden/playerex/ext/ItemStack.kt new file mode 100644 index 00000000..d6ca1c96 --- /dev/null +++ b/src/main/kotlin/com/bibireden/playerex/ext/ItemStack.kt @@ -0,0 +1,11 @@ +package com.bibireden.playerex.ext + +import net.minecraft.world.item.ItemStack + +var ItemStack.level: Int + get() = this.orCreateTag.getInt("Level") + set(value) = this.orCreateTag.putInt("Level", value) + +var ItemStack.xp: Int + get() = this.orCreateTag.getInt("Experience") + set(value) = this.orCreateTag.putInt("Experience", value) \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/playerex/factory/EventFactory.kt b/src/main/kotlin/com/bibireden/playerex/factory/EventFactory.kt index 78a9a292..fc2196e7 100644 --- a/src/main/kotlin/com/bibireden/playerex/factory/EventFactory.kt +++ b/src/main/kotlin/com/bibireden/playerex/factory/EventFactory.kt @@ -4,16 +4,28 @@ import com.bibireden.data_attributes.api.DataAttributesAPI import com.bibireden.playerex.PlayerEX import com.bibireden.playerex.api.attribute.PlayerEXAttributes import com.bibireden.playerex.components.player.PlayerDataComponent +import com.bibireden.playerex.config.PlayerEXConfig import com.bibireden.playerex.ext.component +import com.bibireden.playerex.predicate.FilterCheck import com.bibireden.playerex.registry.DamageModificationRegistry +import com.bibireden.playerex.util.PlayerEXUtil +import net.fabricmc.fabric.api.loot.v2.LootTableSource +import net.fabricmc.fabric.api.tag.convention.v1.ConventionalEntityTypeTags import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer +import net.minecraft.server.packs.resources.ResourceManager import net.minecraft.world.damagesource.DamageSource import net.minecraft.world.effect.MobEffects import net.minecraft.world.entity.Entity import net.minecraft.world.entity.LivingEntity +import net.minecraft.world.entity.MobCategory import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.projectile.AbstractArrow +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.Level +import net.minecraft.world.level.storage.loot.LootDataManager +import net.minecraft.world.level.storage.loot.LootTable object EventFactory { fun reset(oldPlayer: ServerPlayer, newPlayer: ServerPlayer, isAlive: Boolean) @@ -102,4 +114,75 @@ object EventFactory { return original } -} \ No newline at end of file + fun entityWasKilled(level: Level, entity: Entity, killedEntity: Entity) { + when (entity) { + is Player -> { + val mainHandItem: ItemStack = entity.mainHandItem + tryLevelItem(mainHandItem, PlayerEX.CONFIG.weaponLevelingSettings, killedEntity) + + for (item in entity.armorSlots) { + tryLevelItem(item, PlayerEX.CONFIG.armorLevelingSettings, killedEntity) + } + } + } + } + + private fun tryLevelItem(item: ItemStack, xpSettings: PlayerEXConfig.ArmorXpSettings, killedEntity: Entity) { + tryLevelItem(item, + xpSettings.enabled, + xpSettings.xpFromPassive, + xpSettings.xpFromBoss, + xpSettings.xpFromHostile, + xpSettings.maxLevel, + killedEntity + ) + } + + private fun tryLevelItem(item: ItemStack, xpSettings: PlayerEXConfig.WeaponXpSettings, killedEntity: Entity) { + tryLevelItem(item, + xpSettings.enabled, + xpSettings.xpFromPassive, + xpSettings.xpFromBoss, + xpSettings.xpFromHostile, + xpSettings.maxLevel, + killedEntity + ) + } + + private fun tryLevelItem( + item: ItemStack, + enabled: Boolean, + xpFromPassive: Int, + xpFromBoss: Int, + xpFromHostile: Int, + maxLevel: Int, + killedEntity: Entity + ) { + if (PlayerEXUtil.isLevelable(item) && enabled) { + PlayerEXUtil.levelItem(item, if (killedEntity.type.category.isFriendly) { + xpFromPassive + } else if (killedEntity.type.category == MobCategory.MONSTER) { + if (killedEntity.type.`is`(ConventionalEntityTypeTags.BOSSES)) { + xpFromBoss + } else { + xpFromHostile + } + } else { + 0 + }, maxLevel) + } + } + + fun onModifyLootTable( + resourceManager: ResourceManager, + lootManager: LootDataManager, + id: ResourceLocation, + builder: LootTable.Builder, + source: LootTableSource + ) { + + builder.apply { + FilterCheck() + } + } +} diff --git a/src/main/kotlin/com/bibireden/playerex/predicate/FilterCheck.kt b/src/main/kotlin/com/bibireden/playerex/predicate/FilterCheck.kt new file mode 100644 index 00000000..fc530c4a --- /dev/null +++ b/src/main/kotlin/com/bibireden/playerex/predicate/FilterCheck.kt @@ -0,0 +1,63 @@ +package com.bibireden.playerex.predicate + +import com.bibireden.playerex.api.PlayerEXTags +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonObject +import com.google.gson.JsonSerializationContext +import net.minecraft.nbt.CompoundTag +import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.Level +import net.minecraft.world.level.storage.loot.LootContext +import net.minecraft.world.level.storage.loot.Serializer +import net.minecraft.world.level.storage.loot.functions.LootItemFunction +import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType +import kotlin.random.Random + +class FilterCheck : LootItemFunction { + override fun getType(): LootItemFunctionType? { + return type() + } + + override fun apply( + t: ItemStack?, + u: LootContext? + ): ItemStack? { + + val num = when (u?.level?.dimension()) { + Level.NETHER -> Random.nextInt(36, 71) + Level.END -> Random.nextInt(71, 105) + else -> Random.nextInt(1, 36) + } + + var tag = CompoundTag() + tag.putInt("Level", num) + + if(t?.`is`(PlayerEXTags.WEAPONS)!! || t.`is`(PlayerEXTags.ARMOR)) { + t.orCreateTag.merge(tag) + } + + return t + } + + class FilterSerializer : Serializer { + override fun serialize( + json: JsonObject, + value: FilterCheck, + serializationContext: JsonSerializationContext + ) { + } + + override fun deserialize( + json: JsonObject, + serializationContext: JsonDeserializationContext + ): FilterCheck? { + return FilterCheck() + } + } + + companion object { + fun type(): LootItemFunctionType { + return LootItemFunctionType(FilterSerializer()) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/bibireden/playerex/util/PlayerEXUtil.kt b/src/main/kotlin/com/bibireden/playerex/util/PlayerEXUtil.kt index fb188c69..69afed4f 100644 --- a/src/main/kotlin/com/bibireden/playerex/util/PlayerEXUtil.kt +++ b/src/main/kotlin/com/bibireden/playerex/util/PlayerEXUtil.kt @@ -2,7 +2,9 @@ package com.bibireden.playerex.util import com.bibireden.data_attributes.api.util.Maths import com.bibireden.playerex.PlayerEX +import com.bibireden.playerex.api.PlayerEXTags import com.bibireden.playerex.ext.level +import com.bibireden.playerex.ext.xp import com.google.common.collect.Multimap import net.minecraft.world.entity.ai.attributes.Attribute import net.minecraft.world.entity.ai.attributes.AttributeModifier @@ -33,6 +35,21 @@ object PlayerEXUtil { return ExpressionBuilder(PlayerEX.CONFIG.levelingSettings.levelFormula).variable(VARIABLE).function(STAIRCASE_FUNCTION).build() } + private val weaponExpression: Expression + get() = createWeaponExpression() + + @JvmStatic + private fun createWeaponExpression(): Expression { + return ExpressionBuilder(PlayerEX.CONFIG.weaponLevelingSettings.formula).variable(VARIABLE).build() + } + private val armorExpression: Expression + get() = createArmorExpression() + + @JvmStatic + private fun createArmorExpression(): Expression { + return ExpressionBuilder(PlayerEX.CONFIG.armorLevelingSettings.formula).variable(VARIABLE).build() + } + @JvmStatic /** Computes the experience cost of the provided level. */ fun getRequiredXpForLevel(player: Player, target: Double): Int { @@ -52,6 +69,31 @@ object PlayerEXUtil { @JvmStatic fun getRequiredXpForNextLevel(player: Player): Int = getRequiredXpForLevel(player, player.level + 1) + @JvmStatic + fun getRequiredXpForLevel(item: ItemStack, target: Int): Int { + if (target <= item.level) return 0 + var acc = 0 + for (x in item.level+1..target) { + when { + isWeapon(item) -> acc += abs(round(weaponExpression.setVariable(VARIABLE, (x).toDouble()).evaluate())).toInt() + isArmor(item) -> acc += abs(round(armorExpression.setVariable(VARIABLE, (x).toDouble()).evaluate())).toInt() + } + } + return acc + } + + @JvmStatic + fun getRequiredXpForNextLevel(item: ItemStack): Int = getRequiredXpForLevel(item, item.level + 1) + + @JvmStatic + fun getMaxLevel(item: ItemStack): Int { + return when { + isWeapon(item) -> PlayerEX.CONFIG.weaponLevelingSettings.maxLevel + isArmor(item) -> PlayerEX.CONFIG.armorLevelingSettings.maxLevel + else -> 0 + } + } + @JvmStatic fun isBroken(stack: ItemStack): Boolean { if (stack.tag != null) { @@ -60,6 +102,16 @@ object PlayerEXUtil { return false } + @JvmStatic + fun isWeapon(stack: ItemStack): Boolean { + return stack.`is`(PlayerEXTags.WEAPONS) + } + + @JvmStatic + fun isArmor(stack: ItemStack): Boolean { + return stack.`is`(PlayerEXTags.ARMOR) + } + @JvmStatic /** Removes an [Attribute], [AttributeModifier] pair from the provided [Multimap] **/ fun removeModifier(multimap: Multimap, attribute: Attribute) { @@ -71,4 +123,35 @@ object PlayerEXUtil { multimap.put(attribute, newModifier) } } + + @JvmStatic + /** Adds a [Double] to the value of an [AttributeModifier] from the provided [Multimap] **/ + fun addToModifier(multimap: Multimap, attribute: Attribute, amount: Double) { + val optional = multimap[attribute].stream().findFirst() + if (optional.isPresent) { + val modifier = optional.get() + val newModifier = AttributeModifier(modifier.id, modifier.name, modifier.amount + amount, modifier.operation) + multimap.remove(attribute, modifier) + multimap.put(attribute, newModifier) + } + } + + @JvmStatic + fun isLevelable(stack: ItemStack): Boolean { + return (isWeapon(stack) && PlayerEX.CONFIG.weaponLevelingSettings.enabled) + || (isArmor(stack) && PlayerEX.CONFIG.armorLevelingSettings.enabled) + } + + @JvmStatic + fun levelItem(itemStack: ItemStack, xpToAdd: Int, maxLevel: Int) { + if (isLevelable(itemStack)) { + var nextLevel = getRequiredXpForNextLevel(itemStack) + itemStack.xp += xpToAdd + while (itemStack.xp >= nextLevel && itemStack.level < maxLevel) { + itemStack.xp -= nextLevel + itemStack.level += 1 + nextLevel = getRequiredXpForNextLevel(itemStack) + } + } + } } diff --git a/src/main/resources/assets/playerex/lang/en_us.json b/src/main/resources/assets/playerex/lang/en_us.json index 9f103e90..19526597 100644 --- a/src/main/resources/assets/playerex/lang/en_us.json +++ b/src/main/resources/assets/playerex/lang/en_us.json @@ -65,6 +65,7 @@ "attribute.name.playerex.wither_resistance": "Wither Resistance", "attribute.name.playerex.breaking_speed": "Breaking Speed", "attribute.name.playerex.focus": "Focus", + "attribute.name.playerex.percent_damage_reduction": "% Damage Reduction", "attribute.name.playerex.mining": "Mining", "attribute.name.playerex.enchanting": "Enchanting", @@ -76,6 +77,25 @@ "playerex.broken": "Broken", + "playerex.item.level": [ + {"text": "Level: ", "color": "#6EBAE5"}, + {"index": 0, "color": "gold"}, + {"text": "/", "color": "gold"}, + {"index": 1, "color": "gold"} + ], + + "playerex.item.experience": [ + {"text": "Exp: ", "color": "#6EBAE5"}, + {"index": 0, "color": "gold"}, + {"text": "/", "color": "gold"}, + {"index": 1, "color": "gold"} + ], + + "playerex.item.reduction": [ + {"index": 0, "color": "blue"}, + {"text": "% Damage Reduction", "color": "blue"} + ], + "playerex.command.reset_chunk": "Reset experience negation factor for chunk at %s", "playerex.command.refund": "Refunded %s skill points for player %s", @@ -198,5 +218,26 @@ "text.config.playerex-config.category.soundSettings": "Sound Options", "text.config.playerex-config.option.soundSettings.levelUpVolume": "Level Up Volume", "text.config.playerex-config.option.soundSettings.skillUpVolume": "Skill Up Volume", - "text.config.playerex-config.option.soundSettings.refundVolume": "Refund Volume" + "text.config.playerex-config.option.soundSettings.refundVolume": "Refund Volume", + + "text.config.playerex-config.category.weaponLevelingSettings": "Weapon Leveling", + "text.config.playerex-config.option.weaponLevelingSettings.enabled": "Activated", + "text.config.playerex-config.option.weaponLevelingSettings.damagePerLevel": "Attack Damage per Level", + "text.config.playerex-config.option.weaponLevelingSettings.formula": "XP Formula", + "text.config.playerex-config.option.weaponLevelingSettings.xpFromPassive": "XP from Passive Mobs", + "text.config.playerex-config.option.weaponLevelingSettings.xpFromHostile": "XP from Hostile Mobs", + "text.config.playerex-config.option.weaponLevelingSettings.xpFromMiniboss": "XP from Minibosses (unused)", + "text.config.playerex-config.option.weaponLevelingSettings.xpFromBoss": "XP from Bosses", + "text.config.playerex-config.option.weaponLevelingSettings.maxLevel": "Maximum Level", + + "text.config.playerex-config.category.armorLevelingSettings": "Armor Leveling", + "text.config.playerex-config.option.armorLevelingSettings.enabled": "Activated", + "text.config.playerex-config.option.armorLevelingSettings.armorPerLevel": "Armor per Level", + "text.config.playerex-config.option.armorLevelingSettings.reductionPerLevel": "% Damage Reduction per Level", + "text.config.playerex-config.option.armorLevelingSettings.formula": "XP Formula", + "text.config.playerex-config.option.armorLevelingSettings.xpFromPassive": "XP from Passive Mobs", + "text.config.playerex-config.option.armorLevelingSettings.xpFromHostile": "XP from Hostile Mobs", + "text.config.playerex-config.option.armorLevelingSettings.xpFromMiniboss": "XP from Minibosses (unused)", + "text.config.playerex-config.option.armorLevelingSettings.xpFromBoss": "XP from Bosses", + "text.config.playerex-config.option.armorLevelingSettings.maxLevel": "Maximum Level" } \ No newline at end of file diff --git a/src/main/resources/data/playerex/tags/items/armor.json b/src/main/resources/data/playerex/tags/items/armor.json new file mode 100644 index 00000000..da34e96f --- /dev/null +++ b/src/main/resources/data/playerex/tags/items/armor.json @@ -0,0 +1,30 @@ +{ + "replace": false, + "values": [ + "minecraft:leather_helmet", + "minecraft:leather_chestplate", + "minecraft:leather_leggings", + "minecraft:leather_boots", + "minecraft:chainmail_helmet", + "minecraft:chainmail_chestplate", + "minecraft:chainmail_leggings", + "minecraft:chainmail_boots", + "minecraft:iron_helmet", + "minecraft:iron_chestplate", + "minecraft:iron_leggings", + "minecraft:iron_boots", + "minecraft:golden_helmet", + "minecraft:golden_chestplate", + "minecraft:golden_leggings", + "minecraft:golden_boots", + "minecraft:diamond_helmet", + "minecraft:diamond_chestplate", + "minecraft:diamond_leggings", + "minecraft:diamond_boots", + "minecraft:netherite_helmet", + "minecraft:netherite_chestplate", + "minecraft:netherite_leggings", + "minecraft:netherite_boots", + "minecraft:turtle_helmet" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/playerex/tags/items/weapons.json b/src/main/resources/data/playerex/tags/items/weapons.json new file mode 100644 index 00000000..587cfc5d --- /dev/null +++ b/src/main/resources/data/playerex/tags/items/weapons.json @@ -0,0 +1,29 @@ +{ + "replace": false, + "values": [ + { + "id": "#c:swords", + "required": false + }, + { + "id": "#c:axes", + "required": false + }, + { + "id": "#c:spears", + "required": false + }, + { + "id": "#c:shields", + "required": false + }, + { + "id": "#c:bows", + "required": false + }, + "#minecraft:swords", + "#minecraft:axes", + "minecraft:bow", + "minecraft:trident" + ] +} diff --git a/src/main/resources/playerex.mixins.json b/src/main/resources/playerex.mixins.json index 04dfb44c..52aa7db6 100644 --- a/src/main/resources/playerex.mixins.json +++ b/src/main/resources/playerex.mixins.json @@ -6,10 +6,10 @@ "AbstractArrowMixin", "ExperienceOrbMixin", "InventoryMixin", + "ItemStackMixin", "LivingEntityMixin", "PlayerMixin", - "ServerPlayerMixin", - "ItemStackMixin" + "ServerPlayerMixin" ], "injectors": { "defaultRequire": 1