From e36fc74a1fa688c7d4ed2183b6bcf9cff8cb74ad Mon Sep 17 00:00:00 2001 From: fretuks Date: Mon, 24 Aug 2026 20:55:56 +0200 Subject: [PATCH] Revamp the entire skillset and rebalance everything. --- CHANGELOG.md | 52 +++ build.gradle | 7 + gradle.properties | 2 +- .../java/net/fretux/ascend/AscendMod.java | 5 +- .../ascend/client/screen/StatsScreen.java | 235 ++++++++----- .../fretux/ascend/command/AscendCommand.java | 5 +- .../fretux/ascend/compat/AscendMMCompat.java | 36 +- .../ascend/compat/IronsSpellbooksCompat.java | 280 +++------------ .../fretux/ascend/config/AscendConfig.java | 118 ++++--- .../fretux/ascend/event/ActivityEvents.java | 70 +--- .../ascend/event/FortitudeDefenseEvents.java | 49 --- .../ascend/event/IntelligenceAnvilEvents.java | 31 -- .../event/IntelligenceEnchantmentEvents.java | 41 --- .../event/IntelligencePotionEvents.java | 42 --- .../net/fretux/ascend/event/PlayerEvents.java | 35 +- .../event/WeaponMagicScalingEvents.java | 80 ----- .../ServerboundShrineChoicePacket.java | 25 +- .../network/ServerboundSpendPointPacket.java | 2 +- .../net/fretux/ascend/player/PlayerStats.java | 304 ++++++----------- .../ascend/player/PlayerStatsProvider.java | 1 + .../net/fretux/ascend/player/StatEffects.java | 321 ++++-------------- .../resources/assets/ascend/lang/en_us.json | 58 ++-- 22 files changed, 592 insertions(+), 1207 deletions(-) delete mode 100644 src/main/java/net/fretux/ascend/event/FortitudeDefenseEvents.java delete mode 100644 src/main/java/net/fretux/ascend/event/IntelligenceAnvilEvents.java delete mode 100644 src/main/java/net/fretux/ascend/event/IntelligenceEnchantmentEvents.java delete mode 100644 src/main/java/net/fretux/ascend/event/IntelligencePotionEvents.java delete mode 100644 src/main/java/net/fretux/ascend/event/WeaponMagicScalingEvents.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3de0527..00e2a7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,58 @@ This changelog covers all recorded changes after `1.0.0`, grouped by release version from the git history. +## 3.0.8 + +- Increased Agility's default maximum movement-speed bonus from 15% to 25% so the stat retains a stronger mobility identity alongside its weapon-grade damage scaling. +- Raised the configurable minimum to 25%, automatically correcting older lower values while keeping higher custom values valid. + +## 3.0.7 + +- Changed armor penetration to ignore the matching percentage of the target's actual armor before Minecraft's armor-and-toughness calculation. +- Added a configurable 30% armor-penetration cap and a public per-hit Forge event allowing integrations to increase penetration, change the cap, or disable it. + +## 3.0.6 + +- Added `update.md`, a complete release summary comparing every 100-point attribute and documenting all changes from 2.4.4 through 3.0.5. + +## 3.0.5 + +- Restored the normal maximum Ascend level to 20 while retaining the configurable 1–100 range. +- Made the active server configuration override stale maximum-level snapshots stored in existing player data. + +## 3.0.4 + +- Increased Agility's maximum movement-speed bonus from 5% to a minimum configurable 15%, including correction of older configs below that value. +- Removed Ascend's Sanity-loss reduction shim; Mind & Motion now remains the sole owner of Willpower's existing maximum Sanity and maximum Tempo capacity bonuses. +- Corrected Willpower's Iron's Spellbooks cast-time modifier to use the API's required positive reduction value. +- Audited and tested the Iron's Spellbooks mana, mana regeneration, spell power, cooldown, and cast-time attributes plus Mind & Motion's Sanity and Tempo capability setters. + +## 3.0.3 + +- Removed the subtitle from the Ascend Stats screen and reclaimed the unused header space. +- Reworded every attribute summary and tooltip with concise, direct descriptions of its gameplay effects, and widened the cards to keep the new text readable. + +## 3.0.2 + +- Fixed Strength and Agility summaries being clipped by shortening their card text and separating all descriptions vertically from the upgrade buttons. + +## 3.0.1 + +- Redesigned the Ascend Stats screen with a wider two-column layout, spacious attribute cards, color-coded stat accents, short role summaries, a dedicated progression panel, and a visible Ascend XP bar. +- Improved upgrade controls with clearer maximum-value states, stronger unspent-point emphasis, and full attribute descriptions on card hover. + +## 3.0.0 + +- Replaced the ten-stat model with Vigor, Mind, Strength, Agility, Intelligence, Willpower, Charisma, and Arcane; removed Fortitude and all four dedicated weapon-scaling stats. +- Added a shared configurable soft-cap curve, configurable Vigor health, Strength knockback/armor penetration, conservative Agility movement speed, and distinct magic-stat roles. +- Moved Iron's Spellbooks mana capacity and regeneration to Mind, spell power and cooldown bonuses to Intelligence, retained Willpower cast-time support, and removed Charisma's generic mana bonus. +- Extended the optional Mind & Motion bridge so Willpower reduces Sanity loss through its public capability while retaining the existing Tempo and mental-resilience integration. +- Added registry- and datapack-driven weapon scaling with S through E/NONE grades, main-attribute extensibility, vanilla weapon defaults, additive base-damage bonuses, scaling tooltips, and safe optional Mace support. +- Added event-based Arcane duration bonuses for finite beneficial effects on self and harmful effects applied to enemies, with separate configurable curves and no per-tick extension. +- Added player data format version 2 and a one-time server-side migration that refunds every point invested in Fortitude and the removed scaling stats while preserving retained attributes, level, XP, Knowledge, and unrelated player data. +- Updated stats UI, commands, advancement checks, login/respawn/dimension synchronization, localization, and configuration for the new model. +- Added automated tests for the soft-cap curve and legacy save migration, including double-refund prevention and new-player behavior. + ## 2.4.4 - Switched Iron's Spellbooks `3.16.2` to its current Curse Maven artifact because the legacy API-classifier coordinate is no longer published. diff --git a/build.gradle b/build.gradle index 13dc018..148e53c 100644 --- a/build.gradle +++ b/build.gradle @@ -155,6 +155,13 @@ dependencies { // CURIOS implementation fg.deobf("top.theillusivec4.curios:curios-forge:${curios_version}") + + testImplementation platform('org.junit:junit-bom:5.10.2') + testImplementation 'org.junit.jupiter:junit-jupiter' +} + +tasks.named('test', Test).configure { + useJUnitPlatform() } // This block of code expands all declared replace properties in the specified resource targets. diff --git a/gradle.properties b/gradle.properties index 7efd806..eb07f83 100644 --- a/gradle.properties +++ b/gradle.properties @@ -56,7 +56,7 @@ mod_name=Ascend: SbA # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=All Rights Reserved # The mod version. See https://semver.org/ -mod_version=2.4.4 +mod_version=3.0.8 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/net/fretux/ascend/AscendMod.java b/src/main/java/net/fretux/ascend/AscendMod.java index fe4035d..44ee9a2 100644 --- a/src/main/java/net/fretux/ascend/AscendMod.java +++ b/src/main/java/net/fretux/ascend/AscendMod.java @@ -14,10 +14,13 @@ import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.loading.FMLEnvironment; +import org.slf4j.Logger; +import com.mojang.logging.LogUtils; @Mod(AscendMod.MODID) public class AscendMod { public static final String MODID = "ascend"; + public static final Logger LOGGER = LogUtils.getLogger(); public AscendMod() { IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus(); @@ -38,4 +41,4 @@ public class AscendMod { System.out.println("[Ascend] Registering packets... Side=" + FMLEnvironment.dist); }); } -} \ No newline at end of file +} diff --git a/src/main/java/net/fretux/ascend/client/screen/StatsScreen.java b/src/main/java/net/fretux/ascend/client/screen/StatsScreen.java index fbe767e..233e643 100644 --- a/src/main/java/net/fretux/ascend/client/screen/StatsScreen.java +++ b/src/main/java/net/fretux/ascend/client/screen/StatsScreen.java @@ -14,26 +14,29 @@ import java.util.HashMap; import java.util.Map; public class StatsScreen extends Screen { + private static final int WIDTH = 480; + private static final int HEIGHT = 266; + private static final int PADDING = 12; + private static final int COLUMN_GAP = 8; + private static final int CARD_GAP = 4; + private static final int CARD_HEIGHT = 40; + private static final int CARDS_TOP = 64; + private static final int CARD_WIDTH = (WIDTH - PADDING * 2 - COLUMN_GAP) / 2; - private static final int WIDTH = 220; - private static final int HEIGHT = 240; - - private int leftPos; - private int topPos; - - private final Minecraft mc = Minecraft.getInstance(); - private final Map plusButtons = new HashMap<>(); - private String hoveredAttributeKey = null; - - public StatsScreen() { - super(Component.literal("Ascend Stats")); - } - - private static final String[] ATTRIBUTES = new String[]{ - "strength", "agility", "fortitude", "intelligence", - "willpower", "charisma", - "light_scaling", "medium_scaling", "heavy_scaling", "magic_scaling" + private static final String[] ATTRIBUTES = { + "vigor", "mind", "strength", "agility", + "intelligence", "willpower", "charisma", "arcane" }; + private static final Map ATTRIBUTE_COLORS = Map.of( + "vigor", 0xFFD85C5C, + "mind", 0xFF6699E8, + "strength", 0xFFD78445, + "agility", 0xFF71C988, + "intelligence", 0xFF9A7BE8, + "willpower", 0xFF79B9C8, + "charisma", 0xFFE28AB8, + "arcane", 0xFFC878E0 + ); private static final Map ATTRIBUTE_TOOLTIPS = new HashMap<>(); static { @@ -42,97 +45,141 @@ public class StatsScreen extends Screen { } } - @Override - protected void init() { - super.init(); - this.leftPos = (this.width - WIDTH) / 2; - this.topPos = (this.height - HEIGHT) / 2; - this.plusButtons.clear(); - this.clearWidgets(); - int y = topPos + 55; - for (String attr : ATTRIBUTES) { - final String key = attr; - Button plus = Button.builder(Component.literal("+"), (button) -> { - PacketHandler.INSTANCE.sendToServer(new ServerboundSpendPointPacket(key)); - }) - .bounds(leftPos + WIDTH - 40, y - 6, 30, 18) - .build(); - this.addRenderableWidget(plus); - this.plusButtons.put(key, plus); - y += 16; - } - Button done = Button.builder(Component.translatable("gui.done"), (btn) -> onClose()) - .bounds(leftPos + WIDTH / 2 - 40, topPos + HEIGHT - 24, 80, 20) - .build(); - this.addRenderableWidget(done); + private final Minecraft minecraft = Minecraft.getInstance(); + private final Map plusButtons = new HashMap<>(); + private int leftPos; + private int topPos; + private String hoveredAttributeKey; + + public StatsScreen() { + super(Component.translatable("ascend.stats.title")); } @Override - public void render(GuiGraphics gui, int mouseX, int mouseY, float partialTicks) { - this.renderBackground(gui); - RenderSystem.enableBlend(); - gui.fill(leftPos - 8, topPos - 8, leftPos + WIDTH + 8, topPos + HEIGHT + 8, 0xDD0C0B12); - gui.fill(leftPos - 4, topPos - 4, leftPos + WIDTH + 4, topPos + HEIGHT + 4, 0xE0181422); - gui.fill(leftPos - 4, topPos - 4, leftPos + WIDTH + 4, topPos + 20, 0xFF2A2036); - gui.fill(leftPos - 4, topPos + 20, leftPos + WIDTH + 4, topPos + 21, 0x66FFFFFF); - RenderSystem.disableBlend(); - gui.drawCenteredString(this.font, Component.translatable("ascend.stats.title"), this.width / 2, topPos + 6, 0xFFFFFF); + protected void init() { + super.init(); + leftPos = (width - WIDTH) / 2; + topPos = (height - HEIGHT) / 2; + plusButtons.clear(); + clearWidgets(); + + for (int index = 0; index < ATTRIBUTES.length; index++) { + String attribute = ATTRIBUTES[index]; + int column = index % 2; + int row = index / 2; + int cardX = leftPos + PADDING + column * (CARD_WIDTH + COLUMN_GAP); + int cardY = topPos + CARDS_TOP + row * (CARD_HEIGHT + CARD_GAP); + Button plus = Button.builder(Component.literal("+"), button -> + PacketHandler.INSTANCE.sendToServer(new ServerboundSpendPointPacket(attribute))) + .bounds(cardX + CARD_WIDTH - 30, cardY + 5, 22, 18) + .build(); + addRenderableWidget(plus); + plusButtons.put(attribute, plus); + } + + addRenderableWidget(Button.builder(Component.translatable("gui.done"), button -> onClose()) + .bounds(leftPos + WIDTH / 2 - 45, topPos + HEIGHT - 24, 90, 18) + .build()); + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) { + renderBackground(graphics); + drawWindow(graphics); hoveredAttributeKey = null; - if (mc.player != null) { - mc.player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { - int level = stats.getAscendLevel(); - int maxLevel = stats.getMaxAscendLevel(); - int xp = stats.getAscendXP(); - int xpNext = stats.getXPToNextAscendLevel(); - int unspent = stats.getUnspentPoints(); - gui.drawString(font, Component.translatable("ascend.stats.level", level, maxLevel), leftPos + 10, topPos + 28, 0xFFE27D); - if (level < maxLevel) { - gui.drawString(font, Component.translatable("ascend.stats.xp", xp, xpNext), leftPos + 10, topPos + 40, 0xB4B4C8); - } else { - gui.drawString(font, Component.translatable("ascend.stats.xp_max"), leftPos + 10, topPos + 40, 0xB4B4C8); - } - int unspentLabelWidth = font.width("Unspent Points: " + unspent); - int unspentX = leftPos + WIDTH - 10 - unspentLabelWidth; - gui.fill(unspentX - 6, topPos + 24, leftPos + WIDTH - 4, topPos + 38, 0x662D3B1E); - gui.drawString(font, Component.translatable("ascend.stats.unspent", unspent), - unspentX, topPos + 28, 0x8CFF8C); - int y = topPos + 64; - for (String key : ATTRIBUTES) { - int attrLevel = stats.getAttributeLevel(key); - int cost = stats.getCostToUpgrade(key); - Component name = Component.translatable("ascend.attribute." + key); - Component label = Component.translatable("ascend.attribute.display", name, attrLevel); - int labelX = leftPos + 10; - gui.drawString(font, label, labelX, y, 0xE6E6F2); - Button plus = plusButtons.get(key); + if (minecraft.player != null) { + minecraft.player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { + drawProgression(graphics, stats.getAscendLevel(), stats.getMaxAscendLevel(), + stats.getAscendXP(), stats.getXPToNextAscendLevel(), stats.getKnowledge(), stats.getUnspentPoints()); + + for (int index = 0; index < ATTRIBUTES.length; index++) { + String attribute = ATTRIBUTES[index]; + int column = index % 2; + int row = index / 2; + int cardX = leftPos + PADDING + column * (CARD_WIDTH + COLUMN_GAP); + int cardY = topPos + CARDS_TOP + row * (CARD_HEIGHT + CARD_GAP); + int value = stats.getAttributeLevel(attribute); + boolean hovered = mouseX >= cardX && mouseX < cardX + CARD_WIDTH + && mouseY >= cardY && mouseY < cardY + CARD_HEIGHT; + drawAttributeCard(graphics, attribute, value, stats.getMaxAttributePoints(), cardX, cardY, hovered); + if (hovered) hoveredAttributeKey = attribute; + + Button plus = plusButtons.get(attribute); if (plus != null) { - plus.setX(leftPos + WIDTH - 40); - plus.setY(y - 6); - plus.active = (unspent >= cost); - plus.setMessage(Component.literal("+" + cost)); + int cost = stats.getCostToUpgrade(attribute); + plus.active = stats.getUnspentPoints() >= cost && value < stats.getMaxAttributePoints(); + plus.setMessage(value >= stats.getMaxAttributePoints() + ? Component.literal("✓") : Component.literal("+")); } - - int labelWidth = font.width(label); - if (mouseX >= labelX && mouseX <= labelX + labelWidth - && mouseY >= y && mouseY <= y + font.lineHeight) { - hoveredAttributeKey = key; - } - y += 16; } }); } - super.render(gui, mouseX, mouseY, partialTicks); - + super.render(graphics, mouseX, mouseY, partialTicks); if (hoveredAttributeKey != null) { - Component tooltip = ATTRIBUTE_TOOLTIPS.get(hoveredAttributeKey); - if (tooltip != null) { - gui.renderTooltip(this.font, tooltip, mouseX, mouseY); - } + graphics.renderTooltip(font, ATTRIBUTE_TOOLTIPS.get(hoveredAttributeKey), mouseX, mouseY); } } + private void drawWindow(GuiGraphics graphics) { + RenderSystem.enableBlend(); + graphics.fill(leftPos - 6, topPos - 6, leftPos + WIDTH + 6, topPos + HEIGHT + 6, 0xCC07070C); + graphics.fill(leftPos - 3, topPos - 3, leftPos + WIDTH + 3, topPos + HEIGHT + 3, 0xFF30253C); + graphics.fill(leftPos, topPos, leftPos + WIDTH, topPos + HEIGHT, 0xF016131E); + graphics.fill(leftPos, topPos, leftPos + WIDTH, topPos + 20, 0xFF2A2036); + graphics.fill(leftPos, topPos + 20, leftPos + WIDTH, topPos + 21, 0xFF655275); + RenderSystem.disableBlend(); + + graphics.drawCenteredString(font, title, leftPos + WIDTH / 2, topPos + 6, 0xFFF4EAFB); + } + + private void drawProgression(GuiGraphics graphics, int level, int maxLevel, int xp, int xpNext, + int knowledge, int unspent) { + int panelX = leftPos + PADDING; + int panelY = topPos + 26; + int panelWidth = WIDTH - PADDING * 2; + graphics.fill(panelX, panelY, panelX + panelWidth, panelY + 31, 0xAA211C29); + graphics.fill(panelX, panelY, panelX + 3, panelY + 31, 0xFFCFB060); + + graphics.drawString(font, Component.translatable("ascend.stats.level", level, maxLevel), + panelX + 9, panelY + 5, 0xFFFFDF78, false); + graphics.drawString(font, Component.translatable("ascend.stats.knowledge", knowledge), + panelX + 112, panelY + 5, 0xFFA9C7FF, false); + + Component points = Component.translatable("ascend.stats.unspent", unspent); + int pointsWidth = font.width(points); + int pointsX = panelX + panelWidth - pointsWidth - 9; + graphics.fill(pointsX - 5, panelY + 2, panelX + panelWidth - 4, panelY + 16, 0x663D623D); + graphics.drawString(font, points, pointsX, panelY + 5, unspent > 0 ? 0xFF87F58D : 0xFF9D9D9D, false); + + int barX = panelX + 9; + int barY = panelY + 20; + int barWidth = panelWidth - 18; + graphics.fill(barX, barY, barX + barWidth, barY + 5, 0xFF0C0A10); + double progress = level >= maxLevel || xpNext <= 0 ? 1.0 : Math.min(1.0, (double) xp / xpNext); + graphics.fill(barX + 1, barY + 1, barX + 1 + (int) ((barWidth - 2) * progress), barY + 4, 0xFF8E68C3); + Component xpLabel = level >= maxLevel ? Component.translatable("ascend.stats.xp_max") + : Component.translatable("ascend.stats.xp", xp, xpNext); + graphics.drawCenteredString(font, xpLabel, barX + barWidth / 2, barY - 2, 0xFFC9C1D0); + } + + private void drawAttributeCard(GuiGraphics graphics, String attribute, int value, int maximum, + int x, int y, boolean hovered) { + int accent = ATTRIBUTE_COLORS.getOrDefault(attribute, 0xFFFFFFFF); + graphics.fill(x, y, x + CARD_WIDTH, y + CARD_HEIGHT, hovered ? 0xE0332B3D : 0xC925202F); + graphics.fill(x, y, x + 3, y + CARD_HEIGHT, accent); + graphics.fill(x + 3, y, x + CARD_WIDTH, y + 1, hovered ? 0x667F6B8C : 0x334C4057); + + graphics.drawString(font, Component.translatable("ascend.attribute." + attribute), + x + 9, y + 7, 0xFFF1ECF4, false); + Component valueLabel = Component.literal(value + " / " + maximum); + int valueX = x + CARD_WIDTH - 38 - font.width(valueLabel); + graphics.drawString(font, valueLabel, valueX, y + 7, value >= maximum ? 0xFFFFD66B : 0xFFC9C1D0, false); + graphics.drawString(font, Component.translatable("ascend.attribute." + attribute + ".short"), + x + 9, y + 26, 0xFF9F98A8, false); + } + @Override public boolean isPauseScreen() { return false; diff --git a/src/main/java/net/fretux/ascend/command/AscendCommand.java b/src/main/java/net/fretux/ascend/command/AscendCommand.java index 85cb890..d956ae6 100644 --- a/src/main/java/net/fretux/ascend/command/AscendCommand.java +++ b/src/main/java/net/fretux/ascend/command/AscendCommand.java @@ -62,7 +62,10 @@ public class AscendCommand { String attr = StringArgumentType.getString(ctx, "attribute"); int value = IntegerArgumentType.getInteger(ctx, "value"); target.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { - stats.setAttributeLevel(attr, value); + if (!stats.setAttributeLevel(attr, value)) { + ctx.getSource().sendFailure(Component.literal("Unknown Ascend attribute: " + attr)); + return; + } StatEffects.applyAll(target); PlayerStatsProvider.sync(target); ctx.getSource().sendSuccess(() -> diff --git a/src/main/java/net/fretux/ascend/compat/AscendMMCompat.java b/src/main/java/net/fretux/ascend/compat/AscendMMCompat.java index dd07cea..ab87a6f 100644 --- a/src/main/java/net/fretux/ascend/compat/AscendMMCompat.java +++ b/src/main/java/net/fretux/ascend/compat/AscendMMCompat.java @@ -1,9 +1,43 @@ package net.fretux.ascend.compat; +import net.minecraft.world.entity.player.Player; import net.minecraftforge.fml.ModList; -public class AscendMMCompat { +/** Optional Mind & Motion bridge with all foreign types isolated in Loaded. */ +public final class AscendMMCompat { + private AscendMMCompat() {} + public static boolean isMindMotionPresent() { return ModList.get().isLoaded("mindmotion"); } + + public static boolean consumeClearMind(Player player) { + return isMindMotionPresent() && Loaded.consumeClearMind(player); + } + + public static void restoreMind(Player player) { + if (isMindMotionPresent()) Loaded.restoreMind(player); + } + + private static final class Loaded { + private static boolean consumeClearMind(Player player) { + final boolean[] success = {false}; + player.getCapability(net.fretux.mindmotion.player.PlayerCapabilityProvider.SANITY).ifPresent(sanity -> { + if (sanity.getMaxSanity() > 0 && sanity.getSanity() / sanity.getMaxSanity() >= 0.90f) { + sanity.setSanity(0); + sanity.setInsanity(0); + success[0] = true; + } + }); + return success[0]; + } + + private static void restoreMind(Player player) { + player.getCapability(net.fretux.mindmotion.player.PlayerCapabilityProvider.SANITY).ifPresent(sanity -> { + sanity.setSanity(sanity.getMaxSanity()); + sanity.setInsanity(0); + }); + } + + } } diff --git a/src/main/java/net/fretux/ascend/compat/IronsSpellbooksCompat.java b/src/main/java/net/fretux/ascend/compat/IronsSpellbooksCompat.java index e072997..61a7856 100644 --- a/src/main/java/net/fretux/ascend/compat/IronsSpellbooksCompat.java +++ b/src/main/java/net/fretux/ascend/compat/IronsSpellbooksCompat.java @@ -1,8 +1,7 @@ package net.fretux.ascend.compat; -import net.fretux.ascend.player.StatEffects; -import io.redspace.ironsspellbooks.api.registry.AttributeRegistry; import net.fretux.ascend.player.PlayerStatsProvider; +import net.fretux.ascend.player.StatEffects; import net.minecraft.world.entity.ai.attributes.Attribute; import net.minecraft.world.entity.ai.attributes.AttributeInstance; import net.minecraft.world.entity.ai.attributes.AttributeModifier; @@ -11,250 +10,63 @@ import net.minecraftforge.fml.ModList; import java.util.UUID; -public class IronsSpellbooksCompat { - private static final UUID INT_MAX_MANA_UUID = - UUID.fromString("11111111-1111-4111-8111-111111111111"); - private static final UUID INT_SPELL_POWER_UUID = - UUID.fromString("22222222-2222-4222-8222-222222222222"); - private static final UUID INT_MANA_REGEN_UUID = - UUID.fromString("33333333-3333-4333-8333-333333333333"); - private static final UUID INT_COOLDOWN_REDUCTION_UUID = - UUID.fromString("44444444-4444-4444-8444-444444444444"); - private static final UUID CHA_MAX_MANA_UUID = - UUID.fromString("55555555-5555-4555-8555-555555555555"); - // reserved for future spell-trade hooks - private static final UUID CHA_SPELL_TRADE_UUID = - UUID.fromString("66666666-6666-4666-8666-666666666666"); - private static final UUID MAG_SPELL_POWER_UUID = - UUID.fromString("77777777-7777-4777-8777-777777777777"); - private static final UUID MAG_MANA_REGEN_UUID = - UUID.fromString("88888888-8888-4888-8888-888888888888"); - private static final UUID MAG_COOLDOWN_REDUCTION_UUID = - UUID.fromString("99999999-9999-4999-8999-999999999999"); - private static final UUID WILL_MANA_COST_UUID = - UUID.fromString("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); - private static final UUID CHA_SUPPORT_POWER_UUID = - UUID.fromString("bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"); - private static final UUID CHA_SUMMON_HEALTH_UUID = - UUID.fromString("cccccccc-cccc-4ccc-8ccc-cccccccccccc"); - private static final UUID CHA_SUMMON_DAMAGE_UUID = - UUID.fromString("dddddddd-dddd-4ddd-8ddd-dddddddddddd"); +/** Optional Iron's Spellbooks bridge. All Iron's classes are isolated in Loaded. */ +public final class IronsSpellbooksCompat { + private static final UUID MIND_MANA = UUID.fromString("11111111-1111-4111-8111-111111111111"); + private static final UUID INT_SPELL_POWER = UUID.fromString("22222222-2222-4222-8222-222222222222"); + private static final UUID MIND_REGEN = UUID.fromString("33333333-3333-4333-8333-333333333333"); + private static final UUID INT_COOLDOWN = UUID.fromString("44444444-4444-4444-8444-444444444444"); + private static final UUID LEGACY_CHARISMA_MANA = UUID.fromString("55555555-5555-4555-8555-555555555555"); + private static final UUID LEGACY_MAGIC_POWER = UUID.fromString("77777777-7777-4777-8777-777777777777"); + private static final UUID LEGACY_MAGIC_REGEN = UUID.fromString("88888888-8888-4888-8888-888888888888"); + private static final UUID LEGACY_MAGIC_COOLDOWN = UUID.fromString("99999999-9999-4999-8999-999999999999"); + private static final UUID WILL_CAST_TIME = UUID.fromString("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"); + private static final UUID CHARISMA_SUPPORT = UUID.fromString("bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"); + private static final UUID CHARISMA_SUMMON_DAMAGE = UUID.fromString("dddddddd-dddd-4ddd-8ddd-dddddddddddd"); - - private IronsSpellbooksCompat() { - } - - public static boolean isLoaded() { - return ModList.get().isLoaded("irons_spellbooks"); - } + private IronsSpellbooksCompat() {} + public static boolean isLoaded() { return ModList.get().isLoaded("irons_spellbooks"); } public static void applyAll(Player player) { if (!isLoaded()) return; - - player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { - int intelligence = stats.getAttributeLevel("intelligence"); - int charisma = stats.getAttributeLevel("charisma"); - int magicScaling = stats.getAttributeLevel("magic_scaling"); - int willpower = stats.getAttributeLevel("willpower"); - - Loaded.applyIntelligence(player, intelligence); - Loaded.applyCharisma(player, charisma); - Loaded.applyMagicScaling(player, magicScaling); - Loaded.applyWillpowerCastEfficiency(player, willpower); - }); + player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> Loaded.apply(player, + stats.getAttributeLevel("mind"), stats.getAttributeLevel("intelligence"), + stats.getAttributeLevel("willpower"), stats.getAttributeLevel("charisma"))); } private static final class Loaded { - private Loaded() { + private static void apply(Player player, int mind, int intelligence, int willpower, int charisma) { + Attribute maxMana = io.redspace.ironsspellbooks.api.registry.AttributeRegistry.MAX_MANA.get(); + Attribute manaRegen = io.redspace.ironsspellbooks.api.registry.AttributeRegistry.MANA_REGEN.get(); + Attribute spellPower = io.redspace.ironsspellbooks.api.registry.AttributeRegistry.SPELL_POWER.get(); + Attribute cooldown = io.redspace.ironsspellbooks.api.registry.AttributeRegistry.COOLDOWN_REDUCTION.get(); + remove(player, maxMana, LEGACY_CHARISMA_MANA); + remove(player, spellPower, LEGACY_MAGIC_POWER); + remove(player, manaRegen, LEGACY_MAGIC_REGEN); + remove(player, cooldown, LEGACY_MAGIC_COOLDOWN); + update(player, maxMana, MIND_MANA, "Ascend Mind max mana", StatEffects.getMindManaBonus(mind), AttributeModifier.Operation.ADDITION); + update(player, manaRegen, MIND_REGEN, "Ascend Mind mana regeneration", StatEffects.getMindManaRegenBonus(mind), AttributeModifier.Operation.MULTIPLY_BASE); + update(player, spellPower, INT_SPELL_POWER, "Ascend Intelligence spell power", StatEffects.getIntelligenceSpellPowerBonus(intelligence), AttributeModifier.Operation.MULTIPLY_BASE); + update(player, cooldown, INT_COOLDOWN, "Ascend Intelligence cooldown reduction", StatEffects.getIntelligenceCooldownBonus(intelligence), AttributeModifier.Operation.MULTIPLY_BASE); + update(player, io.redspace.ironsspellbooks.api.registry.AttributeRegistry.CAST_TIME_REDUCTION.get(), WILL_CAST_TIME, + "Ascend Willpower cast-time reduction", StatEffects.getWillpowerCastTimeReduction(willpower), AttributeModifier.Operation.MULTIPLY_BASE); + update(player, io.redspace.ironsspellbooks.api.registry.AttributeRegistry.HOLY_SPELL_POWER.get(), CHARISMA_SUPPORT, + "Ascend Charisma support spell power", StatEffects.getCharismaAllyDamageBonus(charisma), AttributeModifier.Operation.MULTIPLY_BASE); + update(player, io.redspace.ironsspellbooks.api.registry.AttributeRegistry.SUMMON_DAMAGE.get(), CHARISMA_SUMMON_DAMAGE, + "Ascend Charisma summon damage", StatEffects.getCharismaAllyDamageBonus(charisma), AttributeModifier.Operation.MULTIPLY_BASE); } - private static void applyIntelligence(Player player, int intelligence) { - if (intelligence <= 0) { - clearIntelligence(player); - return; - } - applyAddModifier( - player, - (Attribute) AttributeRegistry.MAX_MANA.get(), - INT_MAX_MANA_UUID, - "Ascend INT max mana", - StatEffects.getIntelligenceManaBonus(intelligence) - ); - double regenBonus = StatEffects.getIntelligenceAnvilCostReduction(intelligence); - applyMultiplyBaseModifier( - player, - (Attribute) AttributeRegistry.MANA_REGEN.get(), - INT_MANA_REGEN_UUID, - "Ascend INT mana regen", - regenBonus - ); - } - - private static void clearIntelligence(Player player) { - removeModifier(player, (Attribute) AttributeRegistry.MAX_MANA.get(), INT_MAX_MANA_UUID); - removeModifier(player, (Attribute) AttributeRegistry.MANA_REGEN.get(), INT_MANA_REGEN_UUID); - } - - private static void applyWillpowerCastEfficiency(Player player, int willpower) { - clearWillpower(player); - if (willpower <= 0) { - return; - } - double reduction = StatEffects.getWillpowerCastTimeReduction(willpower); - Attribute attr = (Attribute) AttributeRegistry.CAST_TIME_REDUCTION.get(); - if (attr == null) { - return; - } - AttributeInstance inst = player.getAttribute(attr); - if (inst == null) { - return; - } - inst.removeModifier(WILL_MANA_COST_UUID); - if (reduction > 0.0d) { - inst.addTransientModifier(new AttributeModifier( - WILL_MANA_COST_UUID, - "Ascend WILL casting time reduction", - -reduction, - AttributeModifier.Operation.MULTIPLY_BASE - )); - } - } - - private static void clearWillpower(Player player) { - Attribute attr = (Attribute) AttributeRegistry.CAST_TIME_REDUCTION.get(); - if (attr == null) return; - removeModifier(player, attr, WILL_MANA_COST_UUID); - } - - private static void applyCharisma(Player player, int charisma) { - clearCharisma(player); - if (charisma <= 0) { - return; - } - double manaPerPoint = 1.5d; - double bonusMana = Math.min(charisma * manaPerPoint, 150.0d); - applyAddModifier( - player, - (Attribute) AttributeRegistry.MAX_MANA.get(), - CHA_MAX_MANA_UUID, - "Ascend CHA max mana", - bonusMana - ); - double supportBonus = StatEffects.getCharismaAllyDamageBonus(charisma); - Attribute supportAttr = (Attribute) AttributeRegistry.HOLY_SPELL_POWER.get(); - if (supportAttr != null && supportBonus > 0.0d) { - applyMultiplyBaseModifier( - player, - supportAttr, - CHA_SUPPORT_POWER_UUID, - "Ascend CHA support spell power", - supportBonus - ); - } - double minionHpBonus = Math.min(StatEffects.getCharismaAllyHealthBonus(charisma) * 1.25d, 0.25d); - double minionDmgBonus = Math.min(StatEffects.getCharismaAllyDamageBonus(charisma) * 1.25d, 0.25d); - Attribute summonHpAttr = null; - Attribute summonDmgAttr = (Attribute) AttributeRegistry.SUMMON_DAMAGE.get(); - if (summonHpAttr != null && minionHpBonus > 0.0d) { - applyMultiplyBaseModifier( - player, - summonHpAttr, - CHA_SUMMON_HEALTH_UUID, - "Ascend CHA summon max health", - minionHpBonus - ); - } - if (summonDmgAttr != null && minionDmgBonus > 0.0d) { - applyMultiplyBaseModifier( - player, - summonDmgAttr, - CHA_SUMMON_DAMAGE_UUID, - "Ascend CHA summon damage", - minionDmgBonus - ); - } - } - - private static void clearCharisma(Player player) { - removeModifier(player, (Attribute) AttributeRegistry.MAX_MANA.get(), CHA_MAX_MANA_UUID); - Attribute supportAttr = (Attribute) AttributeRegistry.HOLY_SPELL_POWER.get(); - Attribute summonHpAttr = null; - Attribute summonDmgAttr = (Attribute) AttributeRegistry.SUMMON_DAMAGE.get(); - if (supportAttr != null) { - removeModifier(player, supportAttr, CHA_SUPPORT_POWER_UUID); - } - if (summonHpAttr != null) { - removeModifier(player, summonHpAttr, CHA_SUMMON_HEALTH_UUID); - } - if (summonDmgAttr != null) { - removeModifier(player, summonDmgAttr, CHA_SUMMON_DAMAGE_UUID); - } - } - - private static void applyMagicScaling(Player player, int magicScaling) { - if (magicScaling <= 0) { - clearMagicScaling(player); - return; - } - double spBonus = Math.min(StatEffects.getMagicScalingMultiplier(magicScaling) - 1.0d, 0.35d); - applyMultiplyBaseModifier( - player, - (Attribute) AttributeRegistry.SPELL_POWER.get(), - MAG_SPELL_POWER_UUID, - "Ascend Magic Scaling spell power", - spBonus - ); - double cdrBonus = Math.min((StatEffects.getMagicScalingMultiplier(magicScaling) - 1.0d) * 0.4d, 0.14d); - applyMultiplyBaseModifier( - player, - (Attribute) AttributeRegistry.COOLDOWN_REDUCTION.get(), - MAG_COOLDOWN_REDUCTION_UUID, - "Ascend Magic Scaling cooldown reduction", - cdrBonus - ); - } - - private static void clearMagicScaling(Player player) { - removeModifier(player, (Attribute) AttributeRegistry.SPELL_POWER.get(), MAG_SPELL_POWER_UUID); - removeModifier(player, (Attribute) AttributeRegistry.COOLDOWN_REDUCTION.get(), MAG_COOLDOWN_REDUCTION_UUID); - } - - private static void applyAddModifier(Player player, Attribute attribute, UUID id, String name, double value) { + private static void update(Player player, Attribute attribute, UUID id, String name, double value, AttributeModifier.Operation operation) { if (attribute == null) return; - AttributeInstance inst = player.getAttribute(attribute); - if (inst == null) return; - inst.removeModifier(id); - if (value != 0.0d) { - inst.addTransientModifier(new AttributeModifier( - id, - name, - value, - AttributeModifier.Operation.ADDITION - )); - } + AttributeInstance instance = player.getAttribute(attribute); + if (instance == null) return; + instance.removeModifier(id); + if (value != 0) instance.addTransientModifier(new AttributeModifier(id, name, value, operation)); } - - private static void applyMultiplyBaseModifier(Player player, Attribute attribute, UUID id, String name, double value) { + private static void remove(Player player, Attribute attribute, UUID id) { if (attribute == null) return; - AttributeInstance inst = player.getAttribute(attribute); - if (inst == null) return; - inst.removeModifier(id); - if (value != 0.0d) { - inst.addTransientModifier(new AttributeModifier( - id, - name, - value, - AttributeModifier.Operation.MULTIPLY_BASE - )); - } - } - - private static void removeModifier(Player player, Attribute attribute, UUID id) { - if (attribute == null) return; - AttributeInstance inst = player.getAttribute(attribute); - if (inst != null) { - inst.removeModifier(id); - } + AttributeInstance instance = player.getAttribute(attribute); + if (instance != null) instance.removeModifier(id); } } } diff --git a/src/main/java/net/fretux/ascend/config/AscendConfig.java b/src/main/java/net/fretux/ascend/config/AscendConfig.java index 46fc993..445697d 100644 --- a/src/main/java/net/fretux/ascend/config/AscendConfig.java +++ b/src/main/java/net/fretux/ascend/config/AscendConfig.java @@ -4,65 +4,85 @@ import net.minecraftforge.common.ForgeConfigSpec; import org.apache.commons.lang3.tuple.Pair; public class AscendConfig { - public static class Common { - public final ForgeConfigSpec.IntValue xpPerMobKill; - public final ForgeConfigSpec.IntValue xpPerBlockBreak; - public final ForgeConfigSpec.IntValue xpPerCraft; - public final ForgeConfigSpec.IntValue xpPerSmelt; - public final ForgeConfigSpec.IntValue xpPerTrade; - public final ForgeConfigSpec.IntValue xpPerDamageTaken; - public final ForgeConfigSpec.IntValue xpPerMovement; - public final ForgeConfigSpec.IntValue xpPerPotion; - public final ForgeConfigSpec.IntValue pointsPerLevel; - public final ForgeConfigSpec.IntValue maxAscendLevel; - public final ForgeConfigSpec.IntValue maxAttributePoints; - - public final ForgeConfigSpec.DoubleValue xpMultiplier; - public final ForgeConfigSpec.DoubleValue attributeScalingMultiplier; + public final ForgeConfigSpec.IntValue xpPerMobKill, xpPerBlockBreak, xpPerCraft, xpPerSmelt, + xpPerTrade, xpPerDamageTaken, xpPerMovement, xpPerPotion, pointsPerLevel, + maxAscendLevel, maxAttributePoints; + public final ForgeConfigSpec.DoubleValue xpMultiplier, attributeScalingMultiplier; + public final ForgeConfigSpec.IntValue softCap1, softCap2, softCap3, softCap4; + public final ForgeConfigSpec.DoubleValue softCapGain2, softCapGain3, softCapGain4, softCapGain5; + public final ForgeConfigSpec.DoubleValue vigorMaxHealthBonus, mindMaxManaBonus, mindManaRegenBonus, + strengthArmorPenetration, armorPenetrationCap, strengthKnockbackBonus, agilityMovementSpeedBonus, + intelligenceSpellPowerBonus, intelligenceCooldownBonus, willpowerCastTimeReduction, + arcaneBeneficialDurationBonus, arcaneHarmfulDurationBonus; + public final ForgeConfigSpec.DoubleValue gradeE, gradeD, gradeC, gradeB, gradeA, gradeS; Common(ForgeConfigSpec.Builder builder) { builder.push("Ascend Progression"); - - xpPerMobKill = builder.comment("Ascend XP gained for killing a mob.") - .defineInRange("xpPerMobKill", 10, 0, 1000); - xpPerBlockBreak = builder.comment("Ascend XP gained for breaking a block (ore/stone/log).") - .defineInRange("xpPerBlockBreak", 2, 0, 1000); - xpPerCraft = builder.comment("Ascend XP gained for crafting items.") - .defineInRange("xpPerCraft", 1, 0, 1000); - xpPerSmelt = builder.comment("Ascend XP gained for smelting items.") - .defineInRange("xpPerSmelt", 1, 0, 1000); - xpPerTrade = builder.comment("Ascend XP gained for trading with villagers.") - .defineInRange("xpPerTrade", 5, 0, 1000); - xpPerDamageTaken = builder.comment("Ascend XP gained for taking damage.") - .defineInRange("xpPerDamageTaken", 2, 0, 1000); - xpPerMovement = builder.comment("Ascend XP gained for movement actions.") - .defineInRange("xpPerMovement", 1, 0, 1000); - xpPerPotion = builder.comment("Ascend XP gained for using a potion.") - .defineInRange("xpPerPotion", 3, 0, 1000); - - pointsPerLevel = builder.comment("Unspent points gained per Ascend level.") - .defineInRange("pointsPerLevel", 15, 1, 1000); - maxAscendLevel = builder.comment("Maximum Ascend level.") - .defineInRange("maxAscendLevel", 20, 1, 100); - maxAttributePoints = builder.comment("Maximum points in a single attribute.") - .defineInRange("maxAttributePoints", 100, 1, 1000); - - xpMultiplier = builder.comment("Global multiplier for all Ascend XP gain.") - .defineInRange("xpMultiplier", 1.0D, 0.0D, 100.0D); - attributeScalingMultiplier = builder.comment("Global multiplier for all attribute-based stat bonuses (damage, mana, speed, etc.).") - .defineInRange("attributeScalingMultiplier", 1.0D, 0.1D, 10.0D); - + xpPerMobKill = intValue(builder, "xpPerMobKill", "Ascend XP gained for killing a mob.", 10, 0, 1000); + xpPerBlockBreak = intValue(builder, "xpPerBlockBreak", "Ascend XP gained for breaking ore, stone, or logs.", 2, 0, 1000); + xpPerCraft = intValue(builder, "xpPerCraft", "Ascend XP gained for crafting.", 1, 0, 1000); + xpPerSmelt = intValue(builder, "xpPerSmelt", "Ascend XP gained for smelting.", 1, 0, 1000); + xpPerTrade = intValue(builder, "xpPerTrade", "Ascend XP gained for villager trading.", 5, 0, 1000); + xpPerDamageTaken = intValue(builder, "xpPerDamageTaken", "Ascend XP gained from dangerous survival conditions.", 2, 0, 1000); + xpPerMovement = intValue(builder, "xpPerMovement", "Ascend XP gained for movement activities.", 1, 0, 1000); + xpPerPotion = intValue(builder, "xpPerPotion", "Ascend XP gained for potion use.", 3, 0, 1000); + pointsPerLevel = intValue(builder, "pointsPerLevel", "Attribute points gained per Ascend level.", 15, 1, 1000); + maxAscendLevel = intValue(builder, "maxAscendLevel", "Maximum Ascend level.", 20, 1, 100); + maxAttributePoints = intValue(builder, "maxAttributePoints", "Maximum investment in one attribute.", 100, 1, 1000); + xpMultiplier = doubleValue(builder, "xpMultiplier", "Global Ascend XP multiplier.", 1.0, 0, 100); + attributeScalingMultiplier = doubleValue(builder, "attributeScalingMultiplier", "Global multiplier applied to attribute curves.", 1.0, 0.1, 10); builder.pop(); + + builder.push("Attribute Soft Caps"); + softCap1 = intValue(builder, "firstSoftCap", "End of strong gains.", 20, 1, 1000); + softCap2 = intValue(builder, "secondSoftCap", "End of good gains.", 40, 1, 1000); + softCap3 = intValue(builder, "thirdSoftCap", "End of moderate gains.", 60, 1, 1000); + softCap4 = intValue(builder, "fourthSoftCap", "End of weak gains.", 80, 1, 1000); + softCapGain2 = doubleValue(builder, "secondSegmentGain", "Relative gain after the first cap.", 0.75, 0, 1); + softCapGain3 = doubleValue(builder, "thirdSegmentGain", "Relative gain after the second cap.", 0.50, 0, 1); + softCapGain4 = doubleValue(builder, "fourthSegmentGain", "Relative gain after the third cap.", 0.25, 0, 1); + softCapGain5 = doubleValue(builder, "fifthSegmentGain", "Relative gain after the fourth cap.", 0.10, 0, 1); + builder.pop(); + + builder.push("Attribute Effects"); + vigorMaxHealthBonus = doubleValue(builder, "vigorMaxHealthBonus", "Maximum extra health points from Vigor (20 = ten hearts).", 20, 0, 100); + mindMaxManaBonus = doubleValue(builder, "mindMaxManaBonus", "Maximum extra Iron's Spellbooks mana from Mind.", 200, 0, 1000); + mindManaRegenBonus = doubleValue(builder, "mindManaRegenBonus", "Maximum proportional mana regeneration bonus from Mind.", 0.30, 0, 5); + strengthArmorPenetration = doubleValue(builder, "strengthArmorPenetration", "Maximum fraction of the target's armor ignored by Strength.", 0.20, 0, 1); + armorPenetrationCap = doubleValue(builder, "armorPenetrationCap", "Default maximum armor fraction that can be ignored. Integrations may disable this cap per hit.", 0.30, 0, 1); + strengthKnockbackBonus = doubleValue(builder, "strengthKnockbackBonus", "Maximum additive attack knockback from Strength.", 0.15, 0, 2); + agilityMovementSpeedBonus = doubleValue(builder, "agilityMovementSpeedBonus", "Maximum proportional movement speed bonus from Agility.", 0.25, 0.25, 1); + intelligenceSpellPowerBonus = doubleValue(builder, "intelligenceSpellPowerBonus", "Maximum proportional spell power bonus from Intelligence.", 0.35, 0, 5); + intelligenceCooldownBonus = doubleValue(builder, "intelligenceCooldownBonus", "Maximum proportional spell cooldown reduction from Intelligence.", 0.15, 0, 1); + willpowerCastTimeReduction = doubleValue(builder, "willpowerCastTimeReduction", "Maximum Iron's Spellbooks cast-time reduction from Willpower.", 0.20, 0, 0.95); + arcaneBeneficialDurationBonus = doubleValue(builder, "arcaneBeneficialDurationBonus", "Maximum duration bonus for finite beneficial effects received by the player.", 0.30, 0, 5); + arcaneHarmfulDurationBonus = doubleValue(builder, "arcaneHarmfulDurationBonus", "Maximum duration bonus for finite harmful effects applied by the player.", 0.40, 0, 5); + builder.pop(); + + builder.push("Weapon Scaling Grades"); + gradeE = grade(builder, "gradeE", 0.10); gradeD = grade(builder, "gradeD", 0.20); + gradeC = grade(builder, "gradeC", 0.30); gradeB = grade(builder, "gradeB", 0.40); + gradeA = grade(builder, "gradeA", 0.50); gradeS = grade(builder, "gradeS", 0.60); + builder.pop(); + } + + private static ForgeConfigSpec.IntValue intValue(ForgeConfigSpec.Builder b, String key, String comment, int value, int min, int max) { + return b.comment(comment).defineInRange(key, value, min, max); + } + private static ForgeConfigSpec.DoubleValue doubleValue(ForgeConfigSpec.Builder b, String key, String comment, double value, double min, double max) { + return b.comment(comment).defineInRange(key, value, min, max); + } + private static ForgeConfigSpec.DoubleValue grade(ForgeConfigSpec.Builder b, String key, double value) { + return doubleValue(b, key, "Maximum base-weapon-damage contribution for this grade.", value, 0, 2); } } public static final ForgeConfigSpec COMMON_SPEC; public static final Common COMMON; - static { - final Pair specPair = new ForgeConfigSpec.Builder().configure(Common::new); - COMMON_SPEC = specPair.getRight(); - COMMON = specPair.getLeft(); + Pair pair = new ForgeConfigSpec.Builder().configure(Common::new); + COMMON = pair.getLeft(); + COMMON_SPEC = pair.getRight(); } } diff --git a/src/main/java/net/fretux/ascend/event/ActivityEvents.java b/src/main/java/net/fretux/ascend/event/ActivityEvents.java index c9b448c..a448972 100644 --- a/src/main/java/net/fretux/ascend/event/ActivityEvents.java +++ b/src/main/java/net/fretux/ascend/event/ActivityEvents.java @@ -5,9 +5,6 @@ import net.fretux.ascend.config.AscendConfig; import net.fretux.ascend.player.PlayerStatsProvider; import net.fretux.ascend.player.StatEffects; import net.minecraft.core.BlockPos; -import net.minecraft.core.particles.ParticleTypes; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.sounds.SoundEvents; import net.minecraft.world.effect.MobEffectCategory; import net.minecraft.world.entity.ai.gossip.GossipType; import net.minecraft.world.entity.npc.Villager; @@ -24,7 +21,6 @@ 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; import net.minecraft.world.entity.animal.IronGolem; import net.minecraft.world.entity.animal.SnowGolem; @@ -76,7 +72,7 @@ public class ActivityEvents { } @SubscribeEvent - public static void onPlayerTickFortitude(TickEvent.PlayerTickEvent event) { + public static void onPlayerTickSurvivalXP(TickEvent.PlayerTickEvent event) { Player player = event.player; if (player.level().isClientSide || event.phase != TickEvent.Phase.END) return; if (player.tickCount % 100 != 0) return; @@ -133,13 +129,6 @@ public class ActivityEvents { Player player = event.player; if (player.level().isClientSide || event.phase != TickEvent.Phase.END) return; player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { - int willpower = stats.getAttributeLevel("willpower"); - if (willpower > 0 && player.tickCount % 80 == 0) { - float regen = StatEffects.getWillpowerHealthRegen(willpower); - if (regen > 0.0f && player.getHealth() < player.getMaxHealth()) { - player.heal(regen); - } - } if (player.tickCount % 200 != 0) return; boolean hasNegativeEffect = player.getActiveEffects().stream() .anyMatch(e -> e.getEffect().getCategory() == MobEffectCategory.HARMFUL); @@ -252,63 +241,6 @@ public class ActivityEvents { } } - @SubscribeEvent - public static void onPlayerDealsDamage(LivingDamageEvent event) { - if (!(event.getSource().getEntity() instanceof Player player)) return; - if (player.level().isClientSide) return; - player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { - float damage = event.getAmount(); - var stack = player.getMainHandItem(); - if (!stack.isEmpty()) { - double multiplier = 1.0d; - if (stack.getItem() instanceof net.minecraft.world.item.AxeItem) { - int heavy = stats.getAttributeLevel("heavy_scaling"); - multiplier = StatEffects.getHeavyWeaponScalingMultiplier(heavy); - } else if (stack.getItem() instanceof net.minecraft.world.item.SwordItem) { - int medium = stats.getAttributeLevel("medium_scaling"); - multiplier = StatEffects.getWeaponScalingMultiplier(medium); - } else { - int light = stats.getAttributeLevel("light_scaling"); - multiplier = StatEffects.getLightWeaponScalingMultiplier(light); - } - damage *= (float) multiplier; - } - event.setAmount(damage); - }); - } - - @SubscribeEvent - public static void onPlayerEvasion(LivingHurtEvent event) { - if (!(event.getEntity() instanceof Player player)) return; - if (player.level().isClientSide) return; - if (event.getAmount() <= 0) return; - player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { - int agility = stats.getAttributeLevel("agility"); - if (agility <= 0) return; - double evadeChance = StatEffects.getAgilityEvasionChance(agility); - if (player.getRandom().nextDouble() < evadeChance) { - float original = event.getAmount(); - event.setAmount(original * 0.1f); - if (player.level() instanceof ServerLevel serverLevel) { - for (int i = 0; i < 8; i++) { - double offsetX = (serverLevel.random.nextDouble() - 0.5) * 0.6; - double offsetY = serverLevel.random.nextDouble() * 1.5; - double offsetZ = (serverLevel.random.nextDouble() - 0.5) * 0.6; - serverLevel.sendParticles( - ParticleTypes.CLOUD, - player.getX() + offsetX, - player.getY() + offsetY, - player.getZ() + offsetZ, - 1, - 0, 0, 0, 0.0 - ); - } - } - player.playSound(SoundEvents.ENDERMAN_TELEPORT, 0.4f, 1.5f); - } - }); - } - private static boolean isCharismaAlly(Player player, LivingEntity entity) { if (entity instanceof TamableAnimal tame) { return tame.isTame() && tame.isOwnedBy(player); diff --git a/src/main/java/net/fretux/ascend/event/FortitudeDefenseEvents.java b/src/main/java/net/fretux/ascend/event/FortitudeDefenseEvents.java deleted file mode 100644 index ecefa6e..0000000 --- a/src/main/java/net/fretux/ascend/event/FortitudeDefenseEvents.java +++ /dev/null @@ -1,49 +0,0 @@ -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.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 chance = StatEffects.getFortitudeEffectGuardChance(fortitude); - 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 chance = StatEffects.getFortitudeCleanseChance(fortitude); - 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())); - }); - } -} diff --git a/src/main/java/net/fretux/ascend/event/IntelligenceAnvilEvents.java b/src/main/java/net/fretux/ascend/event/IntelligenceAnvilEvents.java deleted file mode 100644 index 65b6202..0000000 --- a/src/main/java/net/fretux/ascend/event/IntelligenceAnvilEvents.java +++ /dev/null @@ -1,31 +0,0 @@ -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/event/IntelligenceEnchantmentEvents.java b/src/main/java/net/fretux/ascend/event/IntelligenceEnchantmentEvents.java deleted file mode 100644 index 8434a57..0000000 --- a/src/main/java/net/fretux/ascend/event/IntelligenceEnchantmentEvents.java +++ /dev/null @@ -1,41 +0,0 @@ -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.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 reduction = StatEffects.getIntelligenceAnvilCostReduction(intelligence); - int newLevel = (int) Math.max(1, Math.floor(original * (1.0d - reduction))); - event.setEnchantLevel(newLevel); - }); - } -} diff --git a/src/main/java/net/fretux/ascend/event/IntelligencePotionEvents.java b/src/main/java/net/fretux/ascend/event/IntelligencePotionEvents.java deleted file mode 100644 index 8b8f887..0000000 --- a/src/main/java/net/fretux/ascend/event/IntelligencePotionEvents.java +++ /dev/null @@ -1,42 +0,0 @@ -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.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; - 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(); - double durBonus = StatEffects.getIntelligenceAnvilCostReduction(intel); - int newDuration = (int) (baseDuration * (1.0d + durBonus)); - if (newDuration <= baseDuration) return; - player.removeEffect(inst.getEffect()); - player.addEffect(new MobEffectInstance( - inst.getEffect(), - newDuration, - inst.getAmplifier(), - inst.isAmbient(), - inst.isVisible(), - inst.showIcon() - )); - }); - } -} diff --git a/src/main/java/net/fretux/ascend/event/PlayerEvents.java b/src/main/java/net/fretux/ascend/event/PlayerEvents.java index db01160..c8dbaac 100644 --- a/src/main/java/net/fretux/ascend/event/PlayerEvents.java +++ b/src/main/java/net/fretux/ascend/event/PlayerEvents.java @@ -3,6 +3,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.fretux.ascend.compat.AscendMMCompat; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; @@ -52,10 +53,10 @@ public class PlayerEvents { @SubscribeEvent public static void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) { if (!event.getEntity().level().isClientSide) { - float healthBeforeFortitude = event.getEntity().getHealth(); + float healthBeforeVigor = event.getEntity().getHealth(); StatEffects.applyAll(event.getEntity()); - if (Math.abs(healthBeforeFortitude - 20.0F) < 0.001F - && event.getEntity().getMaxHealth() > healthBeforeFortitude) { + if (Math.abs(healthBeforeVigor - 20.0F) < 0.001F + && event.getEntity().getMaxHealth() > healthBeforeVigor) { event.getEntity().setHealth(event.getEntity().getMaxHealth()); PENDING_FULL_HEALTH_RESTORE.add(event.getEntity().getUUID()); } @@ -63,16 +64,34 @@ public class PlayerEvents { } } + @SubscribeEvent + public static void onPlayerRespawn(PlayerEvent.PlayerRespawnEvent event) { + if (!event.getEntity().level().isClientSide) { + StatEffects.applyAll(event.getEntity()); + PlayerStatsProvider.sync(event.getEntity()); + } + } + + @SubscribeEvent + public static void onPlayerChangedDimension(PlayerEvent.PlayerChangedDimensionEvent event) { + if (!event.getEntity().level().isClientSide) { + StatEffects.applyAll(event.getEntity()); + PlayerStatsProvider.sync(event.getEntity()); + } + } + @SubscribeEvent public static void onPlayerTick(TickEvent.PlayerTickEvent event) { Player player = event.player; - if (player.level().isClientSide - || event.phase != TickEvent.Phase.END - || !PENDING_FULL_HEALTH_RESTORE.remove(player.getUUID())) { - return; - } + if (player.level().isClientSide || event.phase != TickEvent.Phase.END) return; + if (!PENDING_FULL_HEALTH_RESTORE.remove(player.getUUID())) return; StatEffects.applyAll(player); player.setHealth(player.getMaxHealth()); PlayerStatsProvider.sync(player); } + + @SubscribeEvent + public static void onPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) { + PENDING_FULL_HEALTH_RESTORE.remove(event.getEntity().getUUID()); + } } diff --git a/src/main/java/net/fretux/ascend/event/WeaponMagicScalingEvents.java b/src/main/java/net/fretux/ascend/event/WeaponMagicScalingEvents.java deleted file mode 100644 index b7d7312..0000000 --- a/src/main/java/net/fretux/ascend/event/WeaponMagicScalingEvents.java +++ /dev/null @@ -1,80 +0,0 @@ -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.damagesource.DamageSource; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.entity.projectile.AbstractArrow; -import net.minecraft.world.entity.projectile.FireworkRocketEntity; -import net.minecraft.world.entity.projectile.Projectile; -import net.minecraft.world.entity.projectile.ThrownTrident; -import net.minecraftforge.event.entity.living.LivingHurtEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; - -@Mod.EventBusSubscriber(modid = AscendMod.MODID) -public class WeaponMagicScalingEvents { - @SubscribeEvent - public static void onLivingHurt(LivingHurtEvent event) { - if (!(event.getSource().getEntity() instanceof Player player)) return; - if (event.getEntity() == player) return; - if (player.level().isClientSide) return; - player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { - float base = event.getAmount(); - float modified = base; - if (isMagicDamage(event.getSource())) { - int magicScaling = stats.getAttributeLevel("magic_scaling"); - if (magicScaling > 0) { - double bonusMult = StatEffects.getMagicScalingMultiplier(magicScaling); - modified = (float) (modified * bonusMult); - } - } else if (isRangedDamage(event.getSource())) { - double rangedMultiplier = getRangedScalingMultiplier(stats, event.getSource()); - modified = (float) (modified * rangedMultiplier); - } - - if (modified != base) { - event.setAmount(modified); - } - }); - } - - private static boolean isMagicDamage(DamageSource source) { - String id = source.getMsgId(); - if (id == null) return false; - return id.equals("magic") - || id.equals("indirectMagic") - || id.equals("wither") - || id.equals("dragonBreath") - || id.equals("thrown") - || id.equals("potion"); - } - - private static boolean isRangedDamage(DamageSource source) { - return source.getDirectEntity() instanceof Projectile - || source.getDirectEntity() instanceof AbstractArrow - || source.getDirectEntity() instanceof ThrownTrident - || source.getDirectEntity() instanceof FireworkRocketEntity; - } - - private static double getRangedScalingMultiplier( - net.fretux.ascend.player.PlayerStats stats, - DamageSource source - ) { - double bonus = 1.0d; - int agility = stats.getAttributeLevel("agility"); - if (agility > 0) { - bonus *= StatEffects.getAgilityRangedDamageMultiplier(agility); - } - - if (source.getDirectEntity() instanceof ThrownTrident) { - int strength = stats.getAttributeLevel("strength"); - if (strength > 0) { - bonus *= StatEffects.getStrengthTridentDamageMultiplier(strength); - } - } - - return bonus; - } -} diff --git a/src/main/java/net/fretux/ascend/network/ServerboundShrineChoicePacket.java b/src/main/java/net/fretux/ascend/network/ServerboundShrineChoicePacket.java index d4e1532..a7a8694 100644 --- a/src/main/java/net/fretux/ascend/network/ServerboundShrineChoicePacket.java +++ b/src/main/java/net/fretux/ascend/network/ServerboundShrineChoicePacket.java @@ -4,7 +4,6 @@ import net.fretux.ascend.advancement.AscendAdvancements; import net.fretux.ascend.compat.AscendMMCompat; import net.fretux.ascend.item.RemembranceEssenceItem; import net.fretux.ascend.player.PlayerStatsProvider; -import net.fretux.mindmotion.player.PlayerCapabilityProvider; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; @@ -54,29 +53,19 @@ public class ServerboundShrineChoicePacket { } case "understand" -> { if (!AscendMMCompat.isMindMotionPresent()) return; - player.getCapability(PlayerCapabilityProvider.SANITY).ifPresent(sanity -> { - float sanityPercent = sanity.getSanity() / sanity.getMaxSanity(); - if (sanityPercent >= 0.90f) { - stats.addKnowledgeScaled(5); - sanity.setSanity(0); - sanity.setInsanity(0); - player.sendSystemMessage(Component.literal("YOUR MIND EXPANDS AS IT BREAKS.")); - RemembranceEssenceItem.consume(player); - AscendAdvancements.grant(player, "use_shrine"); - } else { - player.sendSystemMessage(Component.literal("YOUR MIND IS NOT YET CLEAR ENOUGH.")); - } - }); + if (AscendMMCompat.consumeClearMind(player)) { + stats.addKnowledgeScaled(5); + player.sendSystemMessage(Component.literal("YOUR MIND EXPANDS AS IT BREAKS.")); + RemembranceEssenceItem.consume(player); + AscendAdvancements.grant(player, "use_shrine"); + } else player.sendSystemMessage(Component.literal("YOUR MIND IS NOT YET CLEAR ENOUGH.")); PlayerStatsProvider.sync(player); } case "rest" -> { if (!AscendMMCompat.isMindMotionPresent()) return; if (stats.getKnowledge() >= 10) { stats.addKnowledge(-10); - player.getCapability(PlayerCapabilityProvider.SANITY).ifPresent(sanity -> { - sanity.setSanity(sanity.getMaxSanity()); - sanity.setInsanity(0); - }); + AscendMMCompat.restoreMind(player); player.sendSystemMessage(Component.literal("THE SHRINE RESTORES YOUR FRACTURED MIND.")); RemembranceEssenceItem.consume(player); AscendAdvancements.grant(player, "use_shrine"); diff --git a/src/main/java/net/fretux/ascend/network/ServerboundSpendPointPacket.java b/src/main/java/net/fretux/ascend/network/ServerboundSpendPointPacket.java index 4920b53..72ec4dd 100644 --- a/src/main/java/net/fretux/ascend/network/ServerboundSpendPointPacket.java +++ b/src/main/java/net/fretux/ascend/network/ServerboundSpendPointPacket.java @@ -33,7 +33,7 @@ public class ServerboundSpendPointPacket { boolean success = stats.spendPoints(attribute); if (success) { StatEffects.applyAll(player); - if ("fortitude".equals(attribute)) { + if ("vigor".equals(attribute)) { float gainedMaxHealth = player.getMaxHealth() - previousMaxHealth; if (gainedMaxHealth > 0.0F) { player.setHealth(Math.min(player.getHealth() + gainedMaxHealth, player.getMaxHealth())); diff --git a/src/main/java/net/fretux/ascend/player/PlayerStats.java b/src/main/java/net/fretux/ascend/player/PlayerStats.java index 04ac79a..ee6918f 100644 --- a/src/main/java/net/fretux/ascend/player/PlayerStats.java +++ b/src/main/java/net/fretux/ascend/player/PlayerStats.java @@ -1,199 +1,118 @@ package net.fretux.ascend.player; +import net.fretux.ascend.AscendMod; import net.fretux.ascend.config.AscendConfig; import net.minecraft.nbt.CompoundTag; -import java.util.HashMap; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.Map; public class PlayerStats { - private static final String[] ATTRIBUTE_KEYS = { - "strength", "agility", "fortitude", "intelligence", - "willpower", "charisma", - "light_scaling", "medium_scaling", "heavy_scaling", "magic_scaling" - }; - private final Map attributes = new HashMap<>(); - private final Map lastRespecBuild = new HashMap<>(); + public static final int CURRENT_DATA_VERSION = 2; + public static final String[] ATTRIBUTE_KEYS = {"vigor", "mind", "strength", "agility", "intelligence", "willpower", "charisma", "arcane"}; + private static final String[] LEGACY_REFUND_KEYS = {"fortitude", "light_scaling", "medium_scaling", "heavy_scaling", "magic_scaling"}; + private final Map attributes = new LinkedHashMap<>(); + private final Map lastRespecBuild = new LinkedHashMap<>(); + private int dataVersion = CURRENT_DATA_VERSION; private int ascendLevel = 1; - private int ascendXP = 0; + private int ascendXP; private int unspentPoints = getPointsPerLevel(); private int maxAscendLevel = getConfiguredMaxAscendLevel(); private int maxAttributePoints = getConfiguredMaxAttributePoints(); - private int respecCount = 0; - private boolean everInvestedWeaponScaling = false; - public PlayerStats() { - for (String attr : ATTRIBUTE_KEYS) { - attributes.put(attr, 0); - } - } + private int knowledge; + private boolean hasUsedMoonseye; + private int respecCount; + private int lastMigrationRefund; + + public PlayerStats() { for (String key : ATTRIBUTE_KEYS) attributes.put(key, 0); } public void addAscendXP(int amount) { if (amount <= 0 || ascendLevel >= getMaxAscendLevel()) return; ascendXP += amount; while (ascendLevel < getMaxAscendLevel()) { - int xpNeeded = getXPToNextAscendLevel(); - if (ascendXP < xpNeeded) break; - ascendXP -= xpNeeded; + int needed = getXPToNextAscendLevel(); + if (ascendXP < needed) break; + ascendXP -= needed; ascendLevel++; unspentPoints += getPointsPerLevel(); addKnowledgeScaled(1); } - if (ascendLevel >= getMaxAscendLevel()) { - int xpNeeded = getXPToNextAscendLevel(); - if (ascendXP > xpNeeded) { - ascendXP = xpNeeded; - } - } + if (ascendLevel >= getMaxAscendLevel()) ascendXP = Math.min(ascendXP, getXPToNextAscendLevel()); } - public int getAscendLevel() { - return ascendLevel; - } - - public int getAscendXP() { - return ascendXP; - } + public int getAscendLevel() { return ascendLevel; } + public int getAscendXP() { return ascendXP; } + public int getUnspentPoints() { return unspentPoints; } + public int getMaxAscendLevel() { return maxAscendLevel; } + public int getMaxAttributePoints() { return maxAttributePoints; } + public int getKnowledge() { return knowledge; } + public int getRespecCount() { return respecCount; } + public int getDataVersion() { return dataVersion; } + public int getLastMigrationRefund() { return lastMigrationRefund; } + public boolean hasUsedMoonseye() { return hasUsedMoonseye; } + public Map getAttributes() { return Collections.unmodifiableMap(attributes); } public int getXPToNextAscendLevel() { if (ascendLevel >= getMaxAscendLevel()) return 0; - double base = 100.0; - double growth = 1.25; - return (int) Math.ceil(base * Math.pow(growth, ascendLevel - 1) + (ascendLevel * 100)); + return (int) Math.ceil(100.0 * Math.pow(1.25, ascendLevel - 1) + ascendLevel * 100.0); } - public int getUnspentPoints() { - return unspentPoints; - } - - public void addUnspentPoints(int amount) { - unspentPoints = Math.max(0, unspentPoints + amount); - } + public void addUnspentPoints(int amount) { unspentPoints = Math.max(0, unspentPoints + amount); } public boolean spendPoints(String attribute) { - if (!attributes.containsKey(attribute)) return false; - int level = attributes.get(attribute); - if (level >= getMaxAttributePoints()) { - return false; - } - if (unspentPoints <= 0) { - return false; - } - unspentPoints -= 1; - attributes.put(attribute, level + 1); - if (isWeaponScaling(attribute)) { - everInvestedWeaponScaling = true; - } + if (!attributes.containsKey(attribute) || unspentPoints <= 0) return false; + int value = attributes.get(attribute); + if (value >= getMaxAttributePoints()) return false; + attributes.put(attribute, value + 1); + unspentPoints--; return true; } - public int getCostToUpgrade(String attribute) { - return 1; + public int getCostToUpgrade(String attribute) { return attributes.containsKey(attribute) ? 1 : 0; } + public int getAttributeLevel(String attribute) { return attributes.getOrDefault(attribute, 0); } + + public boolean setAttributeLevel(String attribute, int level) { + if (!attributes.containsKey(attribute)) return false; + attributes.put(attribute, Math.max(0, Math.min(level, getMaxAttributePoints()))); + return true; } - public int getAttributeLevel(String attribute) { - return attributes.getOrDefault(attribute, 0); - } - - public int getMaxAscendLevel() { - return maxAscendLevel; - } - - public int getMaxAttributePoints() { - return maxAttributePoints; - } - - public void setAttributeLevel(String attribute, int level) { - if (!attributes.containsKey(attribute)) return; - int clamped = Math.max(0, Math.min(level, getMaxAttributePoints())); - attributes.put(attribute, clamped); - if (clamped > 0 && isWeaponScaling(attribute)) { - everInvestedWeaponScaling = true; - } - } - - public int getMaxMana() { - int intelligence = getAttributeLevel("intelligence"); - int base = 50; - int perPoint = 3; - return base + intelligence * perPoint; - } - - private int knowledge = 0; - private boolean hasUsedMoonseye = false; // Moonseye Tome First Use - - public int getKnowledge() { - return knowledge; - } - - public void addKnowledge(int amount) { - 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 addKnowledge(int amount) { knowledge = Math.max(0, knowledge + amount); } + public int addKnowledgeScaled(int amount) { addKnowledge(amount); return amount; } public void refundAllPoints() { - int totalSpent = 0; - Map refundedBuild = new HashMap<>(); + int total = 0; + lastRespecBuild.clear(); for (String key : ATTRIBUTE_KEYS) { - int lvl = attributes.getOrDefault(key, 0); - totalSpent += lvl; - refundedBuild.put(key, lvl); + int value = attributes.getOrDefault(key, 0); + total += value; + lastRespecBuild.put(key, value); attributes.put(key, 0); } - if (totalSpent > 0) { - lastRespecBuild.clear(); - lastRespecBuild.putAll(refundedBuild); - respecCount++; - } - unspentPoints += totalSpent; - } - - public int getRespecCount() { - return respecCount; - } - - public boolean hasEverInvestedWeaponScaling() { - return everInvestedWeaponScaling; + if (total > 0) respecCount++; + unspentPoints += total; } public boolean hasNearlyRestoredLastBuild() { if (lastRespecBuild.isEmpty()) return false; - int originalTotal = 0; - int currentTotal = 0; - int totalDifference = 0; + int originalTotal = 0, currentTotal = 0, difference = 0; for (String key : ATTRIBUTE_KEYS) { int original = lastRespecBuild.getOrDefault(key, 0); int current = attributes.getOrDefault(key, 0); originalTotal += original; currentTotal += current; - totalDifference += Math.abs(original - current); + difference += Math.abs(original - current); } - int allowedDifference = Math.max(2, (int) Math.ceil(originalTotal * 0.05D)); - return originalTotal > 0 && currentTotal > 0 - && currentTotal >= originalTotal - allowedDifference - && totalDifference <= allowedDifference; - } - - public boolean hasUsedMoonseye() { - return hasUsedMoonseye; - } - - public void setHasUsedMoonseye(boolean used) { - this.hasUsedMoonseye = used; + int allowed = Math.max(2, (int) Math.ceil(originalTotal * 0.05)); + return originalTotal > 0 && currentTotal >= originalTotal - allowed && difference <= allowed; } + public void setHasUsedMoonseye(boolean used) { hasUsedMoonseye = used; } public CompoundTag serializeNBT() { CompoundTag tag = new CompoundTag(); + tag.putInt("DataVersion", dataVersion); tag.putInt("AscendLevel", ascendLevel); tag.putInt("AscendXP", ascendXP); tag.putInt("UnspentPoints", unspentPoints); @@ -202,82 +121,63 @@ public class PlayerStats { tag.putInt("Knowledge", knowledge); tag.putBoolean("HasUsedMoonseye", hasUsedMoonseye); tag.putInt("RespecCount", respecCount); - tag.putBoolean("EverInvestedWeaponScaling", everInvestedWeaponScaling); CompoundTag attrTag = new CompoundTag(); - for (Map.Entry entry : attributes.entrySet()) { - attrTag.putInt(entry.getKey(), entry.getValue()); - } + attributes.forEach(attrTag::putInt); tag.put("Attributes", attrTag); - CompoundTag respecBuildTag = new CompoundTag(); - for (Map.Entry entry : lastRespecBuild.entrySet()) { - respecBuildTag.putInt(entry.getKey(), entry.getValue()); - } - tag.put("LastRespecBuild", respecBuildTag); + CompoundTag respecTag = new CompoundTag(); + lastRespecBuild.forEach(respecTag::putInt); + tag.put("LastRespecBuild", respecTag); return tag; } public void deserializeNBT(CompoundTag tag) { - maxAscendLevel = tag.contains("MaxAscendLevel") - ? Math.max(1, tag.getInt("MaxAscendLevel")) - : getConfiguredMaxAscendLevel(); - maxAttributePoints = tag.contains("MaxAttributePoints") - ? Math.max(1, tag.getInt("MaxAttributePoints")) - : getConfiguredMaxAttributePoints(); - ascendLevel = tag.contains("AscendLevel") ? tag.getInt("AscendLevel") : 1; - ascendLevel = Math.max(1, Math.min(ascendLevel, getMaxAscendLevel())); - ascendXP = tag.contains("AscendXP") ? tag.getInt("AscendXP") : 0; - if (tag.contains("UnspentPoints")) { - unspentPoints = Math.max(0, tag.getInt("UnspentPoints")); - } else { - unspentPoints = getPointsPerLevel(); - } - if (tag.contains("Attributes")) { - CompoundTag attrTag = tag.getCompound("Attributes"); - for (String key : attributes.keySet()) { - int loaded = attrTag.getInt(key); - attributes.put(key, Math.max(0, Math.min(loaded, getMaxAttributePoints()))); - } - } + lastMigrationRefund = 0; + maxAscendLevel = tag.contains("MaxAscendLevel") ? Math.max(1, tag.getInt("MaxAscendLevel")) : getConfiguredMaxAscendLevel(); + maxAttributePoints = tag.contains("MaxAttributePoints") ? Math.max(1, tag.getInt("MaxAttributePoints")) : getConfiguredMaxAttributePoints(); + ascendLevel = Math.max(1, Math.min(tag.contains("AscendLevel") ? tag.getInt("AscendLevel") : 1, maxAscendLevel)); + ascendXP = Math.max(0, tag.contains("AscendXP") ? tag.getInt("AscendXP") : 0); + unspentPoints = tag.contains("UnspentPoints") ? Math.max(0, tag.getInt("UnspentPoints")) : getPointsPerLevel(); + knowledge = Math.max(0, tag.getInt("Knowledge")); + hasUsedMoonseye = tag.getBoolean("HasUsedMoonseye"); respecCount = Math.max(0, tag.getInt("RespecCount")); - everInvestedWeaponScaling = tag.contains("EverInvestedWeaponScaling") - ? tag.getBoolean("EverInvestedWeaponScaling") - : getAttributeLevel("light_scaling") > 0 - || getAttributeLevel("medium_scaling") > 0 - || getAttributeLevel("heavy_scaling") > 0; + CompoundTag attrTag = tag.contains("Attributes") ? tag.getCompound("Attributes") : new CompoundTag(); + for (String key : ATTRIBUTE_KEYS) attributes.put(key, Math.max(0, Math.min(attrTag.getInt(key), maxAttributePoints))); + dataVersion = tag.contains("DataVersion") ? Math.max(1, tag.getInt("DataVersion")) : 1; + if (dataVersion < CURRENT_DATA_VERSION) { + long refund = 0; + for (String legacyKey : LEGACY_REFUND_KEYS) if (attrTag.contains(legacyKey)) refund += Math.max(0, attrTag.getInt(legacyKey)); + lastMigrationRefund = (int) Math.min(Integer.MAX_VALUE, refund); + unspentPoints = (int) Math.min(Integer.MAX_VALUE, (long) unspentPoints + lastMigrationRefund); + dataVersion = CURRENT_DATA_VERSION; + AscendMod.LOGGER.info("Migrated Ascend player data to version {} and refunded {} removed-attribute points", CURRENT_DATA_VERSION, lastMigrationRefund); + } lastRespecBuild.clear(); if (tag.contains("LastRespecBuild")) { - CompoundTag respecBuildTag = tag.getCompound("LastRespecBuild"); - for (String key : ATTRIBUTE_KEYS) { - lastRespecBuild.put(key, Math.max(0, respecBuildTag.getInt(key))); - } - } - if (tag.contains("Knowledge")) { - knowledge = Math.max(0, tag.getInt("Knowledge")); - } - if (tag.contains("HasUsedMoonseye")) { - hasUsedMoonseye = tag.getBoolean("HasUsedMoonseye"); + CompoundTag previous = tag.getCompound("LastRespecBuild"); + for (String key : ATTRIBUTE_KEYS) lastRespecBuild.put(key, Math.max(0, previous.getInt(key))); } int maxXp = getXPToNextAscendLevel(); - if (ascendXP > maxXp) { - ascendXP = maxXp; - } + if (maxXp > 0) ascendXP = Math.min(ascendXP, maxXp); } - private static int getPointsPerLevel() { - return AscendConfig.COMMON.pointsPerLevel.get(); + /** + * Re-applies the server's current progression cap after persistent data is loaded. + * MaxAscendLevel remains serialized because the same payload is sent to clients, + * but a value captured by an older server configuration must not override the + * configuration that is active now. + */ + public void refreshConfiguredMaxAscendLevel() { + maxAscendLevel = getConfiguredMaxAscendLevel(); + ascendLevel = Math.max(1, Math.min(ascendLevel, maxAscendLevel)); + int maxXp = getXPToNextAscendLevel(); + if (maxXp > 0) ascendXP = Math.min(ascendXP, maxXp); } - private static int getConfiguredMaxAscendLevel() { - return AscendConfig.COMMON.maxAscendLevel.get(); - } + private static int getPointsPerLevel() { return configValue(AscendConfig.COMMON.pointsPerLevel); } + private static int getConfiguredMaxAscendLevel() { return configValue(AscendConfig.COMMON.maxAscendLevel); } + private static int getConfiguredMaxAttributePoints() { return configValue(AscendConfig.COMMON.maxAttributePoints); } - private static int getConfiguredMaxAttributePoints() { - return AscendConfig.COMMON.maxAttributePoints.get(); - } - - private static boolean isWeaponScaling(String attribute) { - return "light_scaling".equals(attribute) - || "medium_scaling".equals(attribute) - || "heavy_scaling".equals(attribute); + private static int configValue(net.minecraftforge.common.ForgeConfigSpec.IntValue value) { + return AscendConfig.COMMON_SPEC.isLoaded() ? value.get() : value.getDefault(); } } diff --git a/src/main/java/net/fretux/ascend/player/PlayerStatsProvider.java b/src/main/java/net/fretux/ascend/player/PlayerStatsProvider.java index 3603417..41491ee 100644 --- a/src/main/java/net/fretux/ascend/player/PlayerStatsProvider.java +++ b/src/main/java/net/fretux/ascend/player/PlayerStatsProvider.java @@ -44,6 +44,7 @@ public class PlayerStatsProvider implements ICapabilitySerializable return; } player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { + stats.refreshConfiguredMaxAscendLevel(); AscendAdvancements.checkMilestones(serverPlayer, stats); CompoundTag data = stats.serializeNBT(); PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> serverPlayer), diff --git a/src/main/java/net/fretux/ascend/player/StatEffects.java b/src/main/java/net/fretux/ascend/player/StatEffects.java index c87745a..777dca9 100644 --- a/src/main/java/net/fretux/ascend/player/StatEffects.java +++ b/src/main/java/net/fretux/ascend/player/StatEffects.java @@ -2,288 +2,107 @@ 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; +import net.minecraft.world.entity.player.Player; import java.util.UUID; public final class StatEffects { + private static final UUID STRENGTH_KNOCKBACK_UUID = UUID.fromString("b17f0d74-9cd9-4f90-9ec8-6a1046e2f912"); + private static final UUID AGILITY_SPEED_UUID = UUID.fromString("c2f4e9a1-3b8d-4f21-9d44-8176f8791a11"); + // Reuse the old persisted health UUID so upgrades cleanly replace old world modifiers. + private static final UUID VIGOR_HEALTH_UUID = UUID.fromString("d3a4b5c6-7d8e-4f90-8a1b-223344556677"); + private static final UUID REMOVED_HEALTH_STAT_KB_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 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("c2f4e9a1-3b8d-4f21-9d44-8176f8791a11"); - - private static final UUID FORTITUDE_HEALTH_UUID = - 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() { - } - - private static double getScaling() { - return AscendConfig.COMMON.attributeScalingMultiplier.get(); - } - - private static double scaledRatio(int level, double perPoint, double max) { - if (level <= 0) return 0.0d; - return Math.min(level * perPoint * getScaling(), max); - } - - private static double scalingMultiplier(int level, double perPoint, double maxBonus) { - return 1.0d + scaledRatio(level, perPoint, maxBonus); - } + private StatEffects() {} public static void applyAll(Player player) { player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> { + applyVigor(player, stats.getAttributeLevel("vigor")); applyStrength(player, stats.getAttributeLevel("strength")); applyAgility(player, stats.getAttributeLevel("agility")); - applyFortitude(player, stats.getAttributeLevel("fortitude")); - if (IronsSpellbooksCompat.isLoaded()) { - IronsSpellbooksCompat.applyAll(player); - } - if (player.getHealth() > player.getMaxHealth()) { - player.setHealth(player.getMaxHealth()); - } + IronsSpellbooksCompat.applyAll(player); + if (player.getHealth() > player.getMaxHealth()) player.setHealth(player.getMaxHealth()); }); } - public static void applyStrength(Player player, int strengthLevel) { - // Always remove first so resets correctly. - AttributeInstance atk = player.getAttribute(Attributes.ATTACK_DAMAGE); - if (atk != null) { - atk.removeModifier(STRENGTH_DAMAGE_UUID); - if (strengthLevel > 0) { - double dmgBonus = Math.min(strengthLevel * 0.06d * getScaling(), 6.0d); - 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 = scaledRatio(strengthLevel, 0.002d, 0.2d); - kb.addTransientModifier(new AttributeModifier( - STRENGTH_KNOCKBACK_UUID, - "Ascend Strength knockback bonus", - kbBonus, - AttributeModifier.Operation.ADDITION - )); - } - } + public static void applyVigor(Player player, int level) { + AttributeInstance health = player.getAttribute(Attributes.MAX_HEALTH); + if (health == null) return; + health.removeModifier(VIGOR_HEALTH_UUID); + double bonus = getVigorHealthBonus(level); + if (bonus > 0) health.addPermanentModifier(new AttributeModifier(VIGOR_HEALTH_UUID, + "Ascend Vigor health bonus", bonus, 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 bonus = scaledRatio(agilityLevel, 0.0008d, 0.08d); - move.addTransientModifier(new AttributeModifier( - AGILITY_SPEED_UUID, - "Ascend Agility speed bonus", - bonus, - AttributeModifier.Operation.ADDITION - )); - } - } + public static void applyStrength(Player player, int level) { + AttributeInstance attack = player.getAttribute(Attributes.ATTACK_DAMAGE); + if (attack != null) attack.removeModifier(UUID.fromString("a7b8b2c3-9b0d-4e1a-8ee3-21f6a7b2c3d4")); + AttributeInstance resistance = player.getAttribute(Attributes.KNOCKBACK_RESISTANCE); + if (resistance != null) resistance.removeModifier(REMOVED_HEALTH_STAT_KB_UUID); + AttributeInstance knockback = player.getAttribute(Attributes.ATTACK_KNOCKBACK); + if (knockback == null) return; + knockback.removeModifier(STRENGTH_KNOCKBACK_UUID); + double bonus = getStrengthKnockbackBonus(level); + if (bonus > 0) knockback.addTransientModifier(new AttributeModifier(STRENGTH_KNOCKBACK_UUID, + "Ascend Strength knockback bonus", bonus, AttributeModifier.Operation.ADDITION)); } - public static void applyFortitude(Player player, int fortitudeLevel) { - AttributeInstance hp = player.getAttribute(Attributes.MAX_HEALTH); - if (hp != null) { - hp.removeModifier(FORTITUDE_HEALTH_UUID); - if (fortitudeLevel > 0) { - double healthBonus = Math.min(fortitudeLevel * 0.18d * getScaling(), 18.0d); - // Max health must be present while Minecraft loads the player's saved Health - // value, otherwise values above the vanilla 20 HP are clamped before login. - hp.addPermanentModifier(new AttributeModifier( - FORTITUDE_HEALTH_UUID, - "Ascend Fortitude health bonus", - healthBonus, - AttributeModifier.Operation.ADDITION - )); - } - } - AttributeInstance kbRes = player.getAttribute(Attributes.KNOCKBACK_RESISTANCE); - if (kbRes != null) { - kbRes.removeModifier(FORTITUDE_KB_RESIST_UUID); - if (fortitudeLevel > 0) { - double res = scaledRatio(fortitudeLevel, 0.003d, 0.3d); - kbRes.addTransientModifier(new AttributeModifier( - FORTITUDE_KB_RESIST_UUID, - "Ascend Fortitude knockback resist", - res, - AttributeModifier.Operation.ADDITION - )); - } - } + public static void applyAgility(Player player, int level) { + AttributeInstance movement = player.getAttribute(Attributes.MOVEMENT_SPEED); + if (movement == null) return; + movement.removeModifier(AGILITY_SPEED_UUID); + double bonus = getAgilityMovementSpeedBonus(level); + if (bonus > 0) movement.addTransientModifier(new AttributeModifier(AGILITY_SPEED_UUID, + "Ascend Agility movement bonus", bonus, AttributeModifier.Operation.MULTIPLY_BASE)); } - public static float getWillpowerSanityMultiplier(int willpowerLevel) { - if (willpowerLevel <= 0) return 1.0f; - double reduction = scaledRatio(willpowerLevel, 0.0035d, 0.35d); - return (float) (1.0d - reduction); + public static double getVigorHealthBonus(int level) { return normalized(level) * configured(AscendConfig.COMMON.vigorMaxHealthBonus); } + public static double getStrengthKnockbackBonus(int level) { return normalized(level) * configured(AscendConfig.COMMON.strengthKnockbackBonus); } + public static double getStrengthArmorPenetration(int level) { return normalized(level) * configured(AscendConfig.COMMON.strengthArmorPenetration); } + public static double getAgilityMovementSpeedBonus(int level) { return normalized(level) * configured(AscendConfig.COMMON.agilityMovementSpeedBonus); } + public static int getMindManaBonus(int level) { return (int) Math.floor(normalized(level) * configured(AscendConfig.COMMON.mindMaxManaBonus)); } + public static double getMindManaRegenBonus(int level) { return normalized(level) * configured(AscendConfig.COMMON.mindManaRegenBonus); } + public static double getIntelligenceSpellPowerBonus(int level) { return normalized(level) * configured(AscendConfig.COMMON.intelligenceSpellPowerBonus); } + public static double getIntelligenceCooldownBonus(int level) { return normalized(level) * configured(AscendConfig.COMMON.intelligenceCooldownBonus); } + public static double getWillpowerCastTimeReduction(int level) { return normalized(level) * configured(AscendConfig.COMMON.willpowerCastTimeReduction); } + public static double getArcaneBeneficialDurationMultiplier(int level) { return 1.0 + normalized(level) * configured(AscendConfig.COMMON.arcaneBeneficialDurationBonus); } + public static double getArcaneHarmfulDurationMultiplier(int level) { return 1.0 + normalized(level) * configured(AscendConfig.COMMON.arcaneHarmfulDurationBonus); } + + public static double getCharismaTradeDiscount(int level) { return normalized(level) * 0.30; } + public static int getCharismaVillagerReputationBonus(int level) { return (int) Math.floor(normalized(level) * 8.0); } + public static double getCharismaAllyHealthBonus(int level) { return normalized(level) * 0.20; } + public static double getCharismaAllyDamageBonus(int level) { return normalized(level) * 0.20; } + + public static void applyCharismaAllyBuff(LivingEntity entity, int level) { + if (level <= 0) { clearCharismaAllyBuff(entity); return; } + updateModifier(entity.getAttribute(Attributes.MAX_HEALTH), CHARISMA_ALLY_HEALTH_UUID, + "Ascend Charisma ally max health", getCharismaAllyHealthBonus(level)); + updateModifier(entity.getAttribute(Attributes.ATTACK_DAMAGE), CHARISMA_ALLY_DAMAGE_UUID, + "Ascend Charisma ally damage", getCharismaAllyDamageBonus(level)); } - public static float getWillpowerTempoMultiplier(int willpowerLevel) { - if (willpowerLevel <= 0) return 1.0f; - double bonus = scaledRatio(willpowerLevel, 0.003d, 0.3d); - return (float) (1.0d + bonus); - } - - public static float getWillpowerHealthRegen(int willpowerLevel) { - if (willpowerLevel <= 0) return 0.0f; - double bonus = scaledRatio(willpowerLevel, 0.0125d, 1.25d); - return (float) bonus; - } - - public static double getWillpowerCastTimeReduction(int willpowerLevel) { - return scaledRatio(willpowerLevel, 0.002d, 0.2d); - } - - public static float getPlayerSanityMultiplier(Player player) { - return player.getCapability(PlayerStatsProvider.PLAYER_STATS) - .map(stats -> getWillpowerSanityMultiplier(stats.getAttributeLevel("willpower"))) - .orElse(1.0f); - } - - public static float getPlayerTempoMultiplier(Player player) { - return player.getCapability(PlayerStatsProvider.PLAYER_STATS) - .map(stats -> getWillpowerTempoMultiplier(stats.getAttributeLevel("willpower"))) - .orElse(1.0f); - } - - public static int getIntelligenceManaBonus(int intelligenceLevel) { - return (int) Math.floor(Math.min(intelligenceLevel * 2.0d * getScaling(), 200.0d)); - } - - public static double getIntelligenceAnvilCostReduction(int intelligenceLevel) { - return scaledRatio(intelligenceLevel, 0.003d, 0.3d); - } - - public static int getIntelligenceKnowledgeGain(int intelligenceLevel, int baseGain) { - if (baseGain <= 0) return baseGain; - if (intelligenceLevel <= 0) return baseGain; - double bonus = scaledRatio(intelligenceLevel, 0.003d, 0.3d); - int extra = (int) Math.floor(baseGain * bonus); - return baseGain + Math.max(0, extra); - } - - public static double getCharismaTradeDiscount(int charismaLevel) { - return scaledRatio(charismaLevel, 0.003d, 0.3d); - } - - public static int getCharismaVillagerReputationBonus(int charismaLevel) { - if (charismaLevel <= 0) return 0; - return (int) Math.floor(Math.min(charismaLevel * 0.08d * getScaling(), 8.0d)); - } - - public static double getCharismaAllyHealthBonus(int charismaLevel) { - return scaledRatio(charismaLevel, 0.002d, 0.2d); - } - - public static double getCharismaAllyDamageBonus(int charismaLevel) { - return scaledRatio(charismaLevel, 0.002d, 0.2d); - } - - public static double getFortitudeEffectGuardChance(int fortitudeLevel) { - return scaledRatio(fortitudeLevel, 0.0025d, 0.25d); - } - - public static double getFortitudeCleanseChance(int fortitudeLevel) { - return scaledRatio(fortitudeLevel, 0.002d, 0.2d); - } - - public static double getAgilityEvasionChance(int agilityLevel) { - return scaledRatio(agilityLevel, 0.0015d, 0.15d); - } - - public static double getAgilityRangedDamageMultiplier(int agilityLevel) { - return 1.0d + Math.min((getLightWeaponScalingMultiplier(agilityLevel) - 1.0d) * 0.7d, 0.25d); - } - - public static double getStrengthTridentDamageMultiplier(int strengthLevel) { - return 1.0d + Math.min((getWeaponScalingMultiplier(strengthLevel) - 1.0d) * 0.6d, 0.2d); - } - - 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 - )); - } - } + private static void updateModifier(AttributeInstance instance, UUID id, String name, double value) { + if (instance == null) return; + instance.removeModifier(id); + if (value > 0) instance.addTransientModifier(new AttributeModifier(id, name, value, 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); - } + 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) { - return scalingMultiplier(scalingLevel, 0.0035d, 0.35d); + if (damage != null) damage.removeModifier(CHARISMA_ALLY_DAMAGE_UUID); + if (entity.getHealth() > entity.getMaxHealth()) entity.setHealth(entity.getMaxHealth()); } - public static double getLightWeaponScalingMultiplier(int scalingLevel) { - return scalingMultiplier(scalingLevel, 0.0035d, 0.35d); - } - - public static double getHeavyWeaponScalingMultiplier(int scalingLevel) { - return scalingMultiplier(scalingLevel, 0.0035d, 0.35d); - } - - public static double getMagicScalingMultiplier(int scalingLevel) { - return scalingMultiplier(scalingLevel, 0.0035d, 0.35d); + private static double normalized(int level) { return AttributeScaling.getNormalizedAttributeValue(level); } + private static double configured(net.minecraftforge.common.ForgeConfigSpec.DoubleValue value) { + return AscendConfig.COMMON_SPEC.isLoaded() ? value.get() : value.getDefault(); } } diff --git a/src/main/resources/assets/ascend/lang/en_us.json b/src/main/resources/assets/ascend/lang/en_us.json index 0cd1458..ba2804e 100644 --- a/src/main/resources/assets/ascend/lang/en_us.json +++ b/src/main/resources/assets/ascend/lang/en_us.json @@ -7,32 +7,38 @@ "ascend.stats.xp_max": "Ascend XP: MAX", "ascend.stats.unspent": "Unspent Points: %s", + "ascend.attribute.vigor": "Vigor", + "ascend.attribute.vigor.desc": "Increases maximum health.", + "ascend.attribute.vigor.short": "Increases maximum health", + "ascend.attribute.mind": "Mind", + "ascend.attribute.mind.desc": "Increases mana capacity and regeneration.", + "ascend.attribute.mind.short": "Increases mana capacity and regeneration", "ascend.attribute.strength": "Strength", - "ascend.attribute.strength.desc": "Increases melee damage, knockback and armor penetration.", + "ascend.attribute.strength.desc": "Improves Strength weapon scaling, armor penetration, and knockback.", + "ascend.attribute.strength.short": "Improves heavy weapon scaling", "ascend.attribute.agility": "Agility", - "ascend.attribute.agility.desc": "Increases movement speed and mobility.", - "ascend.attribute.fortitude": "Fortitude", - "ascend.attribute.fortitude.desc": "Increases max health and knockback resistance.", + "ascend.attribute.agility.desc": "Improves agile and ranged weapon scaling and movement speed.", + "ascend.attribute.agility.short": "Improves agile and ranged weapons", "ascend.attribute.intelligence": "Intelligence", - "ascend.attribute.intelligence.desc": "Increases max mana and mana regeneration. Lowers enchantment/anvil costs, buffs potions, and boosts knowledge gain.", + "ascend.attribute.intelligence.desc": "Improves spell power and reduces spell cooldowns.", + "ascend.attribute.intelligence.short": "Improves spell power and cooldowns", "ascend.attribute.willpower": "Willpower", - "ascend.attribute.willpower.desc": "Reduces sanity drain, improves stamina/tempo, and increases passive health regeneration.", + "ascend.attribute.willpower.desc": "Increases maximum Sanity and Tempo and improves spell casting.", + "ascend.attribute.willpower.short": "Increases max Sanity and Tempo", "ascend.attribute.charisma": "Charisma", - "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", - "ascend.attribute.medium_scaling.desc": "Improves medium weapon performance.", - "ascend.attribute.heavy_scaling": "Heavy Scaling", - "ascend.attribute.heavy_scaling.desc": "Improves heavy weapon performance.", - "ascend.attribute.magic_scaling": "Magic Scaling", - "ascend.attribute.magic_scaling.desc": "Increases spell power, spell damage, and spell cooldown reduction.", + "ascend.attribute.charisma.desc": "Improves villager trading and strengthens nearby allies.", + "ascend.attribute.charisma.short": "Improves trading and allies", + "ascend.attribute.arcane": "Arcane", + "ascend.attribute.arcane.desc": "Increases the duration of beneficial and harmful status effects.", + "ascend.attribute.arcane.short": "Improves status effects", "item.ascend.moonseye_tome": "Moonseye Tome", "item.ascend.moonseye_tome.desc": "A forbidden scripture that reveals knowledge of the Depths. Grants 100 Knowledge on first use, 50 afterwards.", "item.ascend.moonseye_tome.used": "You gained %s Knowledge from the Moonseye Tome!", "itemGroup.ascend": "Ascend", "ascend.attribute.display": "%s: %s", "ascend.stats.knowledge": "Knowledge: %s", + "ascend.weapon_scaling.title": "Attribute Scaling", + "ascend.weapon_scaling.grade": "%s: %s", "item.ascend.remembrance_essence": "Remembrance Essence", "block.ascend.shrine_of_remembrance": "Shrine of Remembrance", @@ -58,33 +64,17 @@ "advancements.ascend.cannot_hit_what_you_cannot_see.title": "You Cannot Hit What You Cannot See", "advancements.ascend.cannot_hit_what_you_cannot_see.description": "Reach 100 Agility", "advancements.ascend.health_bar_enthusiast.title": "Health Bar Enthusiast", - "advancements.ascend.health_bar_enthusiast.description": "Reach 100 Fortitude", + "advancements.ascend.health_bar_enthusiast.description": "Reach 100 Vigor", "advancements.ascend.big_brain_small_helmet.title": "Big Brain Time", "advancements.ascend.big_brain_small_helmet.description": "Reach 100 Intelligence", "advancements.ascend.mana_without_purpose.title": "Mana Without Purpose", - "advancements.ascend.mana_without_purpose.description": "Reach 50 Intelligence without Iron's Spells installed", + "advancements.ascend.mana_without_purpose.description": "Reach 100 Mind", "advancements.ascend.unlicensed_therapist.title": "Unlicensed Therapist", "advancements.ascend.unlicensed_therapist.description": "Reach 100 Willpower", "advancements.ascend.talk_no_jutsu.title": "Talk No Jutsu", "advancements.ascend.talk_no_jutsu.description": "Reach 100 Charisma", "advancements.ascend.perfectly_balanced.title": "Perfectly Balanced", - "advancements.ascend.perfectly_balanced.description": "Invest exactly the same number of points in all ten attributes", - "advancements.ascend.light_work.title": "Light Work", - "advancements.ascend.light_work.description": "Reach 100 Light Scaling", - "advancements.ascend.medium_rare.title": "Medium Rare", - "advancements.ascend.medium_rare.description": "Reach 100 Medium Scaling", - "advancements.ascend.compensating.title": "Compensating", - "advancements.ascend.compensating.description": "Reach 100 Heavy Scaling", - "advancements.ascend.quality_build.title": "Quality Build", - "advancements.ascend.quality_build.description": "Reach 75 in all three weapon scaling attributes", - "advancements.ascend.oops_all_magic.title": "Oops, All Magic", - "advancements.ascend.oops_all_magic.description": "Reach 100 Magic Scaling before investing in weapon scaling", - "advancements.ascend.glass_cannonical.title": "Glass Cannonical", - "advancements.ascend.glass_cannonical.description": "Reach 90 Intelligence and Magic Scaling while keeping Fortitude below 10", - "advancements.ascend.muscle_wizard.title": "Muscle Wizard", - "advancements.ascend.muscle_wizard.description": "Reach 100 Strength and Magic Scaling", - "advancements.ascend.optimized_casting_environment.title": "Optimized Casting Environment", - "advancements.ascend.optimized_casting_environment.description": "Reach 80 Intelligence, Willpower, and Magic Scaling", + "advancements.ascend.perfectly_balanced.description": "Invest exactly the same number of points in all eight attributes", "advancements.ascend.indecisive_chosen_one.title": "Indecisive Chosen One", "advancements.ascend.indecisive_chosen_one.description": "Respec ten times on the same character", "advancements.ascend.config_enjoyer.title": "Config Enjoyer",