diff --git a/src/main/java/net/fretux/ascend/event/ActivityEvents.java b/src/main/java/net/fretux/ascend/event/ActivityEvents.java index 258ee69..59b88b3 100644 --- a/src/main/java/net/fretux/ascend/event/ActivityEvents.java +++ b/src/main/java/net/fretux/ascend/event/ActivityEvents.java @@ -14,6 +14,8 @@ import net.minecraft.world.entity.npc.Villager; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.LightLayer; import net.minecraft.world.level.block.Block; +import net.minecraft.world.entity.TamableAnimal; +import net.minecraft.world.entity.animal.horse.AbstractHorse; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.living.LivingDeathEvent; import net.minecraftforge.event.entity.living.LivingHurtEvent; @@ -24,8 +26,14 @@ 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; +import net.minecraft.world.entity.animal.IronGolem; +import net.minecraft.world.entity.animal.SnowGolem; +import net.minecraft.world.entity.npc.GossipType; +import net.minecraftforge.event.entity.living.LivingEvent; +import net.minecraft.world.phys.AABB; import java.util.Objects; +import java.util.UUID; @Mod.EventBusSubscriber(modid = AscendMod.MODID) public class ActivityEvents { @@ -33,6 +41,7 @@ public class ActivityEvents { private static int agilityTickCounter = 0; private static int fortitudeTickCounter = 0; private static int willpowerTickCounter = 0; + private static int charismaTickCounter = 0; @SubscribeEvent public static void onMobKilled(LivingDeathEvent event) { @@ -41,7 +50,7 @@ public class ActivityEvents { stats.addAscendXP((int) (AscendConfig.COMMON.xpPerMobKill.get() * AscendConfig.COMMON.xpMultiplier.get())); LivingEntity mob = event.getEntity(); if (mob.getMaxHealth() > 100f || mob.getType().toString().toLowerCase().contains("boss")) { - stats.addKnowledge(2); + stats.addKnowledgeScaled(2); } PlayerStatsProvider.sync(player); }); @@ -134,8 +143,15 @@ public class ActivityEvents { Player player = event.player; if (player.level().isClientSide || event.phase != TickEvent.Phase.END) return; willpowerTickCounter++; - if (willpowerTickCounter % 200 != 0) return; player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { + int willpower = stats.getAttributeLevel("willpower"); + if (willpower > 0 && willpowerTickCounter % 80 == 0) { + float regen = StatEffects.getWillpowerHealthRegen(willpower); + if (regen > 0.0f && player.getHealth() < player.getMaxHealth()) { + player.heal(regen); + } + } + if (willpowerTickCounter % 200 != 0) return; boolean hasNegativeEffect = player.getActiveEffects().stream() .anyMatch(e -> e.getEffect().getCategory() == MobEffectCategory.HARMFUL); boolean lowHealth = player.getHealth() <= player.getMaxHealth() * 0.25f; @@ -151,12 +167,48 @@ public class ActivityEvents { }); } + @SubscribeEvent + public static void onPlayerTickCharisma(TickEvent.PlayerTickEvent event) { + Player player = event.player; + if (player.level().isClientSide || event.phase != TickEvent.Phase.END) return; + charismaTickCounter++; + if (charismaTickCounter % 40 != 0) return; + player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { + int charisma = stats.getAttributeLevel("charisma"); + if (charisma <= 0) return; + double radius = 16.0d; + AABB range = player.getBoundingBox().inflate(radius); + for (LivingEntity entity : player.level().getEntitiesOfClass(LivingEntity.class, range, + candidate -> isCharismaAlly(player, candidate))) { + StatEffects.applyCharismaAllyBuff(entity, charisma); + } + }); + } + + @SubscribeEvent + public static void onCharismaAllyTick(LivingEvent.LivingTickEvent event) { + LivingEntity entity = event.getEntity(); + if (entity.level().isClientSide) return; + if (!isCharismaAllyCandidate(entity)) return; + if (entity.tickCount % 40 != 0) return; + double radius = 16.0d; + if (hasNearbyCharismaSource(entity, radius)) return; + StatEffects.clearCharismaAllyBuff(entity); + } + @SubscribeEvent public static void onVillagerTrade(PlayerInteractEvent.EntityInteract event) { if (!(event.getTarget() instanceof Villager)) return; Player player = event.getEntity(); if (player.level().isClientSide) return; player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { + int charisma = stats.getAttributeLevel("charisma"); + if (charisma > 0) { + int reputationBonus = StatEffects.getCharismaVillagerReputationBonus(charisma); + if (reputationBonus > 0 && event.getTarget() instanceof Villager villager) { + villager.getGossips().add(player.getUUID(), GossipType.MINOR_POSITIVE, reputationBonus); + } + } stats.addAscendXP((int) (AscendConfig.COMMON.xpPerTrade.get() * AscendConfig.COMMON.xpMultiplier.get())); PlayerStatsProvider.sync(player); }); @@ -271,4 +323,64 @@ public class ActivityEvents { } }); } -} \ No newline at end of file + + private static boolean isCharismaAlly(Player player, LivingEntity entity) { + if (entity instanceof TamableAnimal tame) { + return tame.isTame() && tame.isOwnedBy(player); + } + if (entity instanceof AbstractHorse horse) { + return horse.isTamed() && player.getUUID().equals(horse.getOwnerUUID()); + } + if (entity instanceof IronGolem ironGolem) { + return ironGolem.isPlayerCreated(); + } + if (entity instanceof SnowGolem snowGolem) { + return snowGolem.isPlayerCreated(); + } + return false; + } + + private static boolean isCharismaAllyCandidate(LivingEntity entity) { + if (entity instanceof TamableAnimal tame) { + return tame.isTame(); + } + if (entity instanceof AbstractHorse horse) { + return horse.isTamed(); + } + if (entity instanceof IronGolem ironGolem) { + return ironGolem.isPlayerCreated(); + } + if (entity instanceof SnowGolem snowGolem) { + return snowGolem.isPlayerCreated(); + } + return false; + } + + private static boolean hasNearbyCharismaSource(LivingEntity entity, double radius) { + if (entity instanceof TamableAnimal tame) { + Player owner = tame.getOwner() instanceof Player player ? player : null; + return owner != null && owner.distanceToSqr(entity) <= radius * radius && hasCharisma(owner); + } + if (entity instanceof AbstractHorse horse) { + UUID ownerId = horse.getOwnerUUID(); + if (ownerId == null) return false; + Player owner = entity.level().getPlayerByUUID(ownerId); + return owner != null && owner.distanceToSqr(entity) <= radius * radius && hasCharisma(owner); + } + if (entity instanceof IronGolem || entity instanceof SnowGolem) { + AABB range = entity.getBoundingBox().inflate(radius); + for (Player player : entity.level().getEntitiesOfClass(Player.class, range)) { + if (hasCharisma(player)) { + return true; + } + } + } + return false; + } + + private static boolean hasCharisma(Player player) { + return player.getCapability(PlayerStatsProvider.PLAYER_STATS) + .map(stats -> stats.getAttributeLevel("charisma") > 0) + .orElse(false); + } +} diff --git a/src/main/java/net/fretux/ascend/event/CharismaTradeEvents.java b/src/main/java/net/fretux/ascend/event/CharismaTradeEvents.java index d018944..6b32d21 100644 --- a/src/main/java/net/fretux/ascend/event/CharismaTradeEvents.java +++ b/src/main/java/net/fretux/ascend/event/CharismaTradeEvents.java @@ -57,4 +57,4 @@ public class CharismaTradeEvents { } }); } -} \ No newline at end of file +} diff --git a/src/main/java/net/fretux/ascend/event/IntelligenceAnvilEvents.java b/src/main/java/net/fretux/ascend/event/IntelligenceAnvilEvents.java new file mode 100644 index 0000000..65b6202 --- /dev/null +++ b/src/main/java/net/fretux/ascend/event/IntelligenceAnvilEvents.java @@ -0,0 +1,31 @@ +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.world.entity.player.Player; +import net.minecraftforge.event.AnvilUpdateEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber(modid = AscendMod.MODID) +public class IntelligenceAnvilEvents { + + @SubscribeEvent + public static void onAnvilUpdate(AnvilUpdateEvent event) { + Player player = event.getPlayer(); + if (player == null || player.level().isClientSide) return; + int baseCost = event.getCost(); + if (baseCost <= 0) return; + player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { + int intelligence = stats.getAttributeLevel("intelligence"); + if (intelligence <= 0) return; + double reduction = StatEffects.getIntelligenceAnvilCostReduction(intelligence); + if (reduction <= 0.0d) return; + int newCost = (int) Math.max(1, Math.floor(baseCost * (1.0d - reduction))); + if (newCost < baseCost) { + event.setCost(newCost); + } + }); + } +} diff --git a/src/main/java/net/fretux/ascend/item/MoonseyeTome.java b/src/main/java/net/fretux/ascend/item/MoonseyeTome.java index 743e768..bdf8738 100644 --- a/src/main/java/net/fretux/ascend/item/MoonseyeTome.java +++ b/src/main/java/net/fretux/ascend/item/MoonseyeTome.java @@ -26,12 +26,12 @@ public class MoonseyeTome extends Item { } else { gained = 50; } - stats.addKnowledge(gained); - player.sendSystemMessage(net.minecraft.network.chat.Component.translatable("item.ascend.moonseye_tome.used", gained)); + int totalGained = stats.addKnowledgeScaled(gained); + player.sendSystemMessage(net.minecraft.network.chat.Component.translatable("item.ascend.moonseye_tome.used", totalGained)); player.playSound(SoundEvents.ENCHANTMENT_TABLE_USE, 1.0f, 1.2f); stack.shrink(1); PlayerStatsProvider.sync(player); }); return InteractionResultHolder.consume(stack); } -} \ No newline at end of file +} diff --git a/src/main/java/net/fretux/ascend/network/ServerboundShrineChoicePacket.java b/src/main/java/net/fretux/ascend/network/ServerboundShrineChoicePacket.java index 8592272..2dc23bb 100644 --- a/src/main/java/net/fretux/ascend/network/ServerboundShrineChoicePacket.java +++ b/src/main/java/net/fretux/ascend/network/ServerboundShrineChoicePacket.java @@ -55,7 +55,7 @@ public class ServerboundShrineChoicePacket { player.getCapability(PlayerCapabilityProvider.SANITY).ifPresent(sanity -> { float sanityPercent = sanity.getSanity() / sanity.getMaxSanity(); if (sanityPercent >= 0.90f) { - stats.addKnowledge(5); + stats.addKnowledgeScaled(5); sanity.setSanity(0); sanity.setInsanity(0); player.sendSystemMessage(Component.literal("YOUR MIND EXPANDS AS IT BREAKS.")); @@ -86,4 +86,4 @@ public class ServerboundShrineChoicePacket { }); 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 be18c76..f014e01 100644 --- a/src/main/java/net/fretux/ascend/player/PlayerStats.java +++ b/src/main/java/net/fretux/ascend/player/PlayerStats.java @@ -32,7 +32,7 @@ public class PlayerStats { ascendXP -= xpNeeded; ascendLevel++; unspentPoints += POINTS_PER_LEVEL; - knowledge += 1; + addKnowledgeScaled(1); } if (ascendLevel >= MAX_ASCEND_LEVEL) { int xpNeeded = getXPToNextAscendLevel(); @@ -111,6 +111,17 @@ public class PlayerStats { knowledge = Math.max(0, knowledge + amount); } + public int addKnowledgeScaled(int amount) { + if (amount <= 0) { + addKnowledge(amount); + return amount; + } + int intelligence = getAttributeLevel("intelligence"); + int scaled = StatEffects.getIntelligenceKnowledgeGain(intelligence, amount); + addKnowledge(scaled); + return scaled; + } + public void refundAllPoints() { int totalSpent = 0; for (String key : attributes.keySet()) { @@ -167,4 +178,4 @@ public class PlayerStats { hasUsedMoonseye = tag.getBoolean("HasUsedMoonseye"); } } -} \ No newline at end of file +} diff --git a/src/main/java/net/fretux/ascend/player/StatEffects.java b/src/main/java/net/fretux/ascend/player/StatEffects.java index e021d84..2f35099 100644 --- a/src/main/java/net/fretux/ascend/player/StatEffects.java +++ b/src/main/java/net/fretux/ascend/player/StatEffects.java @@ -3,6 +3,7 @@ package net.fretux.ascend.player; import net.fretux.ascend.compat.IronsSpellbooksCompat; import net.fretux.ascend.config.AscendConfig; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.ai.attributes.AttributeInstance; import net.minecraft.world.entity.ai.attributes.AttributeModifier; import net.minecraft.world.entity.ai.attributes.Attributes; @@ -23,6 +24,10 @@ public final class StatEffects { UUID.fromString("d3a4b5c6-7d8e-4f90-8a1b-223344556677"); private static final UUID FORTITUDE_KB_RESIST_UUID = UUID.fromString("e4b5c6d7-8e9f-4012-9abc-334455667788"); + private static final UUID CHARISMA_ALLY_HEALTH_UUID = + UUID.fromString("f5c6d7e8-9012-4bcd-9ef0-445566778899"); + private static final UUID CHARISMA_ALLY_DAMAGE_UUID = + UUID.fromString("0a1b2c3d-4e5f-4012-8a9b-556677889900"); private StatEffects() { } @@ -145,6 +150,12 @@ public final class StatEffects { return (float) (1.0d + bonus); } + public static float getWillpowerHealthRegen(int willpowerLevel) { + if (willpowerLevel <= 0) return 0.0f; + double bonus = Math.min(willpowerLevel * 0.01d * getScaling(), 2.0d); + return (float) bonus; + } + public static float getPlayerSanityMultiplier(Player player) { return player.getCapability(PlayerStatsProvider.PLAYER_STATS) .map(stats -> getWillpowerSanityMultiplier(stats.getAttributeLevel("willpower"))) @@ -161,12 +172,97 @@ public final class StatEffects { return Math.max(0, intelligenceLevel); } + public static double getIntelligenceAnvilCostReduction(int intelligenceLevel) { + if (intelligenceLevel <= 0) return 0.0d; + double perPoint = 0.005d; + double max = 0.40d; + return Math.min(intelligenceLevel * perPoint * getScaling(), max); + } + + public static int getIntelligenceKnowledgeGain(int intelligenceLevel, int baseGain) { + if (baseGain <= 0) return baseGain; + if (intelligenceLevel <= 0) return baseGain; + double perPoint = 0.005d; + double max = 0.50d; + double bonus = Math.min(intelligenceLevel * perPoint * getScaling(), max); + int extra = (int) Math.floor(baseGain * bonus); + return baseGain + Math.max(0, extra); + } + public static double getCharismaTradeDiscount(int charismaLevel) { if (charismaLevel <= 0) return 0.0d; double perPoint = 0.01d; double max = 0.30d; return Math.min(charismaLevel * perPoint * getScaling(), max); } + + public static int getCharismaVillagerReputationBonus(int charismaLevel) { + if (charismaLevel <= 0) return 0; + double perPoint = 0.1d; + double max = 10.0d; + return (int) Math.floor(Math.min(charismaLevel * perPoint * getScaling(), max)); + } + + public static double getCharismaAllyHealthBonus(int charismaLevel) { + if (charismaLevel <= 0) return 0.0d; + double perPoint = 0.0025d; + double max = 0.25d; + return Math.min(charismaLevel * perPoint * getScaling(), max); + } + + public static double getCharismaAllyDamageBonus(int charismaLevel) { + if (charismaLevel <= 0) return 0.0d; + double perPoint = 0.0020d; + double max = 0.20d; + return Math.min(charismaLevel * perPoint * getScaling(), max); + } + + public static void applyCharismaAllyBuff(LivingEntity entity, int charismaLevel) { + if (charismaLevel <= 0) { + clearCharismaAllyBuff(entity); + return; + } + double healthBonus = getCharismaAllyHealthBonus(charismaLevel); + AttributeInstance health = entity.getAttribute(Attributes.MAX_HEALTH); + if (health != null) { + health.removeModifier(CHARISMA_ALLY_HEALTH_UUID); + if (healthBonus > 0.0d) { + health.addTransientModifier(new AttributeModifier( + CHARISMA_ALLY_HEALTH_UUID, + "Ascend Charisma ally max health", + healthBonus, + AttributeModifier.Operation.MULTIPLY_BASE + )); + } + } + double damageBonus = getCharismaAllyDamageBonus(charismaLevel); + AttributeInstance damage = entity.getAttribute(Attributes.ATTACK_DAMAGE); + if (damage != null) { + damage.removeModifier(CHARISMA_ALLY_DAMAGE_UUID); + if (damageBonus > 0.0d) { + damage.addTransientModifier(new AttributeModifier( + CHARISMA_ALLY_DAMAGE_UUID, + "Ascend Charisma ally damage", + damageBonus, + AttributeModifier.Operation.MULTIPLY_BASE + )); + } + } + } + + public static void clearCharismaAllyBuff(LivingEntity entity) { + AttributeInstance health = entity.getAttribute(Attributes.MAX_HEALTH); + if (health != null) { + health.removeModifier(CHARISMA_ALLY_HEALTH_UUID); + } + AttributeInstance damage = entity.getAttribute(Attributes.ATTACK_DAMAGE); + if (damage != null) { + damage.removeModifier(CHARISMA_ALLY_DAMAGE_UUID); + } + if (entity.getHealth() > entity.getMaxHealth()) { + entity.setHealth(entity.getMaxHealth()); + } + } public static double getWeaponScalingMultiplier(int scalingLevel) { if (scalingLevel <= 0) return 1.0d; @@ -192,4 +288,4 @@ public final class StatEffects { double maxBonus = 0.75d; return 1.0d + Math.min(scalingLevel * perPoint, maxBonus); } -} \ No newline at end of file +} diff --git a/src/main/resources/assets/ascend/lang/en_us.json b/src/main/resources/assets/ascend/lang/en_us.json index 395dcf6..1202c4d 100644 --- a/src/main/resources/assets/ascend/lang/en_us.json +++ b/src/main/resources/assets/ascend/lang/en_us.json @@ -14,11 +14,11 @@ "ascend.attribute.fortitude": "Fortitude", "ascend.attribute.fortitude.desc": "Increases max health and knockback resistance.", "ascend.attribute.intelligence": "Intelligence", - "ascend.attribute.intelligence.desc": "Increases max mana and mana regeneration. Also lowers enchantment cost and buffs potions.", + "ascend.attribute.intelligence.desc": "Increases max mana and mana regeneration. Lowers enchantment/anvil costs, buffs potions, and boosts knowledge gain.", "ascend.attribute.willpower": "Willpower", - "ascend.attribute.willpower.desc": "Reduces sanity drain and improves stamina/tempo.", + "ascend.attribute.willpower.desc": "Reduces sanity drain, improves stamina/tempo, and increases passive health regeneration.", "ascend.attribute.charisma": "Charisma", - "ascend.attribute.charisma.desc": "Increases max mana and improves trade deals.", + "ascend.attribute.charisma.desc": "Increases max mana, improves trade deals, boosts villager reputation, and empowers tamed allies/golems.", "ascend.attribute.light_scaling": "Light Scaling", "ascend.attribute.light_scaling.desc": "Improves light weapon performance.", "ascend.attribute.medium_scaling": "Medium Scaling", @@ -36,4 +36,3 @@ "item.ascend.remembrance_essence": "Remembrance Essence", "block.ascend.shrine_of_remembrance": "Shrine of Remembrance" } -