Compare commits
5 Commits
1af9f1d7fa
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c5db5baee | |||
| c85906aa49 | |||
| efb1b98868 | |||
| cfccdd9b36 | |||
| 53701c2dab |
@@ -16,6 +16,8 @@ out
|
||||
# gradle
|
||||
build
|
||||
.gradle
|
||||
.gradle-user-home
|
||||
mcmodsrepo
|
||||
|
||||
# other
|
||||
eclipse
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"}}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user