diff --git a/src/main/java/net/fretux/ascend/event/ActivityEvents.java b/src/main/java/net/fretux/ascend/event/ActivityEvents.java index 5ae7615..b7023d9 100644 --- a/src/main/java/net/fretux/ascend/event/ActivityEvents.java +++ b/src/main/java/net/fretux/ascend/event/ActivityEvents.java @@ -16,6 +16,8 @@ import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.event.level.BlockEvent; +import net.minecraftforge.event.entity.living.LivingDamageEvent; +import net.minecraft.world.entity.LivingEntity; @Mod.EventBusSubscriber(modid = AscendMod.MODID) public class ActivityEvents { @@ -54,7 +56,7 @@ public class ActivityEvents { public static void onPlayerDamaged(LivingHurtEvent event) { if (event.getEntity() instanceof Player player && !player.level().isClientSide) { player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { - int gain = Math.min((int) event.getAmount() * 2, 10); + int gain = Math.min((int) event.getAmount() / 2, 10); stats.addAscendXP(gain); PlayerStatsProvider.sync(player); }); @@ -203,4 +205,22 @@ public class ActivityEvents { }); } } + + @SubscribeEvent + public static void onPlayerDealsDamage(LivingDamageEvent event) { + if (!(event.getSource().getEntity() instanceof Player player)) return; + if (player.level().isClientSide) return; + LivingEntity target = event.getEntity(); + player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { + int strength = stats.getAttributeLevel("strength"); + if (strength <= 0) return; + float bypassFraction = Math.min(strength * 0.001f, 0.30f); + int armor = target.getArmorValue(); + if (armor <= 0 || bypassFraction <= 0f) return; + float baseDamage = event.getAmount(); + float armorFactor = (float) armor / (armor + 100.0f); + float bonus = baseDamage * armorFactor * bypassFraction; + event.setAmount(baseDamage + bonus); + }); + } } \ No newline at end of file diff --git a/src/main/java/net/fretux/ascend/event/PlayerEvents.java b/src/main/java/net/fretux/ascend/event/PlayerEvents.java index 19ff644..c939f35 100644 --- a/src/main/java/net/fretux/ascend/event/PlayerEvents.java +++ b/src/main/java/net/fretux/ascend/event/PlayerEvents.java @@ -2,6 +2,7 @@ package net.fretux.ascend.event; import net.fretux.ascend.AscendMod; import net.fretux.ascend.player.PlayerStatsProvider; +import net.fretux.ascend.player.StatEffects; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; @@ -27,15 +28,19 @@ public class PlayerEvents { public static void onPlayerClone(PlayerEvent.Clone event) { event.getOriginal().reviveCaps(); event.getOriginal().getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(oldStats -> - event.getEntity().getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(newStats -> - newStats.deserializeNBT(oldStats.serializeNBT()))); + event.getEntity().getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(newStats -> { + newStats.deserializeNBT(oldStats.serializeNBT()); + StatEffects.applyAll(event.getEntity()); + }) + ); } - // Optional but very helpful: sync on login so client sees initial 15 points @SubscribeEvent public static void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) { if (!event.getEntity().level().isClientSide) { PlayerStatsProvider.sync(event.getEntity()); + StatEffects.applyAll(event.getEntity()); } } + } diff --git a/src/main/java/net/fretux/ascend/network/ServerboundSpendPointPacket.java b/src/main/java/net/fretux/ascend/network/ServerboundSpendPointPacket.java index c8a37ec..5a78a94 100644 --- a/src/main/java/net/fretux/ascend/network/ServerboundSpendPointPacket.java +++ b/src/main/java/net/fretux/ascend/network/ServerboundSpendPointPacket.java @@ -1,5 +1,6 @@ package net.fretux.ascend.network; +import net.fretux.ascend.player.StatEffects; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; @@ -50,8 +51,29 @@ public class ServerboundSpendPointPacket { )); } }); + player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { + boolean success = stats.spendPoints(attribute); + if (success) { + if ("strength".equals(attribute)) { + StatEffects.applyStrength(player, stats.getAttributeLevel("strength")); + } + + player.sendSystemMessage(Component.literal("You increased " + attribute + "!")); + CompoundTag data = stats.serializeNBT(); + PacketHandler.INSTANCE.send( + PacketDistributor.PLAYER.with(() -> player), + new ClientboundSyncStatsPacket(data) + ); + } else { + int level = stats.getAttributeLevel(attribute); + int cost = stats.getCostToUpgrade(attribute); + int have = stats.getUnspentPoints(); + player.sendSystemMessage(Component.literal( + "Not enough points (" + have + "/" + cost + ") to increase " + attribute + " (lvl " + level + ")." + )); + } + }); }); ctx.get().setPacketHandled(true); } - } \ No newline at end of file diff --git a/src/main/java/net/fretux/ascend/player/PlayerStats.java b/src/main/java/net/fretux/ascend/player/PlayerStats.java index a76d6ff..ba9c7c5 100644 --- a/src/main/java/net/fretux/ascend/player/PlayerStats.java +++ b/src/main/java/net/fretux/ascend/player/PlayerStats.java @@ -12,6 +12,7 @@ public class PlayerStats { private int ascendLevel = 1; private int ascendXP = 0; private int unspentPoints = POINTS_PER_LEVEL; + public static final int MAX_ATTRIBUTE_POINTS = 100; public PlayerStats() { for (String attr : new String[]{ "strength", "agility", "fortitude", "intelligence", @@ -59,12 +60,18 @@ public class PlayerStats { public void addUnspentPoints(int amount) { unspentPoints += amount; } - + public boolean spendPoints(String attribute) { - if (unspentPoints <= 0) return false; if (!attributes.containsKey(attribute)) return false; - attributes.put(attribute, attributes.get(attribute) + 1); - unspentPoints--; + int level = attributes.get(attribute); + if (level >= MAX_ATTRIBUTE_POINTS) { + return false; + } + if (unspentPoints <= 0) { + return false; + } + unspentPoints -= 1; + attributes.put(attribute, level + 1); return true; } @@ -77,7 +84,9 @@ public class PlayerStats { } public void setAttributeLevel(String attribute, int level) { - attributes.put(attribute, level); + if (!attributes.containsKey(attribute)) return; + int clamped = Math.max(0, Math.min(level, MAX_ATTRIBUTE_POINTS)); + attributes.put(attribute, clamped); } public CompoundTag serializeNBT() { @@ -104,9 +113,8 @@ public class PlayerStats { if (tag.contains("Attributes")) { CompoundTag attrTag = tag.getCompound("Attributes"); for (String key : attributes.keySet()) { - if (attrTag.contains(key)) { - attributes.put(key, attrTag.getInt(key)); - } + int loaded = attrTag.getInt(key); + attributes.put(key, Math.max(0, Math.min(loaded, MAX_ATTRIBUTE_POINTS))); } } } diff --git a/src/main/java/net/fretux/ascend/player/StatEffects.java b/src/main/java/net/fretux/ascend/player/StatEffects.java new file mode 100644 index 0000000..4badbff --- /dev/null +++ b/src/main/java/net/fretux/ascend/player/StatEffects.java @@ -0,0 +1,96 @@ +package net.fretux.ascend.player; + +import net.minecraft.world.entity.player.Player; +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 java.util.UUID; + +public final class StatEffects { + + // ===== Strength UUIDs ===== + private static final UUID STRENGTH_DAMAGE_UUID = + UUID.fromString("a7b8b2c3-9b0d-4e1a-8ee3-21f6a7b2c3d4"); + private static final UUID STRENGTH_KNOCKBACK_UUID = + UUID.fromString("b17f0d74-9cd9-4f90-9ec8-6a1046e2f912"); + private static final UUID AGILITY_SPEED_UUID = + UUID.fromString("c12e9b6f-1d79-4a49-b57f-7f563c10aa11"); + private static final UUID AGILITY_STEALTH_UUID = + UUID.fromString("d4e8a9f2-3b5c-4d0a-9f11-8acb3f0b77e2"); + + private StatEffects() {} + + public static void applyAll(Player player) { + player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { + applyStrength(player, stats.getAttributeLevel("strength")); + applyAgility(player, stats.getAttributeLevel("agility")); + // later: fortitude, etc. + }); + } + + public static void applyStrength(Player player, int strengthLevel) { + AttributeInstance atk = player.getAttribute(Attributes.ATTACK_DAMAGE); + if (atk != null) { + atk.removeModifier(STRENGTH_DAMAGE_UUID); + if (strengthLevel > 0) { + double dmgBonus = strengthLevel * 0.1d; + atk.addTransientModifier(new AttributeModifier( + STRENGTH_DAMAGE_UUID, + "Ascend Strength damage bonus", + dmgBonus, + AttributeModifier.Operation.ADDITION + )); + } + } + + AttributeInstance kb = player.getAttribute(Attributes.ATTACK_KNOCKBACK); + if (kb != null) { + kb.removeModifier(STRENGTH_KNOCKBACK_UUID); + if (strengthLevel > 0) { + double kbBonus = strengthLevel * 0.03d; + kb.addTransientModifier(new AttributeModifier( + STRENGTH_KNOCKBACK_UUID, + "Ascend Strength knockback bonus", + kbBonus, + AttributeModifier.Operation.ADDITION + )); + } + } + } + + public static void applyAgility(Player player, int agilityLevel) { + AttributeInstance move = player.getAttribute(Attributes.MOVEMENT_SPEED); + if (move != null) { + move.removeModifier(AGILITY_SPEED_UUID); + if (agilityLevel > 0) { + double speedBonus = agilityLevel * 0.0005d; + move.addTransientModifier(new AttributeModifier( + AGILITY_SPEED_UUID, + "Ascend Agility speed bonus", + speedBonus, + AttributeModifier.Operation.ADDITION + )); + } + } + AttributeInstance follow = player.getAttribute(Attributes.FOLLOW_RANGE); + if (follow != null) { + follow.removeModifier(AGILITY_STEALTH_UUID); + if (agilityLevel > 0) { + double perPoint = 0.003d; + double maxReduction = 0.5d; + double reduction = Math.min(agilityLevel * perPoint, maxReduction); + + if (reduction > 0.0d) { + double multiplier = 1.0d - reduction; + follow.addTransientModifier(new AttributeModifier( + AGILITY_STEALTH_UUID, + "Ascend Agility stealth bonus", + multiplier - 1.0d, + AttributeModifier.Operation.MULTIPLY_TOTAL + )); + } + } + } + } +}