Compare commits

...

2 Commits

39 changed files with 1024 additions and 43 deletions
+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.
+2 -2
View File
@@ -23,7 +23,7 @@ loader_version_range=[47,)
# Additional mapping channels can be registered through the "channelProviders" extension in a Gradle plugin.
#
mindmotion_version=1.4.5
skillengine_version=1.4.5
skillengine_version=1.4.14
irons_spells_version=1.20.1-3.16.2
irons_spells_file_id=8364933
caelus_version=3.2.0+1.20.1
@@ -56,7 +56,7 @@ mod_name=Ascend: SbA
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=All Rights Reserved
# The mod version. See https://semver.org/
mod_version=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)));
}
}
}
@@ -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<>();
@@ -124,14 +134,14 @@ 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(leftPos - 6, topPos - 6, leftPos + WIDTH + 6, topPos + HEIGHT + 6, 0xCC090A0C);
graphics.fill(leftPos - 3, topPos - 3, leftPos + WIDTH + 3, topPos + HEIGHT + 3, BORDER);
graphics.fill(leftPos, topPos, leftPos + WIDTH, topPos + HEIGHT, BACKGROUND);
graphics.fill(leftPos, topPos, leftPos + WIDTH, topPos + 20, PANEL);
graphics.fill(leftPos, topPos + 20, leftPos + WIDTH, topPos + 21, BORDER);
RenderSystem.disableBlend();
graphics.drawCenteredString(font, title, leftPos + WIDTH / 2, topPos + 6, 0xFFF4EAFB);
graphics.drawCenteredString(font, title, leftPos + WIDTH / 2, topPos + 6, PRIMARY_TEXT);
}
private void drawProgression(GuiGraphics graphics, int level, int maxLevel, int xp, int xpNext,
@@ -139,45 +149,54 @@ public class StatsScreen extends Screen {
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.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
@@ -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
@@ -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.",
File diff suppressed because one or more lines are too long
@@ -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"}}
]
}