diff --git a/CHANGELOG.md b/CHANGELOG.md index 971b95f6..ec6235b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ -**This slates the first official release of the new PlayerEX. It had been stable for a while, although the UI was not fully completed.** +## PlayerEX Is Now Officially Released :) + +**This slates the first official release of the new PlayerEX. It had been stable for a while, although the UI was not fully completed until now.** ## Additions 🍎 - Finished the PlayerEX UI to the point where it is ready for release. - There is now a component set on the right of the UI that has a list of current pages. Use it in order to navigate between multiple mods who use the registry. - - Completely fleshed out the UI with meters and other metrics such as current health and lung capacity along with experience levels to keep track of what you currently have, updated based on your current state. \ No newline at end of file + - Completely fleshed out the UI with meters and other metrics such as current health and lung capacity along with experience levels to keep track of what you currently have, updated based on your current state. +- Added Weapon & Armor Leveling (created by `naomieow` [much appreciated]) + - Items and weapons in loot tables can be generated with a level. + - Added configuration related to armor and weapon leveling. + - The range they can generate in is different based on dimension. + - Non-Vanilla dimensions follow the overworld range. + - Weapon XP increases by a value specified in the config when a mob is killed. + - Thank you `RedstoneWizard08` for contributing to this system! \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index ff66ead5..08c6ce1f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,7 +17,7 @@ parchment_version=1.20.1:2023.09.03 quilt_mappings_version=23 # Mod Properties -mod_version=4.0.0+1.20.1-beta.12 +mod_version=4.0.0+1.20.1 maven_group=com.bibireden.playerex archives_base_name=playerex-directors-cut 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 9e0765f7..46bf15a1 100644 --- a/src/main/java/com/bibireden/playerex/mixin/LivingEntityMixin.java +++ b/src/main/java/com/bibireden/playerex/mixin/LivingEntityMixin.java @@ -7,8 +7,9 @@ import net.minecraft.world.InteractionHand; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; +import net.minecraft.world.entity.player.Player; +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; @@ -70,4 +71,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 793338bf..2c2cc4a9 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 @@ -79,18 +81,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 99e4a61a..ac054928 100644 --- a/src/main/resources/assets/playerex/lang/en_us.json +++ b/src/main/resources/assets/playerex/lang/en_us.json @@ -75,6 +75,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", @@ -86,6 +87,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", @@ -208,5 +228,27 @@ "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.maxReduction": "Max % Damage Reduction", + "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