Compare commits

..

5 Commits

Author SHA1 Message Date
Frederik Spirgi 8c5db5baee fix gradle 2026-10-08 09:48:20 +02:00
Frederik Spirgi c85906aa49 fix shrine because I broke it 2026-10-08 09:37:14 +02:00
Frederik Spirgi efb1b98868 update dependencies and fix a small issue with GUI Scale 2026-10-08 09:28:44 +02:00
Fretux cfccdd9b36 Merge branch 'main' of https://git.bachmannj.ch/Fretux/Ascend
Ascend
2026-10-07 19:57:59 +02:00
Fretux 53701c2dab Add new advancements, weapon scaling tooltips, passive agility API, and attribute scaling logic 2026-09-19 20:49:04 +02:00
43 changed files with 1214 additions and 162 deletions
+2
View File
@@ -16,6 +16,8 @@ out
# gradle
build
.gradle
.gradle-user-home
mcmodsrepo
# other
eclipse
+6
View File
@@ -2,6 +2,12 @@
This changelog covers all recorded changes after `1.0.0`, grouped by release version from the git history.
## 3.1.1
- Refined the Stats screen with charcoal and iron surfaces, consistent neutral attribute cards, and a shared muted accent instead of screen-only attribute colors.
- Reserved gold for level and XP progression and green for available points, with an aligned progression header and a separate XP bar and label.
- Added distinct 12-pixel attribute icons and stronger name/description contrast while retaining the two-column layout, native Minecraft buttons, and darkened world backdrop.
## 3.1.0
- Added a public passive Agility event API so integrations such as skill-tree mods can contribute additional Agility points per player.
+5 -5
View File
@@ -135,19 +135,19 @@ repositories {
dependencies {
minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}"
implementation fg.deobf("net.fretux.mindmotion:mindmotion:${mindmotion_version}")
implementation fg.deobf("curse.maven:mind-motion-1386864:${mindmotion_file_id}")
// Skill Engine is an Ascend addon, so include it in development runs without
// creating a circular mandatory dependency from Ascend back to Skill Engine.
// Skill Engine is an Ascend addon, so include the locally published artifact
// in development runs without creating a circular mandatory dependency.
runtimeOnly fg.deobf("net.fretux.skillengine:SkillEngine:${skillengine_version}")
// Current Iron's Spellbooks releases are distributed through Curse Maven.
compileOnly fg.deobf("curse.maven:irons-spells-n-spellbooks-855414:${irons_spells_file_id}")
// GECKOLIB — deobfuscated, full mod
// GECKOLIB - deobfuscated, full mod
implementation fg.deobf("software.bernie.geckolib:geckolib-forge-${geckolib_version}")
// PLAYER ANIMATOR — deobf
// PLAYER ANIMATOR - deobf
implementation fg.deobf("dev.kosmx.player-anim:player-animation-lib-forge:${player_animator_version}")
// CAELUS
+4 -3
View File
@@ -22,8 +22,9 @@ loader_version_range=[47,)
# The default set of supported mapping channels are ["official", "snapshot", "snapshot_nodoc", "stable", "stable_nodoc"].
# Additional mapping channels can be registered through the "channelProviders" extension in a Gradle plugin.
#
mindmotion_version=1.4.5
skillengine_version=1.4.5
mindmotion_version=1.4.7
mindmotion_file_id=8726482
skillengine_version=1.4.9
irons_spells_version=1.20.1-3.16.2
irons_spells_file_id=8364933
caelus_version=3.2.0+1.20.1
@@ -56,7 +57,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=3.1.0
mod_version=3.1.1
# 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
@@ -0,0 +1,99 @@
package net.fretux.ascend.advancement;
import net.fretux.ascend.AscendMod;
import net.fretux.ascend.config.AscendConfig;
import net.fretux.ascend.player.PlayerStats;
import net.minecraft.advancements.Advancement;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
/** Awards Ascend's state-based advancements from server-authoritative player stats. */
public final class AscendAdvancements {
private static final String CRITERION = "achieved";
private AscendAdvancements() {
}
public static void checkMilestones(ServerPlayer player, PlayerStats stats) {
int level = stats.getAscendLevel();
if (level >= 2) grant(player, "first_steps");
if (level >= 5) grant(player, "level_5");
if (level >= 10) grant(player, "level_10");
if (level >= stats.getMaxAscendLevel()) grant(player, "max_level");
if (stats.getKnowledge() >= 100) grant(player, "knowledge_100");
if (stats.hasUsedMoonseye()) grant(player, "read_moonseye_tome");
for (String attribute : PlayerStats.ATTRIBUTE_KEYS) {
if (stats.getAttributeLevel(attribute) >= stats.getMaxAttributePoints()) {
grant(player, "max_attribute");
break;
}
}
int vigor = stats.getAttributeLevel("vigor");
int mind = stats.getAttributeLevel("mind");
int strength = stats.getAttributeLevel("strength");
int agility = stats.getAttributeLevel("agility");
int intelligence = stats.getAttributeLevel("intelligence");
int willpower = stats.getAttributeLevel("willpower");
int charisma = stats.getAttributeLevel("charisma");
int arcane = stats.getAttributeLevel("arcane");
if (strength >= 100) grant(player, "bonk_scholar");
if (agility >= 100) grant(player, "cannot_hit_what_you_cannot_see");
if (vigor >= 100) grant(player, "health_bar_enthusiast");
if (intelligence >= 100) grant(player, "big_brain_small_helmet");
if (mind >= 100) grant(player, "mana_without_purpose");
if (willpower >= 100) grant(player, "unlicensed_therapist");
if (charisma >= 100) grant(player, "talk_no_jutsu");
if (vigor > 0 && vigor == mind && vigor == strength && vigor == agility
&& vigor == intelligence && vigor == willpower && vigor == charisma && vigor == arcane) {
grant(player, "perfectly_balanced");
}
if (stats.getRespecCount() >= 10) grant(player, "indecisive_chosen_one");
if (hasUnrecognizableProgressionConfig()) grant(player, "config_enjoyer");
if (hasExtremeXpConfig()) grant(player, "game_journalist_mode");
if (stats.hasNearlyRestoredLastBuild()) grant(player, "definition_of_insanity");
}
private static boolean hasUnrecognizableProgressionConfig() {
int changed = 0;
if (AscendConfig.COMMON.xpPerMobKill.get() != 10) changed++;
if (AscendConfig.COMMON.xpPerBlockBreak.get() != 2) changed++;
if (AscendConfig.COMMON.xpPerCraft.get() != 1) changed++;
if (AscendConfig.COMMON.xpPerSmelt.get() != 1) changed++;
if (AscendConfig.COMMON.xpPerTrade.get() != 5) changed++;
if (AscendConfig.COMMON.xpPerDamageTaken.get() != 2) changed++;
if (AscendConfig.COMMON.xpPerMovement.get() != 1) changed++;
if (AscendConfig.COMMON.xpPerPotion.get() != 3) changed++;
if (AscendConfig.COMMON.pointsPerLevel.get() != 15) changed++;
if (AscendConfig.COMMON.maxAscendLevel.get() != 20) changed++;
if (AscendConfig.COMMON.maxAttributePoints.get() != 100) changed++;
if (Double.compare(AscendConfig.COMMON.xpMultiplier.get(), 1.0D) != 0) changed++;
if (Double.compare(AscendConfig.COMMON.attributeScalingMultiplier.get(), 1.0D) != 0) changed++;
return changed >= 6;
}
private static boolean hasExtremeXpConfig() {
if (AscendConfig.COMMON.xpMultiplier.get() >= 25.0D) return true;
int extremeSources = 0;
if (AscendConfig.COMMON.xpPerMobKill.get() >= 250) extremeSources++;
if (AscendConfig.COMMON.xpPerBlockBreak.get() >= 250) extremeSources++;
if (AscendConfig.COMMON.xpPerCraft.get() >= 250) extremeSources++;
if (AscendConfig.COMMON.xpPerSmelt.get() >= 250) extremeSources++;
if (AscendConfig.COMMON.xpPerTrade.get() >= 250) extremeSources++;
if (AscendConfig.COMMON.xpPerDamageTaken.get() >= 250) extremeSources++;
if (AscendConfig.COMMON.xpPerMovement.get() >= 250) extremeSources++;
if (AscendConfig.COMMON.xpPerPotion.get() >= 250) extremeSources++;
return extremeSources >= 4;
}
public static void grant(ServerPlayer player, String advancementPath) {
Advancement advancement = player.server.getAdvancements().getAdvancement(
ResourceLocation.fromNamespaceAndPath(AscendMod.MODID, advancementPath)
);
if (advancement != null) {
player.getAdvancements().award(advancement, CRITERION);
}
}
}
@@ -0,0 +1,40 @@
package net.fretux.ascend.api;
import net.fretux.ascend.api.event.PassiveAgilityEvent;
import net.fretux.ascend.player.AttributeScaling;
import net.fretux.ascend.player.PlayerStatsProvider;
import net.fretux.ascend.player.StatEffects;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.common.MinecraftForge;
/** Public entry point for Ascend's passive Agility integration. */
public final class PassiveAgilityApi {
private PassiveAgilityApi() {}
/** Collects all passive Agility contributions registered for this player. */
public static int getPassiveAgility(Player player, int investedAgility) {
PassiveAgilityEvent event = new PassiveAgilityEvent(player, investedAgility);
MinecraftForge.EVENT_BUS.post(event);
return event.getPassiveAgility();
}
/** Returns invested and passive Agility, saturating safely at the integer limit. */
public static int getTotalAgility(Player player, int investedAgility) {
long total = (long) Math.max(0, investedAgility) + getPassiveAgility(player, investedAgility);
return (int) Math.min(Integer.MAX_VALUE, total);
}
/**
* Returns Agility normalized against the normal investment cap. Values may
* exceed {@code 1.0} when integrations supply passive points above that cap.
*/
public static double getNormalizedAgility(Player player, int investedAgility) {
return AttributeScaling.getExtendedNormalizedAttributeValue(getTotalAgility(player, investedAgility));
}
/** Recalculates the player's movement-speed modifier after passive Agility changes. */
public static void refresh(Player player) {
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats ->
StatEffects.applyAgility(player, stats.getAttributeLevel("agility")));
}
}
@@ -0,0 +1,62 @@
package net.fretux.ascend.api.event;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.eventbus.api.Event;
/**
* Fired before Ascend applies armor penetration to a player attack.
* Integrations may add penetration, change the cap, or disable the cap for
* this hit. Values use fractions: {@code 0.10} ignores ten percent of armor.
*/
public final class ArmorPenetrationEvent extends Event {
private final Player attacker;
private final LivingEntity target;
private final DamageSource damageSource;
private final ItemStack weapon;
private double armorPenetration;
private double cap;
private boolean capEnabled = true;
public ArmorPenetrationEvent(Player attacker, LivingEntity target, DamageSource damageSource,
ItemStack weapon, double armorPenetration, double cap) {
this.attacker = attacker;
this.target = target;
this.damageSource = damageSource;
this.weapon = weapon.copy();
this.armorPenetration = armorPenetration;
this.cap = cap;
}
public Player getAttacker() { return attacker; }
public LivingEntity getTarget() { return target; }
public DamageSource getDamageSource() { return damageSource; }
public ItemStack getWeapon() { return weapon.copy(); }
public double getArmorPenetration() { return armorPenetration; }
public double getCap() { return cap; }
public boolean isCapEnabled() { return capEnabled; }
public void setArmorPenetration(double armorPenetration) { this.armorPenetration = finiteOrZero(armorPenetration); }
public void addArmorPenetration(double armorPenetration) {
setArmorPenetration(this.armorPenetration + finiteOrZero(armorPenetration));
}
public void setCap(double cap) { this.cap = finiteOrZero(cap); }
public void setCapEnabled(boolean capEnabled) { this.capEnabled = capEnabled; }
public void disableCap() { this.capEnabled = false; }
/** Returns the fraction of armor ignored after applying the requested cap. */
public double getEffectiveArmorPenetration() {
return calculateEffectiveArmorPenetration(armorPenetration, cap, capEnabled);
}
/** Pure helper for integrations that need to preview Ascend's cap behavior. */
public static double calculateEffectiveArmorPenetration(double armorPenetration, double cap, boolean capEnabled) {
double maximum = capEnabled ? clamp(cap) : 1.0;
return Math.min(clamp(armorPenetration), maximum);
}
private static double clamp(double value) { return Math.max(0.0, Math.min(1.0, finiteOrZero(value))); }
private static double finiteOrZero(double value) { return Double.isFinite(value) ? value : 0.0; }
}
@@ -0,0 +1,37 @@
package net.fretux.ascend.api.event;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.eventbus.api.Event;
/**
* Fired whenever Ascend calculates a player's Agility effects.
*
* <p>Integrations may add passive points from skill trees, equipment, or other
* progression systems. Contributions from multiple listeners are additive.
* Passive points are applied after the normal investment cap, but still use
* Ascend's configured diminishing-return curve.</p>
*/
public final class PassiveAgilityEvent extends Event {
private final Player player;
private final int investedAgility;
private int passiveAgility;
public PassiveAgilityEvent(Player player, int investedAgility) {
this.player = player;
this.investedAgility = Math.max(0, investedAgility);
}
public Player getPlayer() { return player; }
public int getInvestedAgility() { return investedAgility; }
public int getPassiveAgility() { return passiveAgility; }
public void setPassiveAgility(int passiveAgility) {
this.passiveAgility = Math.max(0, passiveAgility);
}
public void addPassiveAgility(int passiveAgility) {
if (passiveAgility <= 0) return;
long combined = (long) this.passiveAgility + passiveAgility;
this.passiveAgility = (int) Math.min(Integer.MAX_VALUE, combined);
}
}
@@ -0,0 +1,29 @@
package net.fretux.ascend.client;
import net.fretux.ascend.client.screen.ShrineScreen;
import net.fretux.ascend.player.PlayerStatsProvider;
import net.fretux.ascend.player.StatEffects;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
public final class ClientPacketHandlers {
private ClientPacketHandlers() {
}
public static void openShrineScreen(boolean essenceMode) {
Minecraft.getInstance().setScreen(new ShrineScreen(essenceMode));
}
public static void syncStats(CompoundTag data) {
Minecraft mc = Minecraft.getInstance();
var player = mc.player;
if (player == null) {
return;
}
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
stats.deserializeNBT(data);
StatEffects.applyAll(player);
});
}
}
@@ -0,0 +1,34 @@
package net.fretux.ascend.client;
import net.fretux.ascend.AscendMod;
import net.fretux.ascend.scaling.ScalingGrade;
import net.fretux.ascend.scaling.WeaponScaling;
import net.fretux.ascend.scaling.WeaponScalingRegistry;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.event.entity.player.ItemTooltipEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import java.util.ArrayList;
import java.util.List;
@Mod.EventBusSubscriber(modid = AscendMod.MODID, value = Dist.CLIENT)
public final class WeaponScalingTooltips {
@SubscribeEvent
public static void addScalingTooltip(ItemTooltipEvent event) {
WeaponScaling scaling = WeaponScalingRegistry.get(event.getItemStack().getItem());
if (scaling == null) return;
List<Component> grades = new ArrayList<>();
for (String attribute : WeaponScaling.SUPPORTED_ATTRIBUTES) {
ScalingGrade grade = scaling.grade(attribute);
if (grade != ScalingGrade.NONE) grades.add(Component.translatable("ascend.weapon_scaling.grade",
Component.translatable("ascend.attribute." + attribute), grade.name()));
}
if (!grades.isEmpty()) {
event.getToolTip().add(Component.translatable("ascend.weapon_scaling.title").withStyle(ChatFormatting.DARK_GRAY));
grades.forEach(line -> event.getToolTip().add(line.copy().withStyle(ChatFormatting.GRAY)));
}
}
}
@@ -1,6 +1,7 @@
package net.fretux.ascend.client.screen;
import com.mojang.blaze3d.systems.RenderSystem;
import net.fretux.ascend.compat.AscendMMCompat;
import net.fretux.ascend.network.PacketHandler;
import net.fretux.ascend.network.ServerboundShrineChoicePacket;
import net.fretux.ascend.player.PlayerStatsProvider;
@@ -11,12 +12,21 @@ import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
public class ShrineScreen extends Screen {
private static final int WIDTH = 360;
private static final int HEIGHT = 220;
private static final int PADDING = 12;
private static final int BACKGROUND = 0xFF121316;
private static final int PANEL = 0xFF1C1E22;
private static final int CARD = 0xFF25272D;
private static final int BORDER = 0xFF45484F;
private static final int PRIMARY_TEXT = 0xFFE4E4E4;
private static final int SECONDARY_TEXT = 0xFF999DA5;
private static final int ACCENT = 0xFFAAB6C2;
private static final int PROGRESS = 0xFFD1A744;
private static final int WIDTH = 260;
private static final int HEIGHT = 200;
private int leftPos;
private int topPos;
private float uiScale = 1.0F;
private int uiLeft;
private int uiTop;
private int dialogueStage = 0; // 0 = intro, 1 = offer, 2 = confirm
private int playerKnowledge = 0;
@@ -28,7 +38,7 @@ public class ShrineScreen extends Screen {
: Component.literal("Shrine of Remembrance"));
this.essenceMode = essenceMode;
}
public ShrineScreen() {
this(false);
}
@@ -36,8 +46,10 @@ public class ShrineScreen extends Screen {
@Override
protected void init() {
super.init();
this.leftPos = (this.width - WIDTH) / 2;
this.topPos = (this.height - HEIGHT) / 2;
uiScale = Math.min(1.0F, Math.min((width - 12.0F) / WIDTH, (height - 12.0F) / HEIGHT));
uiScale = Math.max(0.25F, uiScale);
uiLeft = Math.round((width - WIDTH * uiScale) / 2.0F);
uiTop = Math.round((height - HEIGHT * uiScale) / 2.0F);
Minecraft mc = Minecraft.getInstance();
if (mc.player != null) {
@@ -50,126 +62,139 @@ public class ShrineScreen extends Screen {
}
private void updateButtons() {
this.clearWidgets();
int buttonY = topPos + 85;
clearWidgets();
// ESSENCE MODE — skip all dialogue, go straight to options
if (essenceMode) {
addRenderableWidget(Button.builder(Component.literal("FORGET"), (btn) -> {
PacketHandler.INSTANCE.sendToServer(new ServerboundShrineChoicePacket("forget"));
Minecraft.getInstance().setScreen(null);
}).bounds(leftPos + 20, buttonY, WIDTH - 40, 20).build());
int y = buttonY + 25;
addRenderableWidget(Button.builder(Component.literal("UNDERSTAND"), (btn) -> {
PacketHandler.INSTANCE.sendToServer(new ServerboundShrineChoicePacket("understand"));
Minecraft.getInstance().setScreen(null);
}).bounds(leftPos + 20, y, WIDTH - 40, 20).build());
y += 25;
addRenderableWidget(Button.builder(Component.literal("REST"), (btn) -> {
PacketHandler.INSTANCE.sendToServer(new ServerboundShrineChoicePacket("rest"));
Minecraft.getInstance().setScreen(null);
}).bounds(leftPos + 20, y, WIDTH - 40, 20).build());
y += 25;
addRenderableWidget(Button.builder(Component.literal("LEAVE"), (btn) -> {
Minecraft.getInstance().setScreen(null);
}).bounds(leftPos + 20, y, WIDTH - 40, 20).build());
addChoiceButton("FORGET", 84, () -> choose("forget"));
addChoiceButton("UNDERSTAND", 109, () -> choose("understand"));
addChoiceButton("REST", 134, () -> choose("rest"));
addChoiceButton("LEAVE", 159, this::closeScreen);
return;
}
switch (dialogueStage) {
case 0 -> { // Intro
Button engageButton = Button.builder(Component.literal("I WISH TO ENGAGE IN A DEAL."), (btn) -> {
case 0 -> {
addChoiceButton("I WISH TO ENGAGE IN A DEAL.", 104, () -> {
dialogueStage = 1;
updateButtons();
}).bounds(leftPos + 20, buttonY, WIDTH - 40, 20).build();
Button leaveButton = Button.builder(Component.literal("LEAVE."), (btn) -> {
Minecraft.getInstance().setScreen(null);
}).bounds(leftPos + 20, buttonY + 25, WIDTH - 40, 20).build();
addRenderableWidget(engageButton);
addRenderableWidget(leaveButton);
});
addChoiceButton("LEAVE.", 129, this::closeScreen);
}
case 1 -> { // Shrine explains terms
Button agreeButton = Button.builder(Component.literal("I AGREE."), (btn) -> {
case 1 -> {
addChoiceButton("I AGREE.", 104, () -> {
dialogueStage = 2;
updateButtons();
}).bounds(leftPos + 20, buttonY, WIDTH - 40, 20).build();
Button leaveButton = Button.builder(Component.literal("LEAVE."), (btn) -> {
Minecraft.getInstance().setScreen(null);
}).bounds(leftPos + 20, buttonY + 25, WIDTH - 40, 20).build();
addRenderableWidget(agreeButton);
addRenderableWidget(leaveButton);
});
addChoiceButton("LEAVE.", 129, this::closeScreen);
}
case 2 -> {
// Existing Ascend option
Button forgetButton = Button.builder(Component.literal("I WISH TO FORGET."), (btn) -> {
PacketHandler.INSTANCE.sendToServer(new ServerboundShrineChoicePacket("forget"));
Minecraft.getInstance().setScreen(null);
}).bounds(leftPos + 20, buttonY, WIDTH - 40, 20).build();
addRenderableWidget(forgetButton);
int y = buttonY + 25;
if (net.fretux.ascend.compat.AscendMMCompat.isMindMotionPresent()) {
Button understandButton = Button.builder(Component.literal("I WISH TO UNDERSTAND."), (btn) -> {
PacketHandler.INSTANCE.sendToServer(new ServerboundShrineChoicePacket("understand"));
Minecraft.getInstance().setScreen(null);
}).bounds(leftPos + 20, y, WIDTH - 40, 20).build();
addRenderableWidget(understandButton);
int y = 96;
addChoiceButton("I WISH TO FORGET.", y, () -> choose("forget"));
y += 25;
if (AscendMMCompat.isMindMotionPresent()) {
addChoiceButton("I WISH TO UNDERSTAND.", y, () -> choose("understand"));
y += 25;
Button restButton = Button.builder(Component.literal("I WISH TO REST."), (btn) -> {
PacketHandler.INSTANCE.sendToServer(new ServerboundShrineChoicePacket("rest"));
Minecraft.getInstance().setScreen(null);
}).bounds(leftPos + 20, y, WIDTH - 40, 20).build();
addRenderableWidget(restButton);
addChoiceButton("I WISH TO REST.", y, () -> choose("rest"));
y += 25;
}
Button leaveButton = Button.builder(Component.literal("LEAVE."), (btn) -> {
Minecraft.getInstance().setScreen(null);
}).bounds(leftPos + 20, y, WIDTH - 40, 20).build();
addRenderableWidget(leaveButton);
addChoiceButton("LEAVE.", y, this::closeScreen);
}
}
}
private void addChoiceButton(String label, int y, Runnable action) {
addRenderableWidget(Button.builder(Component.literal(label), button -> action.run())
.bounds(20, y, WIDTH - 40, 20)
.build());
}
private void choose(String choice) {
PacketHandler.INSTANCE.sendToServer(new ServerboundShrineChoicePacket(choice));
closeScreen();
}
private void closeScreen() {
Minecraft.getInstance().setScreen(null);
}
@Override
public void render(GuiGraphics gui, int mouseX, int mouseY, float partialTicks) {
this.renderBackground(gui);
renderBackground(gui);
int uiMouseX = (int) toUiX(mouseX);
int uiMouseY = (int) toUiY(mouseY);
gui.pose().pushPose();
gui.pose().translate(uiLeft, uiTop, 0.0F);
gui.pose().scale(uiScale, uiScale, 1.0F);
drawWindow(gui);
gui.drawCenteredString(font, title, WIDTH / 2, 6, PRIMARY_TEXT);
gui.drawCenteredString(font, Component.translatable("ascend.stats.knowledge", playerKnowledge),
WIDTH / 2, 36, PROGRESS);
drawDialogue(gui);
super.render(gui, uiMouseX, uiMouseY, partialTicks);
gui.pose().popPose();
}
private void drawWindow(GuiGraphics gui) {
RenderSystem.enableBlend();
gui.fill(leftPos - 8, topPos - 8, leftPos + WIDTH + 8, topPos + HEIGHT + 8, 0xDD0D0B14);
gui.fill(leftPos - 4, topPos - 4, leftPos + WIDTH + 4, topPos + HEIGHT + 4, 0xE01A1424);
gui.fill(leftPos - 4, topPos - 4, leftPos + WIDTH + 4, topPos + 20, 0xFF2A1F3A);
gui.fill(leftPos - 4, topPos + 20, leftPos + WIDTH + 4, topPos + 21, 0x66FFFFFF);
gui.fill(-6, -6, WIDTH + 6, HEIGHT + 6, 0xCC090A0C);
gui.fill(-3, -3, WIDTH + 3, HEIGHT + 3, BORDER);
gui.fill(0, 0, WIDTH, HEIGHT, BACKGROUND);
gui.fill(0, 0, WIDTH, 24, PANEL);
gui.fill(0, 24, WIDTH, 25, BORDER);
gui.fill(PADDING, 30, WIDTH - PADDING, 54, CARD);
gui.fill(PADDING, 54, WIDTH - PADDING, 55, BORDER);
RenderSystem.disableBlend();
}
int titleColor = 0xCDA1FF;
gui.drawCenteredString(this.font, "Shrine of Remembrance", this.width / 2, topPos + 6, 0xFFFFFF);
gui.drawCenteredString(this.font, "Knowledge: " + playerKnowledge, this.width / 2, topPos + 28, 0x8FEBD2);
private void drawDialogue(GuiGraphics gui) {
if (essenceMode) {
drawCentered(gui, "CHOOSE WHAT TO CHANGE.", 68, ACCENT);
return;
}
switch (dialogueStage) {
case 0 -> {
gui.drawCenteredString(this.font, "SPEAK, LITTLE ONE.", this.width / 2, topPos + 46, titleColor);
gui.drawCenteredString(this.font, "WHAT IS IT YOU SEEK TO UNMAKE?", this.width / 2, topPos + 62, titleColor);
drawCentered(gui, "SPEAK, LITTLE ONE.", 66, ACCENT);
drawCentered(gui, "WHAT IS IT YOU SEEK TO UNMAKE?", 82, SECONDARY_TEXT);
}
case 1 -> {
gui.drawCenteredString(this.font, "THE TERMS ARE AS FOLLOWS:", this.width / 2, topPos + 46, titleColor);
gui.drawCenteredString(this.font, "I WILL CONSUME YOUR KNOWLEDGE.", this.width / 2, topPos + 62, titleColor);
gui.drawCenteredString(this.font, "YOU WILL REAP A BENEFIT.", this.width / 2, topPos + 78, titleColor);
}
case 2 -> {
gui.drawCenteredString(this.font, "SPEAK YOUR DESIRE, FOOLISH ONE.", this.width / 2, topPos + 52, titleColor);
drawCentered(gui, "THE TERMS ARE AS FOLLOWS:", 64, ACCENT);
drawCentered(gui, "I WILL CONSUME YOUR KNOWLEDGE.", 78, SECONDARY_TEXT);
drawCentered(gui, "YOU WILL REAP A BENEFIT.", 92, SECONDARY_TEXT);
}
case 2 -> drawCentered(gui, "SPEAK YOUR DESIRE, FOOLISH ONE.", 68, ACCENT);
}
}
super.render(gui, mouseX, mouseY, partialTicks);
private void drawCentered(GuiGraphics gui, String text, int y, int color) {
gui.drawCenteredString(font, text, WIDTH / 2, y, color);
}
private double toUiX(double mouseX) {
return (mouseX - uiLeft) / uiScale;
}
private double toUiY(double mouseY) {
return (mouseY - uiTop) / uiScale;
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
return super.mouseClicked(toUiX(mouseX), toUiY(mouseY), button);
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int button) {
return super.mouseReleased(toUiX(mouseX), toUiY(mouseY), button);
}
@Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) {
return super.mouseDragged(toUiX(mouseX), toUiY(mouseY), button, dragX / uiScale, dragY / uiScale);
}
@Override
@@ -15,27 +15,37 @@ 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 HEIGHT = 280;
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 CARDS_TOP = 78;
private static final int CARD_WIDTH = (WIDTH - PADDING * 2 - COLUMN_GAP) / 2;
private static final int BACKGROUND = 0xFF121316;
private static final int PANEL = 0xFF1C1E22;
private static final int CARD = 0xFF25272D;
private static final int BORDER = 0xFF45484F;
private static final int PRIMARY_TEXT = 0xFFE4E4E4;
private static final int SECONDARY_TEXT = 0xFF999DA5;
private static final int ACCENT = 0xFFAAB6C2;
private static final int PROGRESS = 0xFFD1A744;
private static final int AVAILABLE = 0xFF6EAE67;
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
// Twelve-pixel glyphs: heart, orb, sword, boot, book, eye, speech, rune.
private static final Map<String, int[]> ATTRIBUTE_ICONS = Map.of(
"vigor", new int[]{0x000, 0x318, 0x7BC, 0xFFE, 0xFFE, 0xFFE, 0x7FC, 0x3F8, 0x1F0, 0x0E0, 0x040, 0x000},
"mind", new int[]{0x000, 0x0F0, 0x30C, 0x402, 0x4C2, 0x482, 0x402, 0x402, 0x30C, 0x0F0, 0x000, 0x000},
"strength", new int[]{0x060, 0x0F0, 0x0F0, 0x0F0, 0x0F0, 0x0F0, 0x264, 0x3FC, 0x060, 0x060, 0x0F0, 0x000},
"agility", new int[]{0x000, 0x3C0, 0x240, 0x240, 0x3C0, 0x240, 0x27C, 0x202, 0x202, 0x3FE, 0x000, 0x000},
"intelligence", new int[]{0x000, 0x7BC, 0xC46, 0x842, 0xB5A, 0x842, 0xB5A, 0x842, 0xC46, 0x7BC, 0x040, 0x000},
"willpower", new int[]{0x000, 0x000, 0x0F0, 0x30C, 0x492, 0x891, 0x891, 0x492, 0x30C, 0x0F0, 0x000, 0x000},
"charisma", new int[]{0x000, 0x7FE, 0x402, 0x402, 0x552, 0x402, 0x402, 0x7FE, 0x180, 0x100, 0x000, 0x000},
"arcane", new int[]{0x040, 0x0E0, 0x150, 0x248, 0x444, 0x248, 0x150, 0x0E0, 0x150, 0x248, 0x040, 0x000}
);
private static final Map<String, Component> ATTRIBUTE_TOOLTIPS = new HashMap<>();
@@ -47,8 +57,9 @@ public class StatsScreen extends Screen {
private final Minecraft minecraft = Minecraft.getInstance();
private final Map<String, Button> plusButtons = new HashMap<>();
private int leftPos;
private int topPos;
private float uiScale = 1.0F;
private int uiLeft;
private int uiTop;
private String hoveredAttributeKey;
public StatsScreen() {
@@ -58,8 +69,10 @@ public class StatsScreen extends Screen {
@Override
protected void init() {
super.init();
leftPos = (width - WIDTH) / 2;
topPos = (height - HEIGHT) / 2;
uiScale = Math.min(1.0F, Math.min((width - 12.0F) / WIDTH, (height - 12.0F) / HEIGHT));
uiScale = Math.max(0.25F, uiScale);
uiLeft = Math.round((width - WIDTH * uiScale) / 2.0F);
uiTop = Math.round((height - HEIGHT * uiScale) / 2.0F);
plusButtons.clear();
clearWidgets();
@@ -67,8 +80,8 @@ public class StatsScreen extends Screen {
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 cardX = PADDING + column * (CARD_WIDTH + COLUMN_GAP);
int cardY = 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)
@@ -78,16 +91,23 @@ public class StatsScreen extends Screen {
}
addRenderableWidget(Button.builder(Component.translatable("gui.done"), button -> onClose())
.bounds(leftPos + WIDTH / 2 - 45, topPos + HEIGHT - 24, 90, 18)
.bounds(WIDTH / 2 - 45, HEIGHT - 24, 90, 18)
.build());
}
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
renderBackground(graphics);
drawWindow(graphics);
hoveredAttributeKey = null;
int uiMouseX = (int) toUiX(mouseX);
int uiMouseY = (int) toUiY(mouseY);
graphics.pose().pushPose();
graphics.pose().translate(uiLeft, uiTop, 0.0F);
graphics.pose().scale(uiScale, uiScale, 1.0F);
drawWindow(graphics);
if (minecraft.player != null) {
minecraft.player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
drawProgression(graphics, stats.getAscendLevel(), stats.getMaxAscendLevel(),
@@ -97,11 +117,11 @@ public class StatsScreen extends Screen {
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 cardX = PADDING + column * (CARD_WIDTH + COLUMN_GAP);
int cardY = 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;
boolean hovered = uiMouseX >= cardX && uiMouseX < cardX + CARD_WIDTH
&& uiMouseY >= cardY && uiMouseY < cardY + CARD_HEIGHT;
drawAttributeCard(graphics, attribute, value, stats.getMaxAttributePoints(), cardX, cardY, hovered);
if (hovered) hoveredAttributeKey = attribute;
@@ -116,7 +136,8 @@ public class StatsScreen extends Screen {
});
}
super.render(graphics, mouseX, mouseY, partialTicks);
super.render(graphics, uiMouseX, uiMouseY, partialTicks);
graphics.pose().popPose();
if (hoveredAttributeKey != null) {
graphics.renderTooltip(font, ATTRIBUTE_TOOLTIPS.get(hoveredAttributeKey), mouseX, mouseY);
}
@@ -124,64 +145,96 @@ public class StatsScreen extends Screen {
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);
graphics.fill(-6, -6, WIDTH + 6, HEIGHT + 6, 0xCC090A0C);
graphics.fill(-3, -3, WIDTH + 3, HEIGHT + 3, BORDER);
graphics.fill(0, 0, WIDTH, HEIGHT, BACKGROUND);
graphics.fill(0, 0, WIDTH, 20, PANEL);
graphics.fill(0, 20, WIDTH, 21, BORDER);
RenderSystem.disableBlend();
graphics.drawCenteredString(font, title, leftPos + WIDTH / 2, topPos + 6, 0xFFF4EAFB);
graphics.drawCenteredString(font, title, WIDTH / 2, 6, PRIMARY_TEXT);
}
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 panelX = PADDING;
int panelY = 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.fill(panelX, panelY, panelX + panelWidth, panelY + 45, PANEL);
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);
panelX + 9, panelY + 5, PROGRESS, false);
graphics.drawCenteredString(font, Component.translatable("ascend.stats.knowledge", knowledge),
panelX + panelWidth / 2, panelY + 5, SECONDARY_TEXT);
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);
graphics.drawString(font, points, pointsX, panelY + 5, unspent > 0 ? AVAILABLE : SECONDARY_TEXT, 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);
graphics.fill(barX, barY, barX + barWidth, barY + 7, BORDER);
graphics.fill(barX + 1, barY + 1, barX + barWidth - 1, barY + 6, BACKGROUND);
double progress = level >= maxLevel || xpNext <= 0 ? 1.0 : Math.max(0.0, Math.min(1.0, (double) xp / xpNext));
graphics.fill(barX + 1, barY + 1, barX + 1 + (int) ((barWidth - 2) * progress), barY + 6, PROGRESS);
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);
graphics.drawCenteredString(font, xpLabel, barX + barWidth / 2, barY + 11, PROGRESS);
}
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.fill(x, y, x + CARD_WIDTH, y + CARD_HEIGHT, hovered ? ACCENT : BORDER);
graphics.fill(x + 1, y + 1, x + CARD_WIDTH - 1, y + CARD_HEIGHT - 1, CARD);
drawAttributeIcon(graphics, attribute, x + 8, y + 5);
graphics.drawString(font, Component.translatable("ascend.attribute." + attribute),
x + 9, y + 7, 0xFFF1ECF4, false);
x + 25, y + 7, PRIMARY_TEXT, 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, valueLabel, valueX, y + 7, value >= maximum ? PROGRESS : PRIMARY_TEXT, false);
graphics.drawString(font, Component.translatable("ascend.attribute." + attribute + ".short"),
x + 9, y + 26, 0xFF9F98A8, false);
x + 9, y + 26, SECONDARY_TEXT, false);
}
private void drawAttributeIcon(GuiGraphics graphics, String attribute, int x, int y) {
int[] rows = ATTRIBUTE_ICONS.get(attribute);
for (int row = 0; row < rows.length; row++) {
for (int column = 0; column < 12; column++) {
if ((rows[row] & (1 << (11 - column))) != 0) {
graphics.fill(x + column, y + row, x + column + 1, y + row + 1, ACCENT);
}
}
}
}
@Override
public boolean isPauseScreen() {
return false;
}
private double toUiX(double mouseX) {
return (mouseX - uiLeft) / uiScale;
}
private double toUiY(double mouseY) {
return (mouseY - uiTop) / uiScale;
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
return super.mouseClicked(toUiX(mouseX), toUiY(mouseY), button);
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int button) {
return super.mouseReleased(toUiX(mouseX), toUiY(mouseY), button);
}
@Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) {
return super.mouseDragged(toUiX(mouseX), toUiY(mouseY), button, dragX / uiScale, dragY / uiScale);
}
}
@@ -0,0 +1,50 @@
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.minecraft.world.entity.projectile.Projectile;
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 final class ArcaneEffectEvents {
private static final ThreadLocal<Boolean> MODIFYING = ThreadLocal.withInitial(() -> false);
@SubscribeEvent
public static void onEffectAdded(MobEffectEvent.Added event) {
if (MODIFYING.get() || event.getEntity().level().isClientSide || event.getOldEffectInstance() != null) return;
MobEffectInstance added = event.getEffectInstance();
if (added.isInfiniteDuration() || added.getDuration() <= 0) return;
Player arcaneOwner;
boolean beneficialSelf = event.getEntity() instanceof Player player
&& added.getEffect().getCategory() == MobEffectCategory.BENEFICIAL;
if (beneficialSelf) arcaneOwner = (Player) event.getEntity();
else if (added.getEffect().getCategory() == MobEffectCategory.HARMFUL) {
if (event.getEffectSource() instanceof Player source && source != event.getEntity()) arcaneOwner = source;
else if (event.getEffectSource() instanceof Projectile projectile
&& projectile.getOwner() instanceof Player source && source != event.getEntity()) arcaneOwner = source;
else return;
} else return;
arcaneOwner.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
int arcane = stats.getAttributeLevel("arcane");
if (arcane <= 0) return;
double multiplier = beneficialSelf ? StatEffects.getArcaneBeneficialDurationMultiplier(arcane)
: StatEffects.getArcaneHarmfulDurationMultiplier(arcane);
MobEffectInstance active = event.getEntity().getEffect(added.getEffect());
if (active == null || active.isInfiniteDuration()) return;
int oldDuration = active.getDuration();
int newDuration = (int) Math.min(Integer.MAX_VALUE, Math.round(oldDuration * multiplier));
if (newDuration <= oldDuration) return;
MODIFYING.set(true);
try { active.mapDuration(ignored -> newDuration); }
finally { MODIFYING.set(false); }
});
}
}
@@ -0,0 +1,136 @@
package net.fretux.ascend.event;
import com.google.common.collect.Multimap;
import net.fretux.ascend.AscendMod;
import net.fretux.ascend.api.event.ArmorPenetrationEvent;
import net.fretux.ascend.config.AscendConfig;
import net.fretux.ascend.player.PlayerStatsProvider;
import net.fretux.ascend.player.StatEffects;
import net.fretux.ascend.scaling.ScalingGrade;
import net.fretux.ascend.scaling.WeaponScaling;
import net.fretux.ascend.scaling.WeaponScalingRegistry;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
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.ThrownTrident;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.DamageTypeTags;
import net.minecraft.world.damagesource.CombatRules;
import net.minecraft.world.entity.LivingEntity;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.event.entity.EntityJoinLevelEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.registries.ForgeRegistries;
@Mod.EventBusSubscriber(modid = AscendMod.MODID)
public final class WeaponScalingEvents {
private static final String PROJECTILE_WEAPON_TAG = "ascend:scaling_weapon";
@SubscribeEvent
public static void rememberProjectileWeapon(EntityJoinLevelEvent event) {
if (event.getLevel().isClientSide || !(event.getEntity() instanceof AbstractArrow arrow)
|| arrow instanceof ThrownTrident || !(arrow.getOwner() instanceof Player player)) return;
ItemStack stack = player.getMainHandItem();
if (!stack.is(Items.BOW) && !stack.is(Items.CROSSBOW)) return;
ResourceLocation id = ForgeRegistries.ITEMS.getKey(stack.getItem());
if (id != null) arrow.getPersistentData().putString(PROJECTILE_WEAPON_TAG, id.toString());
}
@SubscribeEvent
public static void addWeaponScaling(LivingHurtEvent event) {
if (!(event.getSource().getEntity() instanceof Player player) || player.level().isClientSide || event.getEntity() == player) return;
Item item = resolveWeapon(player, event.getSource().getDirectEntity());
WeaponScaling scaling = item == null ? null : WeaponScalingRegistry.get(item);
if (scaling == null) return;
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
double baseDamage = event.getSource().getDirectEntity() instanceof AbstractArrow
? event.getAmount() : getPhysicalBaseDamage(new ItemStack(item), event.getAmount());
double bonus = WeaponScalingRegistry.calculateWeaponScalingBonus(baseDamage, scaling, player, stats);
if (bonus > 0) event.setAmount((float) (event.getAmount() + bonus));
});
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public static void applyArmorPenetration(LivingHurtEvent event) {
if (!(event.getSource().getEntity() instanceof Player player) || player.level().isClientSide || event.getEntity() == player) return;
if (event.getSource().is(DamageTypeTags.BYPASSES_ARMOR) || event.getAmount() <= 0 || event.getEntity().getArmorValue() <= 0) return;
Item item = resolveWeapon(player, event.getSource().getDirectEntity());
WeaponScaling scaling = item == null ? null : WeaponScalingRegistry.get(item);
ItemStack weapon = item == null ? ItemStack.EMPTY : new ItemStack(item);
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
double basePenetration = scaling != null && scaling.grade("strength") != ScalingGrade.NONE
? StatEffects.getStrengthArmorPenetration(stats.getAttributeLevel("strength")) : 0.0;
ArmorPenetrationEvent penetrationEvent = new ArmorPenetrationEvent(player, event.getEntity(),
event.getSource(), weapon, basePenetration, configuredArmorPenetrationCap());
MinecraftForge.EVENT_BUS.post(penetrationEvent);
double penetration = penetrationEvent.getEffectiveArmorPenetration();
if (penetration <= 0) return;
event.setAmount(getPreArmorDamageForReducedArmor(event.getEntity(), event.getAmount(), penetration));
});
}
/**
* Finds the pre-armor damage that produces the same result through vanilla's
* normal armor pipeline as applying the original hit against reduced armor.
*/
static float getPreArmorDamageForReducedArmor(LivingEntity target, float incomingDamage, double penetration) {
float armor = Math.max(0.0F, target.getArmorValue());
float toughness = (float) Math.max(0.0, target.getAttributeValue(Attributes.ARMOR_TOUGHNESS));
return getPreArmorDamageForReducedArmor(incomingDamage, armor, toughness, penetration);
}
static float getPreArmorDamageForReducedArmor(float incomingDamage, float armor, float toughness, double penetration) {
float reducedArmor = armor * (1.0F - (float) Math.max(0.0, Math.min(1.0, penetration)));
float desiredPostArmor = CombatRules.getDamageAfterAbsorb(incomingDamage, reducedArmor, toughness);
if (desiredPostArmor <= CombatRules.getDamageAfterAbsorb(incomingDamage, armor, toughness)) return incomingDamage;
float low = incomingDamage;
float high = Math.max(incomingDamage + 1.0F, incomingDamage * 2.0F);
while (CombatRules.getDamageAfterAbsorb(high, armor, toughness) < desiredPostArmor && high < Float.MAX_VALUE / 4.0F) {
high *= 2.0F;
}
for (int i = 0; i < 32; i++) {
float middle = low + (high - low) * 0.5F;
if (CombatRules.getDamageAfterAbsorb(middle, armor, toughness) < desiredPostArmor) low = middle;
else high = middle;
}
return high;
}
private static double configuredArmorPenetrationCap() {
return AscendConfig.COMMON_SPEC.isLoaded()
? AscendConfig.COMMON.armorPenetrationCap.get()
: AscendConfig.COMMON.armorPenetrationCap.getDefault();
}
private static Item resolveWeapon(Player player, net.minecraft.world.entity.Entity direct) {
if (direct instanceof FireworkRocketEntity) return null;
if (direct instanceof ThrownTrident) return Items.TRIDENT;
ItemStack stack = player.getMainHandItem();
if (direct instanceof AbstractArrow) {
String storedId = direct.getPersistentData().getString(PROJECTILE_WEAPON_TAG);
ResourceLocation id = ResourceLocation.tryParse(storedId);
if (id != null && ForgeRegistries.ITEMS.containsKey(id)) return ForgeRegistries.ITEMS.getValue(id);
return stack.is(Items.BOW) || stack.is(Items.CROSSBOW) ? stack.getItem() : null;
}
return direct == player && !stack.isEmpty() ? stack.getItem() : null;
}
private static double getPhysicalBaseDamage(ItemStack stack, float eventDamage) {
Multimap<net.minecraft.world.entity.ai.attributes.Attribute, AttributeModifier> modifiers =
stack.getAttributeModifiers(EquipmentSlot.MAINHAND);
double itemDamage = modifiers.get(Attributes.ATTACK_DAMAGE).stream()
.filter(modifier -> modifier.getOperation() == AttributeModifier.Operation.ADDITION)
.mapToDouble(AttributeModifier::getAmount).sum();
return Math.max(0, Math.min(eventDamage, 1.0 + itemDamage));
}
}
@@ -0,0 +1,56 @@
package net.fretux.ascend.player;
import net.fretux.ascend.config.AscendConfig;
/** Shared piecewise soft-cap curve used by attributes, weapons, and integrations. */
public final class AttributeScaling {
private AttributeScaling() {}
public static double getEffectiveAttributeValue(int investment) {
int value = Math.max(0, Math.min(investment, intValue(AscendConfig.COMMON.maxAttributePoints)));
return calculateEffectiveAttributeValue(value);
}
/**
* Applies the configured curve without enforcing the investable attribute cap.
* Intended for combined values that include passive points supplied by integrations.
*/
public static double getExtendedEffectiveAttributeValue(int investment) {
return calculateEffectiveAttributeValue(Math.max(0, investment));
}
private static double calculateEffectiveAttributeValue(int value) {
int c1 = intValue(AscendConfig.COMMON.softCap1);
int c2 = Math.max(c1, intValue(AscendConfig.COMMON.softCap2));
int c3 = Math.max(c2, intValue(AscendConfig.COMMON.softCap3));
int c4 = Math.max(c3, intValue(AscendConfig.COMMON.softCap4));
double effective = segment(value, 0, c1, 1.0);
effective += segment(value, c1, c2, doubleValue(AscendConfig.COMMON.softCapGain2));
effective += segment(value, c2, c3, doubleValue(AscendConfig.COMMON.softCapGain3));
effective += segment(value, c3, c4, doubleValue(AscendConfig.COMMON.softCapGain4));
effective += Math.max(0, value - c4) * doubleValue(AscendConfig.COMMON.softCapGain5);
return effective * doubleValue(AscendConfig.COMMON.attributeScalingMultiplier);
}
public static double getNormalizedAttributeValue(int investment) {
double max = getEffectiveAttributeValue(intValue(AscendConfig.COMMON.maxAttributePoints));
return max <= 0 ? 0 : Math.min(1.0, getEffectiveAttributeValue(investment) / max);
}
/** Returns a value normalized at the investable cap while permitting passive overflow above {@code 1.0}. */
public static double getExtendedNormalizedAttributeValue(int investment) {
double max = getEffectiveAttributeValue(intValue(AscendConfig.COMMON.maxAttributePoints));
return max <= 0 ? 0 : getExtendedEffectiveAttributeValue(investment) / max;
}
private static double segment(int value, int start, int end, double rate) {
return Math.max(0, Math.min(value, end) - start) * rate;
}
private static int intValue(net.minecraftforge.common.ForgeConfigSpec.IntValue value) {
return AscendConfig.COMMON_SPEC.isLoaded() ? value.get() : value.getDefault();
}
private static double doubleValue(net.minecraftforge.common.ForgeConfigSpec.DoubleValue value) {
return AscendConfig.COMMON_SPEC.isLoaded() ? value.get() : value.getDefault();
}
}
@@ -0,0 +1,24 @@
package net.fretux.ascend.scaling;
import net.fretux.ascend.config.AscendConfig;
public enum ScalingGrade {
S, A, B, C, D, E, NONE;
public double coefficient() {
return switch (this) {
case S -> AscendConfig.COMMON.gradeS.get();
case A -> AscendConfig.COMMON.gradeA.get();
case B -> AscendConfig.COMMON.gradeB.get();
case C -> AscendConfig.COMMON.gradeC.get();
case D -> AscendConfig.COMMON.gradeD.get();
case E -> AscendConfig.COMMON.gradeE.get();
case NONE -> 0.0;
};
}
public static ScalingGrade parse(String value) {
try { return valueOf(value.toUpperCase(java.util.Locale.ROOT)); }
catch (RuntimeException ignored) { return NONE; }
}
}
@@ -0,0 +1,15 @@
package net.fretux.ascend.scaling;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
public record WeaponScaling(Map<String, ScalingGrade> attributes) {
public static final String[] SUPPORTED_ATTRIBUTES = {"strength", "agility", "intelligence", "willpower", "charisma", "arcane"};
public WeaponScaling {
attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes));
}
public ScalingGrade grade(String attribute) { return attributes.getOrDefault(attribute, ScalingGrade.NONE); }
}
@@ -0,0 +1,111 @@
package net.fretux.ascend.scaling;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.fretux.ascend.AscendMod;
import net.fretux.ascend.api.PassiveAgilityApi;
import net.fretux.ascend.player.AttributeScaling;
import net.fretux.ascend.player.PlayerStats;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.item.Item;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.event.AddReloadListenerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.registries.ForgeRegistries;
import java.util.LinkedHashMap;
import java.util.Map;
@Mod.EventBusSubscriber(modid = AscendMod.MODID)
public final class WeaponScalingRegistry extends SimpleJsonResourceReloadListener {
private static final Gson GSON = new Gson();
private static volatile Map<Item, WeaponScaling> entries = createVanillaDefaults();
public WeaponScalingRegistry() { super(GSON, "weapon_scaling"); }
@SubscribeEvent
public static void addReloadListener(AddReloadListenerEvent event) { event.addListener(new WeaponScalingRegistry()); }
public static WeaponScaling get(Item item) { return entries.get(item); }
/** Public API for integrations; datapack reloads remain authoritative and may replace this entry. */
public static synchronized void register(Item item, WeaponScaling scaling) {
Map<Item, WeaponScaling> copy = new LinkedHashMap<>(entries);
copy.put(item, scaling);
entries = Map.copyOf(copy);
}
public static double getScalingCoefficient(ScalingGrade grade) { return grade.coefficient(); }
public static double calculateWeaponScalingBonus(double baseDamage, WeaponScaling scaling, PlayerStats stats) {
return calculateWeaponScalingBonus(baseDamage, scaling, null, stats);
}
/** Calculates weapon scaling, including passive Agility supplied through Ascend's public API. */
public static double calculateWeaponScalingBonus(double baseDamage, WeaponScaling scaling, Player player, PlayerStats stats) {
if (baseDamage <= 0 || scaling == null) return 0;
double coefficient = 0;
for (String attribute : WeaponScaling.SUPPORTED_ATTRIBUTES) {
ScalingGrade grade = scaling.grade(attribute);
double normalized = "agility".equals(attribute) && player != null
? PassiveAgilityApi.getNormalizedAgility(player, stats.getAttributeLevel(attribute))
: AttributeScaling.getNormalizedAttributeValue(stats.getAttributeLevel(attribute));
coefficient += getScalingCoefficient(grade) * normalized;
}
return baseDamage * coefficient;
}
@Override
protected void apply(Map<ResourceLocation, JsonElement> resources, ResourceManager manager, ProfilerFiller profiler) {
Map<Item, WeaponScaling> loaded = createVanillaDefaults();
resources.forEach((file, json) -> {
try {
JsonObject root = json.getAsJsonObject();
if (root.has("entries")) {
root.getAsJsonArray("entries").forEach(element -> loadEntry(loaded, element.getAsJsonObject()));
} else loadEntry(loaded, root);
} catch (RuntimeException ex) {
AscendMod.LOGGER.warn("Ignoring invalid weapon scaling data {}: {}", file, ex.getMessage());
}
});
entries = Map.copyOf(loaded);
AscendMod.LOGGER.info("Loaded {} Ascend weapon scaling entries", entries.size());
}
private static void loadEntry(Map<Item, WeaponScaling> target, JsonObject json) {
ResourceLocation id = ResourceLocation.tryParse(json.get("item").getAsString());
if (id == null || !ForgeRegistries.ITEMS.containsKey(id)) return;
Map<String, ScalingGrade> grades = new LinkedHashMap<>();
JsonObject scaling = json.getAsJsonObject("scaling");
for (String key : WeaponScaling.SUPPORTED_ATTRIBUTES) {
if (scaling.has(key)) grades.put(key, ScalingGrade.parse(scaling.get(key).getAsString()));
}
target.put(ForgeRegistries.ITEMS.getValue(id), new WeaponScaling(grades));
}
private static Map<Item, WeaponScaling> createVanillaDefaults() {
Map<Item, WeaponScaling> result = new LinkedHashMap<>();
String[][] defaults = {
{"wooden_sword","D","D"},{"stone_sword","D","C"},{"iron_sword","C","C"},{"golden_sword","E","B"},{"diamond_sword","C","B"},{"netherite_sword","B","B"},
{"wooden_axe","C","E"},{"stone_axe","B","E"},{"iron_axe","B","D"},{"golden_axe","D","C"},{"diamond_axe","A","D"},{"netherite_axe","A","C"},
{"wooden_pickaxe","D","E"},{"stone_pickaxe","C","E"},{"iron_pickaxe","C","E"},{"golden_pickaxe","E","D"},{"diamond_pickaxe","B","E"},{"netherite_pickaxe","B","E"},
{"wooden_shovel","D","E"},{"stone_shovel","D","E"},{"iron_shovel","C","E"},{"golden_shovel","E","D"},{"diamond_shovel","C","E"},{"netherite_shovel","C","E"},
{"wooden_hoe","E","D"},{"stone_hoe","E","C"},{"iron_hoe","E","B"},{"golden_hoe","E","B"},{"diamond_hoe","D","B"},{"netherite_hoe","D","A"},
{"bow","NONE","A"},{"crossbow","D","B"},{"trident","C","B"},{"mace","A","D"}
};
for (String[] row : defaults) {
ResourceLocation id = ResourceLocation.fromNamespaceAndPath("minecraft", row[0]);
if (!ForgeRegistries.ITEMS.containsKey(id)) continue;
Map<String, ScalingGrade> grades = new LinkedHashMap<>();
grades.put("strength", ScalingGrade.parse(row[1]));
grades.put("agility", ScalingGrade.parse(row[2]));
result.put(ForgeRegistries.ITEMS.getValue(id), new WeaponScaling(grades));
}
return result;
}
}
-2
View File
@@ -27,8 +27,6 @@ displayName="${mod_name}" #mandatory
#updateJSONURL="https://change.me.example.invalid/updates.json" #optional
# A URL for the "homepage" for this mod, displayed in the mod UI
#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional
# A file name (in the root of the mod JAR) containing a logo for display
#logoFile="examplemod.png" #optional
# A text field displayed in the mod UI
#credits="" #optional
# A text field displayed in the mod UI
@@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "ascend:block/shrine_of_remembrance"
}
}
}
@@ -2,10 +2,10 @@
"key.ascend.open_stats": "Open Ascend Stats",
"key.categories.inventory": "Inventory",
"ascend.stats.title": "Ascend Stats",
"ascend.stats.level": "Level: %s/%s",
"ascend.stats.xp": "Ascend XP: %s / %s",
"ascend.stats.xp_max": "Ascend XP: MAX",
"ascend.stats.unspent": "Unspent Points: %s",
"ascend.stats.level": "Level %s / %s",
"ascend.stats.xp": "%s / %s XP",
"ascend.stats.xp_max": "Maximum Level",
"ascend.stats.unspent": "%s Points Available",
"ascend.attribute.vigor": "Vigor",
"ascend.attribute.vigor.desc": "Increases maximum health.",
@@ -48,7 +48,7 @@
"advancements.ascend.level_5.description": "Reach Ascend level 5",
"advancements.ascend.level_10.title": "Beyond Mortal Limits",
"advancements.ascend.level_10.description": "Reach Ascend level 10",
"advancements.ascend.max_level.title": "This is Ascend",
"advancements.ascend.max_level.title": "Ascendant",
"advancements.ascend.max_level.description": "Reach the maximum Ascend level",
"advancements.ascend.max_attribute.title": "Fear Not the One-Trick",
"advancements.ascend.max_attribute.description": "Max out any Ascend attribute",
@@ -76,7 +76,7 @@
"advancements.ascend.perfectly_balanced.title": "Perfectly Balanced",
"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.indecisive_chosen_one.description": "Respec ten times in the same world",
"advancements.ascend.config_enjoyer.title": "Config Enjoyer",
"advancements.ascend.config_enjoyer.description": "Make the intended progression unrecognizable",
"advancements.ascend.game_journalist_mode.title": "Game Journalist Mode",
@@ -0,0 +1,3 @@
{
"parent": "ascend:block/shrine_of_remembrance"
}
@@ -0,0 +1,10 @@
{
"parent": "ascend:first_steps",
"display": {
"icon": { "item": "minecraft:chainmail_helmet" },
"title": { "translate": "advancements.ascend.big_brain_small_helmet.title" },
"description": { "translate": "advancements.ascend.big_brain_small_helmet.description" },
"frame": "challenge", "show_toast": true, "announce_to_chat": true, "hidden": true
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,10 @@
{
"parent": "ascend:first_steps",
"display": {
"icon": { "item": "minecraft:anvil" },
"title": { "translate": "advancements.ascend.bonk_scholar.title" },
"description": { "translate": "advancements.ascend.bonk_scholar.description" },
"frame": "challenge", "show_toast": true, "announce_to_chat": true, "hidden": true
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,10 @@
{
"parent": "ascend:first_steps",
"display": {
"icon": { "item": "minecraft:ender_pearl" },
"title": { "translate": "advancements.ascend.cannot_hit_what_you_cannot_see.title" },
"description": { "translate": "advancements.ascend.cannot_hit_what_you_cannot_see.description" },
"frame": "challenge", "show_toast": true, "announce_to_chat": true, "hidden": true
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,10 @@
{
"parent": "ascend:first_steps",
"display": {
"icon": { "item": "minecraft:comparator" },
"title": { "translate": "advancements.ascend.config_enjoyer.title" },
"description": { "translate": "advancements.ascend.config_enjoyer.description" },
"frame": "challenge", "show_toast": true, "announce_to_chat": true, "hidden": true
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,10 @@
{
"parent": "ascend:first_steps",
"display": {
"icon": { "item": "minecraft:clock" },
"title": { "translate": "advancements.ascend.definition_of_insanity.title" },
"description": { "translate": "advancements.ascend.definition_of_insanity.description" },
"frame": "challenge", "show_toast": true, "announce_to_chat": true, "hidden": true
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,13 @@
{
"display": {
"icon": { "item": "minecraft:experience_bottle" },
"title": { "translate": "advancements.ascend.first_steps.title" },
"description": { "translate": "advancements.ascend.first_steps.description" },
"background": "minecraft:textures/gui/advancements/backgrounds/adventure.png",
"frame": "task",
"show_toast": true,
"announce_to_chat": true,
"hidden": false
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,10 @@
{
"parent": "ascend:first_steps",
"display": {
"icon": { "item": "minecraft:experience_bottle" },
"title": { "translate": "advancements.ascend.game_journalist_mode.title" },
"description": { "translate": "advancements.ascend.game_journalist_mode.description" },
"frame": "challenge", "show_toast": true, "announce_to_chat": true, "hidden": true
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,10 @@
{
"parent": "ascend:first_steps",
"display": {
"icon": { "item": "minecraft:golden_apple" },
"title": { "translate": "advancements.ascend.health_bar_enthusiast.title" },
"description": { "translate": "advancements.ascend.health_bar_enthusiast.description" },
"frame": "challenge", "show_toast": true, "announce_to_chat": true, "hidden": true
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,10 @@
{
"parent": "ascend:first_steps",
"display": {
"icon": { "item": "ascend:remembrance_essence" },
"title": { "translate": "advancements.ascend.indecisive_chosen_one.title" },
"description": { "translate": "advancements.ascend.indecisive_chosen_one.description" },
"frame": "challenge", "show_toast": true, "announce_to_chat": true, "hidden": true
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,13 @@
{
"parent": "ascend:first_steps",
"display": {
"icon": { "item": "minecraft:enchanting_table" },
"title": { "translate": "advancements.ascend.knowledge_100.title" },
"description": { "translate": "advancements.ascend.knowledge_100.description" },
"frame": "goal",
"show_toast": true,
"announce_to_chat": true,
"hidden": false
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,13 @@
{
"parent": "ascend:level_5",
"display": {
"icon": { "item": "minecraft:diamond_sword" },
"title": { "translate": "advancements.ascend.level_10.title" },
"description": { "translate": "advancements.ascend.level_10.description" },
"frame": "goal",
"show_toast": true,
"announce_to_chat": true,
"hidden": false
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,13 @@
{
"parent": "ascend:first_steps",
"display": {
"icon": { "item": "minecraft:iron_sword" },
"title": { "translate": "advancements.ascend.level_5.title" },
"description": { "translate": "advancements.ascend.level_5.description" },
"frame": "task",
"show_toast": true,
"announce_to_chat": true,
"hidden": false
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,10 @@
{
"parent": "ascend:first_steps",
"display": {
"icon": { "item": "minecraft:glass_bottle" },
"title": { "translate": "advancements.ascend.mana_without_purpose.title" },
"description": { "translate": "advancements.ascend.mana_without_purpose.description" },
"frame": "goal", "show_toast": true, "announce_to_chat": true, "hidden": true
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,14 @@
{
"parent": "ascend:first_steps",
"display": {
"icon": { "item": "minecraft:enchanted_golden_apple" },
"title": { "translate": "advancements.ascend.max_attribute.title" },
"description": { "translate": "advancements.ascend.max_attribute.description" },
"frame": "challenge",
"show_toast": true,
"announce_to_chat": true,
"hidden": false
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } },
"rewards": { "experience": 250 }
}
@@ -0,0 +1,14 @@
{
"parent": "ascend:level_10",
"display": {
"icon": { "item": "minecraft:nether_star" },
"title": { "translate": "advancements.ascend.max_level.title" },
"description": { "translate": "advancements.ascend.max_level.description" },
"frame": "challenge",
"show_toast": true,
"announce_to_chat": true,
"hidden": false
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } },
"rewards": { "experience": 500 }
}
@@ -0,0 +1,10 @@
{
"parent": "ascend:first_steps",
"display": {
"icon": { "item": "minecraft:recovery_compass" },
"title": { "translate": "advancements.ascend.perfectly_balanced.title" },
"description": { "translate": "advancements.ascend.perfectly_balanced.description" },
"frame": "challenge", "show_toast": true, "announce_to_chat": true, "hidden": true
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,13 @@
{
"parent": "ascend:knowledge_100",
"display": {
"icon": { "item": "ascend:moonseye_tome" },
"title": { "translate": "advancements.ascend.read_moonseye_tome.title" },
"description": { "translate": "advancements.ascend.read_moonseye_tome.description" },
"frame": "goal",
"show_toast": true,
"announce_to_chat": true,
"hidden": false
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,10 @@
{
"parent": "ascend:first_steps",
"display": {
"icon": { "item": "minecraft:emerald" },
"title": { "translate": "advancements.ascend.talk_no_jutsu.title" },
"description": { "translate": "advancements.ascend.talk_no_jutsu.description" },
"frame": "challenge", "show_toast": true, "announce_to_chat": true, "hidden": true
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,10 @@
{
"parent": "ascend:first_steps",
"display": {
"icon": { "item": "minecraft:amethyst_shard" },
"title": { "translate": "advancements.ascend.unlicensed_therapist.title" },
"description": { "translate": "advancements.ascend.unlicensed_therapist.description" },
"frame": "challenge", "show_toast": true, "announce_to_chat": true, "hidden": true
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,13 @@
{
"parent": "ascend:first_steps",
"display": {
"icon": { "item": "ascend:remembrance_essence" },
"title": { "translate": "advancements.ascend.use_shrine.title" },
"description": { "translate": "advancements.ascend.use_shrine.description" },
"frame": "goal",
"show_toast": true,
"announce_to_chat": true,
"hidden": false
},
"criteria": { "achieved": { "trigger": "minecraft:impossible" } }
}
@@ -0,0 +1,38 @@
{
"entries": [
{"item":"minecraft:wooden_sword","scaling":{"strength":"D","agility":"D"}},
{"item":"minecraft:stone_sword","scaling":{"strength":"D","agility":"C"}},
{"item":"minecraft:iron_sword","scaling":{"strength":"C","agility":"C"}},
{"item":"minecraft:golden_sword","scaling":{"strength":"E","agility":"B"}},
{"item":"minecraft:diamond_sword","scaling":{"strength":"C","agility":"B"}},
{"item":"minecraft:netherite_sword","scaling":{"strength":"B","agility":"B"}},
{"item":"minecraft:wooden_axe","scaling":{"strength":"C","agility":"E"}},
{"item":"minecraft:stone_axe","scaling":{"strength":"B","agility":"E"}},
{"item":"minecraft:iron_axe","scaling":{"strength":"B","agility":"D"}},
{"item":"minecraft:golden_axe","scaling":{"strength":"D","agility":"C"}},
{"item":"minecraft:diamond_axe","scaling":{"strength":"A","agility":"D"}},
{"item":"minecraft:netherite_axe","scaling":{"strength":"A","agility":"C"}},
{"item":"minecraft:wooden_pickaxe","scaling":{"strength":"D","agility":"E"}},
{"item":"minecraft:stone_pickaxe","scaling":{"strength":"C","agility":"E"}},
{"item":"minecraft:iron_pickaxe","scaling":{"strength":"C","agility":"E"}},
{"item":"minecraft:golden_pickaxe","scaling":{"strength":"E","agility":"D"}},
{"item":"minecraft:diamond_pickaxe","scaling":{"strength":"B","agility":"E"}},
{"item":"minecraft:netherite_pickaxe","scaling":{"strength":"B","agility":"E"}},
{"item":"minecraft:wooden_shovel","scaling":{"strength":"D","agility":"E"}},
{"item":"minecraft:stone_shovel","scaling":{"strength":"D","agility":"E"}},
{"item":"minecraft:iron_shovel","scaling":{"strength":"C","agility":"E"}},
{"item":"minecraft:golden_shovel","scaling":{"strength":"E","agility":"D"}},
{"item":"minecraft:diamond_shovel","scaling":{"strength":"C","agility":"E"}},
{"item":"minecraft:netherite_shovel","scaling":{"strength":"C","agility":"E"}},
{"item":"minecraft:wooden_hoe","scaling":{"strength":"E","agility":"D"}},
{"item":"minecraft:stone_hoe","scaling":{"strength":"E","agility":"C"}},
{"item":"minecraft:iron_hoe","scaling":{"strength":"E","agility":"B"}},
{"item":"minecraft:golden_hoe","scaling":{"strength":"E","agility":"B"}},
{"item":"minecraft:diamond_hoe","scaling":{"strength":"D","agility":"B"}},
{"item":"minecraft:netherite_hoe","scaling":{"strength":"D","agility":"A"}},
{"item":"minecraft:bow","scaling":{"strength":"NONE","agility":"A"}},
{"item":"minecraft:crossbow","scaling":{"strength":"D","agility":"B"}},
{"item":"minecraft:trident","scaling":{"strength":"C","agility":"B"}},
{"item":"minecraft:mace","scaling":{"strength":"A","agility":"D"}}
]
}