diff --git a/src/main/java/net/fretux/ascend/event/FortitudeDefenseEvents.java b/src/main/java/net/fretux/ascend/event/FortitudeDefenseEvents.java new file mode 100644 index 0000000..5d8b5ef --- /dev/null +++ b/src/main/java/net/fretux/ascend/event/FortitudeDefenseEvents.java @@ -0,0 +1,52 @@ +package net.fretux.ascend.event; + +import net.fretux.ascend.AscendMod; +import net.fretux.ascend.player.PlayerStatsProvider; +import net.minecraft.world.effect.MobEffectCategory; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.entity.living.MobEffectEvent; +import net.minecraftforge.eventbus.api.Event; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber(modid = AscendMod.MODID) +public class FortitudeDefenseEvents { + @SubscribeEvent + public static void onHarmfulEffectApplicable(MobEffectEvent.Applicable event) { + if (!(event.getEntity() instanceof Player player)) return; + if (player.level().isClientSide) return; + if (event.getEffectInstance() == null) return; + if (event.getEffectInstance().getEffect().getCategory() != MobEffectCategory.HARMFUL) return; + player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { + int fortitude = stats.getAttributeLevel("fortitude"); + if (fortitude <= 0) return; + double perPoint = 0.004d; + double max = 0.5d; + double chance = Math.min(fortitude * perPoint, max); + if (chance > 0 && player.getRandom().nextDouble() < chance) { + event.setResult(Event.Result.DENY); + } + }); + } + + @SubscribeEvent + public static void onPlayerTickFortitudeCleanse(TickEvent.PlayerTickEvent event) { + Player player = event.player; + if (player.level().isClientSide || event.phase != TickEvent.Phase.END) return; + player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { + int fortitude = stats.getAttributeLevel("fortitude"); + if (fortitude <= 0) return; + if (player.tickCount % 40 != 0) return; + double perPoint = 0.003d; + double max = 0.4d; + double chance = Math.min(fortitude * perPoint, max); + if (chance <= 0) return; + if (player.getRandom().nextDouble() >= chance) return; + player.getActiveEffects().stream() + .filter(inst -> inst.getEffect().getCategory() == MobEffectCategory.HARMFUL) + .findAny() + .ifPresent(inst -> player.removeEffect(inst.getEffect())); + }); + } +} \ No newline at end of file diff --git a/src/main/java/net/fretux/ascend/event/IntelligenceEnchantmentEvents.java b/src/main/java/net/fretux/ascend/event/IntelligenceEnchantmentEvents.java new file mode 100644 index 0000000..aaf2969 --- /dev/null +++ b/src/main/java/net/fretux/ascend/event/IntelligenceEnchantmentEvents.java @@ -0,0 +1,42 @@ +package net.fretux.ascend.event; + +import net.fretux.ascend.AscendMod; +import net.fretux.ascend.player.PlayerStatsProvider; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.event.enchanting.EnchantmentLevelSetEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber(modid = AscendMod.MODID) +public class IntelligenceEnchantmentEvents { + + @SubscribeEvent + public static void onEnchantmentLevelSet(EnchantmentLevelSetEvent event) { + if (!(event.getLevel() instanceof ServerLevel serverLevel)) { + return; + } + Player player = serverLevel.getNearestPlayer( + event.getPos().getX() + 0.5, + event.getPos().getY() + 0.5, + event.getPos().getZ() + 0.5, + 5.0, + false + ); + if (player == null) { + return; + } + + player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { + int intelligence = stats.getAttributeLevel("intelligence"); + if (intelligence <= 0) return; + int original = event.getOriginalLevel(); + if (original <= 0) return; + double perPoint = 0.005d; + double max = 0.40d; + double reduction = Math.min(intelligence * perPoint, max); + int newLevel = (int) Math.max(1, Math.floor(original * (1.0d - reduction))); + event.setEnchantLevel(newLevel); + }); + } +} \ No newline at end of file diff --git a/src/main/java/net/fretux/ascend/event/IntelligencePotionEvents.java b/src/main/java/net/fretux/ascend/event/IntelligencePotionEvents.java new file mode 100644 index 0000000..9f89985 --- /dev/null +++ b/src/main/java/net/fretux/ascend/event/IntelligencePotionEvents.java @@ -0,0 +1,54 @@ +package net.fretux.ascend.event; + +import net.fretux.ascend.AscendMod; +import net.fretux.ascend.player.PlayerStatsProvider; +import net.minecraft.world.effect.MobEffectCategory; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.event.entity.living.MobEffectEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber(modid = AscendMod.MODID) +public class IntelligencePotionEvents { + + @SubscribeEvent + public static void onBeneficialEffectAdded(MobEffectEvent.Added event) { + if (!(event.getEntity() instanceof Player player)) return; + if (player.level().isClientSide) return; + + MobEffectInstance inst = event.getEffectInstance(); + if (inst == null) return; + if (inst.getEffect().getCategory() != MobEffectCategory.BENEFICIAL) return; + + // Avoid re-scaling already huge/custom durations. + if (inst.getDuration() > 12000) return; + + player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { + int intel = stats.getAttributeLevel("intelligence"); + if (intel <= 0) return; + + int baseDuration = inst.getDuration(); + + // +0.5% duration per INT, up to +50% + double durPerPoint = 0.005d; + double durMax = 0.5d; + double durBonus = Math.min(intel * durPerPoint, durMax); + int newDuration = (int) (baseDuration * (1.0d + durBonus)); + + if (newDuration <= baseDuration) return; // nothing to do + + // Replace effect: remove original, add scaled. + // This will fire another Added event, but newDuration > 12000 guard prevents loops. + player.removeEffect(inst.getEffect()); + player.addEffect(new MobEffectInstance( + inst.getEffect(), + newDuration, + inst.getAmplifier(), + inst.isAmbient(), + inst.isVisible(), + inst.showIcon() + )); + }); + } +} \ 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 ba9c7c5..d6bb3ba 100644 --- a/src/main/java/net/fretux/ascend/player/PlayerStats.java +++ b/src/main/java/net/fretux/ascend/player/PlayerStats.java @@ -89,6 +89,13 @@ public class PlayerStats { attributes.put(attribute, clamped); } + public int getMaxMana() { + int intelligence = getAttributeLevel("intelligence"); + int base = 50; + int perPoint = 3; + return base + intelligence * perPoint; + } + public CompoundTag serializeNBT() { CompoundTag tag = new CompoundTag(); tag.putInt("AscendLevel", ascendLevel); diff --git a/src/main/java/net/fretux/ascend/player/StatEffects.java b/src/main/java/net/fretux/ascend/player/StatEffects.java index 4badbff..8765617 100644 --- a/src/main/java/net/fretux/ascend/player/StatEffects.java +++ b/src/main/java/net/fretux/ascend/player/StatEffects.java @@ -14,27 +14,39 @@ public final class StatEffects { UUID.fromString("a7b8b2c3-9b0d-4e1a-8ee3-21f6a7b2c3d4"); private static final UUID STRENGTH_KNOCKBACK_UUID = UUID.fromString("b17f0d74-9cd9-4f90-9ec8-6a1046e2f912"); + + // ===== Agility UUIDs ===== 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"); + // ===== Fortitude UUIDs ===== + private static final UUID FORTITUDE_HEALTH_UUID = + UUID.fromString("e2b8ff31-2c3d-4a60-9ce4-12aa4d3f9b77"); + private static final UUID FORTITUDE_KB_RESIST_UUID = + UUID.fromString("f4c1a9d2-6b8e-4f01-8a21-9e7c5b3d12aa"); + private StatEffects() {} + /** Call on login/clone and after stat changes. */ 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. + applyFortitude(player, stats.getAttributeLevel("fortitude")); + applyIntelligence(player, stats.getAttributeLevel("intelligence")); }); } + // ========== STRENGTH ========== + 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; + double dmgBonus = strengthLevel * 0.1d; // 0.05 hearts per point atk.addTransientModifier(new AttributeModifier( STRENGTH_DAMAGE_UUID, "Ascend Strength damage bonus", @@ -43,7 +55,7 @@ public final class StatEffects { )); } } - + AttributeInstance kb = player.getAttribute(Attributes.ATTACK_KNOCKBACK); if (kb != null) { kb.removeModifier(STRENGTH_KNOCKBACK_UUID); @@ -58,13 +70,17 @@ public final class StatEffects { } } } - + + // ========== AGILITY ========== + public static void applyAgility(Player player, int agilityLevel) { + // Movement speed AttributeInstance move = player.getAttribute(Attributes.MOVEMENT_SPEED); if (move != null) { move.removeModifier(AGILITY_SPEED_UUID); if (agilityLevel > 0) { - double speedBonus = agilityLevel * 0.0005d; + // ~+2.5% at 10, +25% at 100 + double speedBonus = agilityLevel * 0.00025d; move.addTransientModifier(new AttributeModifier( AGILITY_SPEED_UUID, "Ascend Agility speed bonus", @@ -73,12 +89,14 @@ public final class StatEffects { )); } } + + // Stealth via follow range reduction 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 perPoint = 0.003d; // 0.3% per point + double maxReduction = 0.5d; // up to -50% double reduction = Math.min(agilityLevel * perPoint, maxReduction); if (reduction > 0.0d) { @@ -93,4 +111,62 @@ public final class StatEffects { } } } -} + + // ========== FORTITUDE ========== + + public static void applyFortitude(Player player, int fortitudeLevel) { + // Max HP + AttributeInstance maxHp = player.getAttribute(Attributes.MAX_HEALTH); + if (maxHp != null) { + maxHp.removeModifier(FORTITUDE_HEALTH_UUID); + if (fortitudeLevel > 0) { + // +0.2 HP per point (1 heart per 10 points) + double bonusHp = fortitudeLevel * 0.2d; + maxHp.addTransientModifier(new AttributeModifier( + FORTITUDE_HEALTH_UUID, + "Ascend Fortitude health bonus", + bonusHp, + AttributeModifier.Operation.ADDITION + )); + } + } + + float maxHealthNow = player.getMaxHealth(); + if (player.getHealth() > maxHealthNow) { + player.setHealth(maxHealthNow); + } + + // Knockback resistance + AttributeInstance kbRes = player.getAttribute(Attributes.KNOCKBACK_RESISTANCE); + if (kbRes != null) { + kbRes.removeModifier(FORTITUDE_KB_RESIST_UUID); + if (fortitudeLevel > 0) { + // 0.3% per point, up to 30% at 100 (with safety cap) + double bonus = fortitudeLevel * 0.003d; + double safeBonus = Math.min(bonus, 0.8d); + if (safeBonus > 0) { + kbRes.addTransientModifier(new AttributeModifier( + FORTITUDE_KB_RESIST_UUID, + "Ascend Fortitude knockback resist", + safeBonus, + AttributeModifier.Operation.ADDITION + )); + } + } + } + } + + // ========== INTELLIGENCE ========== + + /** + * Intelligence: + * - Max mana is exposed via PlayerStats (see below). + * - Potions & enchanting are handled via event hooks. + * + * This exists for future attribute-based hooks if needed. + */ + public static void applyIntelligence(Player player, int intelligenceLevel) { + // No direct vanilla attribute to touch here for mana. + // Potion & enchanting effects are implemented in event handlers. + } +} \ No newline at end of file