Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Weapon/Armor Leveling System (#23) #49

Merged
85 changes: 66 additions & 19 deletions src/main/java/com/bibireden/playerex/mixin/ItemStackMixin.java
naomieow marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -39,22 +36,22 @@
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)
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<InteractionResultHolder<ItemStack>> 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));
}
Expand All @@ -64,7 +61,7 @@ public void preventUse(Level level, Player player, InteractionHand usedHand, Cal
public void preventUseOnBlock(UseOnContext context, CallbackInfoReturnable<InteractionResult> 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);
}
Expand All @@ -74,7 +71,7 @@ public void preventUseOnBlock(UseOnContext context, CallbackInfoReturnable<Inter
public void preventDamage(int amount, RandomSource random, ServerPlayer user, CallbackInfoReturnable<Boolean> cir) {
if (!PlayerEX.CONFIG.getFeatureSettings().getItemBreakingEnabled()) return;

ItemStack stack = (ItemStack)(Object)this;
ItemStack stack = (ItemStack) (Object) this;
if (PlayerEXUtil.isBroken(stack)) {
cir.setReturnValue(true);
}
Expand All @@ -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 <T extends LivingEntity> void preventBreak(int amount, T entity, Consumer<T> 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);
}
Expand All @@ -98,20 +95,26 @@ public <T extends LivingEntity> 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<Multimap<Attribute, AttributeModifier>> cir) {
public void modifyAttributeModifiers(EquipmentSlot slot, CallbackInfoReturnable<Multimap<Attribute, AttributeModifier>> cir) {
if (!PlayerEX.CONFIG.getFeatureSettings().getItemBreakingEnabled()) return;

ItemStack stack = (ItemStack)(Object)this;
ItemStack stack = (ItemStack) (Object) this;
HashMultimap<Attribute, AttributeModifier> 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);
Expand Down Expand Up @@ -166,7 +169,9 @@ public void preventArmour(EquipmentSlot slot, CallbackInfoReturnable<Multimap<At
}

@ModifyVariable(method = "getTooltipLines", at = @At(value = "STORE", ordinal = 1), ordinal = 1)
private double playerex$modifyAdditionAttributeKnockback(double original) { return original / 10.0; }
private double playerex$modifyAdditionAttributeKnockback(double original) {
return original / 10.0;
}

// todo: not sure about the implementation(s) here...
@Inject(method = "getTooltipLines", at = @At(value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z", ordinal = 7, shift = At.Shift.AFTER))
Expand Down Expand Up @@ -237,4 +242,46 @@ public void preventArmour(EquipmentSlot slot, CallbackInfoReturnable<Multimap<At
);
}
}

@Unique
private int getLevel() {
ItemStack itemStack = (ItemStack) (Object) this;
return itemStack.getOrCreateTag().getInt("Level");
}

@Unique
private int getXp() {
ItemStack itemStack = (ItemStack) (Object) this;
return itemStack.getOrCreateTag().getInt("Experience");
}

@Unique
private double getReduction() {
return Math.min(getLevel() * PlayerEX.CONFIG.getArmorLevelingSettings().getReductionPerLevel(), PlayerEX.CONFIG.getArmorLevelingSettings().getMaxReduction());
}

@Inject(method = "getTooltipLines", at = @At(value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z", ordinal = 0, shift = At.Shift.AFTER))
private void playerex$insertLevelTooltip(
Player player, TooltipFlag context,
CallbackInfoReturnable<List<Component>> info,
@Local List<Component> 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<List<Component>> info,
@Local List<Component> list
) {
ItemStack itemStack = (ItemStack) (Object) this;
if (PlayerEXUtil.isArmor(itemStack)) {
list.add(Component.translatable("playerex.item.reduction", String.format("%.2f", getReduction())));
}
}
}
16 changes: 16 additions & 0 deletions src/main/java/com/bibireden/playerex/mixin/LivingEntityMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
}
}
10 changes: 9 additions & 1 deletion src/main/kotlin/com/bibireden/playerex/PlayerEX.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@ 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.*
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
Expand Down Expand Up @@ -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

Expand Down
8 changes: 7 additions & 1 deletion src/main/kotlin/com/bibireden/playerex/api/PlayerEXTags.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,10 @@ import net.minecraft.world.item.Item
object PlayerEXTags {
@JvmField
val UNBREAKABLE_ITEMS: TagKey<Item> = TagKey.create(Registries.ITEM, PlayerEX.id("unbreakable"))
}

@JvmField
val WEAPONS: TagKey<Item> = TagKey.create(Registries.ITEM, PlayerEX.id("weapons"))

@JvmField
val ARMOR: TagKey<Item> = TagKey.create(Registries.ITEM, PlayerEX.id("armor"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,93 @@ 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")

@JvmField @Nest @Expanded var visualSettings = VisualSettings()
@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
Expand Down
11 changes: 11 additions & 0 deletions src/main/kotlin/com/bibireden/playerex/ext/ItemStack.kt
Original file line number Diff line number Diff line change
@@ -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)
Loading