Revamp the entire skillset and rebalance everything.

This commit is contained in:
2026-08-24 20:55:56 +02:00
parent 4f51decfca
commit e36fc74a1f
22 changed files with 592 additions and 1207 deletions
@@ -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);
});
}
}
}
@@ -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<String, Button> 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<String, Integer> ATTRIBUTE_COLORS = Map.of(
"vigor", 0xFFD85C5C,
"mind", 0xFF6699E8,
"strength", 0xFFD78445,
"agility", 0xFF71C988,
"intelligence", 0xFF9A7BE8,
"willpower", 0xFF79B9C8,
"charisma", 0xFFE28AB8,
"arcane", 0xFFC878E0
);
private static final Map<String, Component> 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<String, Button> 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;
@@ -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(() ->
@@ -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);
});
}
}
}
@@ -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);
}
}
}
@@ -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<Common, ForgeConfigSpec> specPair = new ForgeConfigSpec.Builder().configure(Common::new);
COMMON_SPEC = specPair.getRight();
COMMON = specPair.getLeft();
Pair<Common, ForgeConfigSpec> pair = new ForgeConfigSpec.Builder().configure(Common::new);
COMMON = pair.getLeft();
COMMON_SPEC = pair.getRight();
}
}
@@ -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);
@@ -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()));
});
}
}
@@ -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);
}
});
}
}
@@ -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);
});
}
}
@@ -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()
));
});
}
}
@@ -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());
}
}
@@ -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;
}
}
@@ -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");
@@ -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()));
@@ -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<String, Integer> attributes = new HashMap<>();
private final Map<String, Integer> 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<String, Integer> attributes = new LinkedHashMap<>();
private final Map<String, Integer> 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<String, Integer> 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<String, Integer> 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<String, Integer> entry : attributes.entrySet()) {
attrTag.putInt(entry.getKey(), entry.getValue());
}
attributes.forEach(attrTag::putInt);
tag.put("Attributes", attrTag);
CompoundTag respecBuildTag = new CompoundTag();
for (Map.Entry<String, Integer> 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();
}
}
@@ -44,6 +44,7 @@ public class PlayerStatsProvider implements ICapabilitySerializable<CompoundTag>
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),
@@ -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();
}
}