Implement config and fix small bugs

This commit is contained in:
2025-11-09 13:03:08 +01:00
parent 459a5f7843
commit d655f6a25d
10 changed files with 129 additions and 166 deletions
@@ -1,8 +1,10 @@
package net.fretux.ascend;
import net.fretux.ascend.config.AscendConfig;
import net.fretux.ascend.network.PacketHandler;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
@@ -17,6 +19,7 @@ public class AscendMod {
modBus.addListener(this::onCommonSetup);
MinecraftForge.EVENT_BUS.register(this);
System.out.println("[Ascend] Mod loaded! Side=" + FMLEnvironment.dist);
net.minecraftforge.fml.ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, AscendConfig.COMMON_SPEC);
}
private void onCommonSetup(final FMLCommonSetupEvent event) {
@@ -1,55 +0,0 @@
package net.fretux.ascend;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.config.ModConfigEvent;
import net.minecraftforge.registries.ForgeRegistries;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Mod.EventBusSubscriber(modid = AscendMod.MODID, bus = Mod.EventBusSubscriber.Bus.MOD)
public class Config {
private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder();
private static final ForgeConfigSpec.BooleanValue LOG_DIRT_BLOCK = BUILDER
.comment("Whether to log the dirt block on common setup")
.define("logDirtBlock", true);
private static final ForgeConfigSpec.IntValue MAGIC_NUMBER = BUILDER
.comment("A magic number")
.defineInRange("magicNumber", 42, 0, Integer.MAX_VALUE);
public static final ForgeConfigSpec.ConfigValue<String> MAGIC_NUMBER_INTRODUCTION = BUILDER
.comment("What you want the introduction message to be for the magic number")
.define("magicNumberIntroduction", "The magic number is... ");
private static final ForgeConfigSpec.ConfigValue<List<? extends String>> ITEM_STRINGS = BUILDER
.comment("A list of items to log on common setup.")
.defineListAllowEmpty("items", List.of("minecraft:iron_ingot"), Config::validateItemName);
static final ForgeConfigSpec SPEC = BUILDER.build();
public static boolean logDirtBlock;
public static int magicNumber;
public static String magicNumberIntroduction;
public static Set<Item> items;
private static boolean validateItemName(final Object obj) {
return obj instanceof final String itemName && ForgeRegistries.ITEMS.containsKey(new ResourceLocation(itemName));
}
@SubscribeEvent
static void onLoad(final ModConfigEvent event) {
logDirtBlock = LOG_DIRT_BLOCK.get();
magicNumber = MAGIC_NUMBER.get();
magicNumberIntroduction = MAGIC_NUMBER_INTRODUCTION.get();
items = ITEM_STRINGS.get().stream()
.map(itemName -> ForgeRegistries.ITEMS.getValue(new ResourceLocation(itemName)))
.collect(Collectors.toSet());
}
}
@@ -0,0 +1,68 @@
package net.fretux.ascend.config;
import net.minecraftforge.common.ForgeConfigSpec;
import org.apache.commons.lang3.tuple.Pair;
public class AscendConfig {
public static class Common {
public final ForgeConfigSpec.IntValue xpPerMobKill;
public final ForgeConfigSpec.IntValue xpPerBlockBreak;
public final ForgeConfigSpec.IntValue xpPerCraft;
public final ForgeConfigSpec.IntValue xpPerSmelt;
public final ForgeConfigSpec.IntValue xpPerTrade;
public final ForgeConfigSpec.IntValue xpPerDamageTaken;
public final ForgeConfigSpec.IntValue xpPerMovement;
public final ForgeConfigSpec.IntValue xpPerPotion;
public final ForgeConfigSpec.IntValue pointsPerLevel;
public final ForgeConfigSpec.IntValue maxAscendLevel;
public final ForgeConfigSpec.IntValue maxAttributePoints;
public final ForgeConfigSpec.DoubleValue xpMultiplier;
public final ForgeConfigSpec.DoubleValue attributeScalingMultiplier;
Common(ForgeConfigSpec.Builder builder) {
builder.push("Ascend Progression");
xpPerMobKill = builder.comment("Ascend XP gained for killing a mob.")
.defineInRange("xpPerMobKill", 5, 0, 1000);
xpPerBlockBreak = builder.comment("Ascend XP gained for breaking a block (ore/stone/log).")
.defineInRange("xpPerBlockBreak", 2, 0, 1000);
xpPerCraft = builder.comment("Ascend XP gained for crafting items.")
.defineInRange("xpPerCraft", 3, 0, 1000);
xpPerSmelt = builder.comment("Ascend XP gained for smelting items.")
.defineInRange("xpPerSmelt", 2, 0, 1000);
xpPerTrade = builder.comment("Ascend XP gained for trading with villagers.")
.defineInRange("xpPerTrade", 5, 0, 1000);
xpPerDamageTaken = builder.comment("Ascend XP gained for taking damage.")
.defineInRange("xpPerDamageTaken", 2, 0, 1000);
xpPerMovement = builder.comment("Ascend XP gained for movement actions.")
.defineInRange("xpPerMovement", 1, 0, 1000);
xpPerPotion = builder.comment("Ascend XP gained for using a potion.")
.defineInRange("xpPerPotion", 3, 0, 1000);
pointsPerLevel = builder.comment("Unspent points gained per Ascend level.")
.defineInRange("pointsPerLevel", 15, 1, 1000);
maxAscendLevel = builder.comment("Maximum Ascend level.")
.defineInRange("maxAscendLevel", 20, 1, 100);
maxAttributePoints = builder.comment("Maximum points in a single attribute.")
.defineInRange("maxAttributePoints", 100, 1, 1000);
xpMultiplier = builder.comment("Global multiplier for all Ascend XP gain.")
.defineInRange("xpMultiplier", 1.0D, 0.0D, 100.0D);
attributeScalingMultiplier = builder.comment("Global multiplier for all attribute-based stat bonuses (damage, mana, speed, etc.).")
.defineInRange("attributeScalingMultiplier", 1.0D, 0.1D, 10.0D);
builder.pop();
}
}
public static final ForgeConfigSpec COMMON_SPEC;
public static final Common COMMON;
static {
final Pair<Common, ForgeConfigSpec> specPair = new ForgeConfigSpec.Builder().configure(Common::new);
COMMON_SPEC = specPair.getRight();
COMMON = specPair.getLeft();
}
}
@@ -1,6 +1,7 @@
package net.fretux.ascend.event;
import net.fretux.ascend.AscendMod;
import net.fretux.ascend.config.AscendConfig;
import net.fretux.ascend.player.PlayerStatsProvider;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ParticleTypes;
@@ -33,7 +34,7 @@ public class ActivityEvents {
public static void onMobKilled(LivingDeathEvent event) {
if (event.getSource().getEntity() instanceof Player player && !player.level().isClientSide) {
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
stats.addAscendXP(5);
stats.addAscendXP((int) (AscendConfig.COMMON.xpPerMobKill.get() * AscendConfig.COMMON.xpMultiplier.get()));
PlayerStatsProvider.sync(player);
});
}
@@ -43,13 +44,11 @@ public class ActivityEvents {
public static void onBlockBreak(BlockEvent.BreakEvent event) {
Player player = event.getPlayer();
if (player == null || player.level().isClientSide) return;
Block block = event.getState().getBlock();
String name = block.getName().getString().toLowerCase();
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
if (name.contains("log") || name.contains("ore") || name.contains("stone")) {
stats.addAscendXP(2);
stats.addAscendXP((int) (AscendConfig.COMMON.xpPerBlockBreak.get() * AscendConfig.COMMON.xpMultiplier.get()));
PlayerStatsProvider.sync(player);
}
});
@@ -77,7 +76,7 @@ public class ActivityEvents {
boolean hasNegativeEffect = player.getActiveEffects().stream()
.anyMatch(e -> e.getEffect().getCategory() == MobEffectCategory.HARMFUL);
if (player.getFoodData().getFoodLevel() <= 6 || hasNegativeEffect) {
stats.addAscendXP(2);
stats.addAscendXP((int) (AscendConfig.COMMON.xpPerDamageTaken.get() * AscendConfig.COMMON.xpMultiplier.get()));
PlayerStatsProvider.sync(player);
}
});
@@ -97,7 +96,7 @@ public class ActivityEvents {
|| Math.abs(player.getDeltaMovement().y) > 0.25
|| player.onClimbable();
if (didAgileThing) {
stats.addAscendXP(1);
stats.addAscendXP((int) (AscendConfig.COMMON.xpPerMovement.get() * AscendConfig.COMMON.xpMultiplier.get()));
PlayerStatsProvider.sync(player);
}
});
@@ -108,7 +107,7 @@ public class ActivityEvents {
Player player = event.getEntity();
if (player.level().isClientSide) return;
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
stats.addAscendXP(3);
stats.addAscendXP((int) (AscendConfig.COMMON.xpPerCraft.get() * AscendConfig.COMMON.xpMultiplier.get()));
PlayerStatsProvider.sync(player);
});
}
@@ -118,7 +117,7 @@ public class ActivityEvents {
Player player = event.getEntity();
if (player.level().isClientSide) return;
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
stats.addAscendXP(2);
stats.addAscendXP((int) (AscendConfig.COMMON.xpPerSmelt.get() * AscendConfig.COMMON.xpMultiplier.get()));
PlayerStatsProvider.sync(player);
});
}
@@ -151,7 +150,7 @@ public class ActivityEvents {
Player player = event.getEntity();
if (player.level().isClientSide) return;
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
stats.addAscendXP(5);
stats.addAscendXP((int) (AscendConfig.COMMON.xpPerTrade.get() * AscendConfig.COMMON.xpMultiplier.get()));
PlayerStatsProvider.sync(player);
});
}
@@ -178,7 +177,7 @@ public class ActivityEvents {
if (player.level().isClientSide) return;
if (event.getItemStack().getItem().getDescriptionId().contains("potion")) {
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
stats.addAscendXP(3);
stats.addAscendXP((int) (AscendConfig.COMMON.xpPerPotion.get() * AscendConfig.COMMON.xpMultiplier.get()));
PlayerStatsProvider.sync(player);
});
}
@@ -26,17 +26,16 @@ public class PlayerEvents {
@SubscribeEvent
public static void onPlayerClone(PlayerEvent.Clone event) {
if (!event.isWasDeath()) return;
event.getOriginal().reviveCaps();
event.getOriginal().getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(oldStats ->
event.getEntity().getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(newStats -> {
newStats.deserializeNBT(oldStats.serializeNBT());
StatEffects.applyAll(event.getEntity());
if (!event.getEntity().level().isClientSide) {
PlayerStatsProvider.sync(event.getEntity());
}
})
);
if (!event.getEntity().level().isClientSide) {
StatEffects.applyAll(event.getEntity());
PlayerStatsProvider.sync(event.getEntity());
}
}
@SubscribeEvent
@@ -46,5 +45,4 @@ public class PlayerEvents {
StatEffects.applyAll(event.getEntity());
}
}
}
}
@@ -1,6 +1,7 @@
package net.fretux.ascend.event;
import net.fretux.ascend.AscendMod;
import net.fretux.ascend.config.AscendConfig;
import net.fretux.ascend.player.PlayerStatsProvider;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.player.Player;
@@ -11,6 +12,7 @@ import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(modid = AscendMod.MODID)
public class WeaponMagicScalingEvents {
static double scale = AscendConfig.COMMON.attributeScalingMultiplier.get();
@SubscribeEvent
public static void onLivingHurt(LivingHurtEvent event) {
@@ -25,7 +27,7 @@ public class WeaponMagicScalingEvents {
if (magicScaling > 0) {
double perPoint = 0.005d;
double maxBonus = 0.40d;
double bonusMult = 1.0d + Math.min(magicScaling * perPoint, maxBonus);
double bonusMult = 1.0d + Math.min(magicScaling * perPoint * scale, maxBonus);
modified = (float) (modified * bonusMult);
}
} else {
@@ -33,12 +35,10 @@ public class WeaponMagicScalingEvents {
if (player.getAttribute(Attributes.ATTACK_SPEED) != null) {
attackSpeed = player.getAttribute(Attributes.ATTACK_SPEED).getValue();
}
String key =
(attackSpeed >= 1.8d) ? "light_scaling" :
(attackSpeed >= 1.2d) ? "medium_scaling" :
"heavy_scaling";
int level = stats.getAttributeLevel(key);
if (level > 0) {
double perPoint =
@@ -46,7 +46,7 @@ public class WeaponMagicScalingEvents {
key.equals("medium_scaling") ? 0.004d :
0.005d;
double maxBonus = 0.40d;
double bonusMult = 1.0d + Math.min(level * perPoint, maxBonus);
double bonusMult = 1.0d + Math.min(level * perPoint * scale, maxBonus);
modified = (float) (modified * bonusMult);
}
}
@@ -2,6 +2,7 @@ package net.fretux.ascend.network;
import net.fretux.ascend.player.PlayerStats;
import net.fretux.ascend.player.PlayerStatsProvider;
import net.fretux.ascend.player.StatEffects;
import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
@@ -12,9 +13,17 @@ import java.util.function.Supplier;
public class ClientboundSyncStatsPacket {
private final CompoundTag data;
public ClientboundSyncStatsPacket(CompoundTag data) { this.data = data; }
public ClientboundSyncStatsPacket(FriendlyByteBuf buf) { this.data = buf.readNbt(); }
public void toBytes(FriendlyByteBuf buf) { buf.writeNbt(data); }
public ClientboundSyncStatsPacket(CompoundTag data) {
this.data = data;
}
public ClientboundSyncStatsPacket(FriendlyByteBuf buf) {
this.data = buf.readNbt();
}
public void toBytes(FriendlyByteBuf buf) {
buf.writeNbt(data);
}
public void handle(Supplier<NetworkEvent.Context> ctx) {
System.out.println("[SERVER] Spend packet handle() called");
@@ -22,7 +31,9 @@ public class ClientboundSyncStatsPacket {
Minecraft mc = Minecraft.getInstance();
var player = mc.player;
if (player != null) {
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
mc.player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
stats.deserializeNBT(data);
StatEffects.applyAll(mc.player);
stats.deserializeNBT(data);
System.out.println("[CLIENT] Before sync: points="
+ player.getCapability(PlayerStatsProvider.PLAYER_STATS)
@@ -31,10 +42,9 @@ public class ClientboundSyncStatsPacket {
System.out.println("[CLIENT] After sync: points="
+ player.getCapability(PlayerStatsProvider.PLAYER_STATS)
.map(PlayerStats::getUnspentPoints).orElse(-1));
});
}
ctx.get().setPacketHandled(true);
});
ctx.get().setPacketHandled(true);
}
}
@@ -1,11 +1,11 @@
package net.fretux.ascend.network;
import net.fretux.ascend.player.PlayerStatsProvider;
import net.fretux.ascend.player.StatEffects;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;
import net.fretux.ascend.player.PlayerStatsProvider;
import net.minecraftforge.network.PacketDistributor;
import net.minecraft.nbt.CompoundTag;
@@ -31,9 +31,6 @@ public class ServerboundSpendPointPacket {
context.enqueueWork(() -> {
ServerPlayer player = context.getSender();
if (player == null) return;
System.out.println("[Ascend] Received spend packet '" + attribute + "' from " + player.getGameProfile().getName());
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
boolean success = stats.spendPoints(attribute);
if (success) {
@@ -1,18 +1,19 @@
package net.fretux.ascend.player;
import net.fretux.ascend.config.AscendConfig;
import net.minecraft.nbt.CompoundTag;
import java.util.HashMap;
import java.util.Map;
public class PlayerStats {
public static final int MAX_ASCEND_LEVEL = 20;
public static final int POINTS_PER_LEVEL = 15;
public static final int POINTS_PER_LEVEL = AscendConfig.COMMON.pointsPerLevel.get();
public static final int MAX_ASCEND_LEVEL = AscendConfig.COMMON.maxAscendLevel.get();
public static final int MAX_ATTRIBUTE_POINTS = AscendConfig.COMMON.maxAttributePoints.get();
private final Map<String, Integer> attributes = new HashMap<>();
private int ascendLevel = 1;
private int ascendXP = 0;
private int unspentPoints = POINTS_PER_LEVEL;
public static final int MAX_ATTRIBUTE_POINTS = 100;
public PlayerStats() {
for (String attr : new String[]{
"strength", "agility", "fortitude", "intelligence",
@@ -1,6 +1,7 @@
package net.fretux.ascend.player;
import net.fretux.ascend.compat.IronsSpellbooksCompat;
import net.fretux.ascend.config.AscendConfig;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
@@ -8,9 +9,6 @@ import net.minecraft.world.entity.ai.attributes.Attributes;
import java.util.UUID;
import static net.fretux.ascend.compat.IronsSpellbooksCompat.applyCharisma;
import static net.fretux.ascend.compat.IronsSpellbooksCompat.applyIntelligence;
public final class StatEffects {
private static final UUID STRENGTH_DAMAGE_UUID =
@@ -27,6 +25,8 @@ public final class StatEffects {
UUID.fromString("e4b5c6d7-8e9f-4012-9abc-334455667788");
private StatEffects() {}
static double scale = AscendConfig.COMMON.attributeScalingMultiplier.get();
public static void applyAll(Player player) {
player.getCapability(PlayerStatsProvider.PLAYER_STATS).ifPresent(stats -> {
@@ -39,20 +39,14 @@ public final class StatEffects {
}
});
}
/**
* Strength:
* - +0.1 attack damage per point (0.05 hearts)
* - +0.03 knockback per point
* - Armor bypass handled in your combat event using strength level.
*/
public static void applyStrength(Player player, int strengthLevel) {
// Damage
AttributeInstance atk = player.getAttribute(Attributes.ATTACK_DAMAGE);
if (atk != null) {
atk.removeModifier(STRENGTH_DAMAGE_UUID);
if (strengthLevel > 0) {
double dmgBonus = strengthLevel * 0.1d;
double dmgBonus = strengthLevel * 0.1d * scale;
atk.addTransientModifier(new AttributeModifier(
STRENGTH_DAMAGE_UUID,
"Ascend Strength damage bonus",
@@ -67,7 +61,7 @@ public final class StatEffects {
if (kb != null) {
kb.removeModifier(STRENGTH_KNOCKBACK_UUID);
if (strengthLevel > 0) {
double kbBonus = strengthLevel * 0.03d;
double kbBonus = strengthLevel * 0.03d * scale;
kb.addTransientModifier(new AttributeModifier(
STRENGTH_KNOCKBACK_UUID,
"Ascend Strength knockback bonus",
@@ -77,19 +71,13 @@ public final class StatEffects {
}
}
}
/**
* Agility:
* - Small movement speed bonus per point.
* - Stealth handled externally (e.g. other mods can read agility).
*/
public static void applyAgility(Player player, int agilityLevel) {
AttributeInstance move = player.getAttribute(Attributes.MOVEMENT_SPEED);
if (move != null) {
move.removeModifier(AGILITY_SPEED_UUID);
if (agilityLevel > 0) {
// Example: +0.002 per point = +0.2 at 100 (about +20% on base 0.1)
double bonus = agilityLevel * 0.002d;
double bonus = agilityLevel * 0.002d * scale;
move.addTransientModifier(new AttributeModifier(
AGILITY_SPEED_UUID,
"Ascend Agility speed bonus",
@@ -99,21 +87,13 @@ public final class StatEffects {
}
}
}
/**
* Fortitude:
* - Max HP up per point.
* - Knockback taken reduced via attribute.
* (Poison/hunger/debuff tuning handled via your other event hooks.)
*/
public static void applyFortitude(Player player, int fortitudeLevel) {
// Max health
AttributeInstance hp = player.getAttribute(Attributes.MAX_HEALTH);
if (hp != null) {
hp.removeModifier(FORTITUDE_HEALTH_UUID);
if (fortitudeLevel > 0) {
// Example: 0.25 HP (1/8 heart) per point = +12.5 hearts at 100
double healthBonus = fortitudeLevel * 0.25d;
double healthBonus = fortitudeLevel * 0.25d * scale;
hp.addTransientModifier(new AttributeModifier(
FORTITUDE_HEALTH_UUID,
"Ascend Fortitude health bonus",
@@ -122,14 +102,11 @@ public final class StatEffects {
));
}
}
// Knockback resistance
AttributeInstance kbRes = player.getAttribute(Attributes.KNOCKBACK_RESISTANCE);
if (kbRes != null) {
kbRes.removeModifier(FORTITUDE_KB_RESIST_UUID);
if (fortitudeLevel > 0) {
// Tiny scaling; cap at 60% here
double res = Math.min(0.006d * fortitudeLevel, 0.60d);
double res = Math.min(0.006d * fortitudeLevel * scale, 0.60d);
kbRes.addTransientModifier(new AttributeModifier(
FORTITUDE_KB_RESIST_UUID,
"Ascend Fortitude knockback resist",
@@ -139,75 +116,40 @@ public final class StatEffects {
}
}
}
/**
* Intelligence:
* - Used for mana & potion/enchant effects via other hooks.
* - Here we only expose a mana bonus helper.
*/
// ===== Willpower external API =====
/**
* Sanity drain multiplier from Willpower.
* Other mods can call this directly.
*
* - Starts at 1.0
* - Reduces up to 50% at high Willpower.
*/
public static float getWillpowerSanityMultiplier(int willpowerLevel) {
if (willpowerLevel <= 0) return 1.0f;
double reduction = Math.min(willpowerLevel * 0.005d, 0.5d); // up to -50%
double reduction = Math.min(willpowerLevel * 0.005d * scale, 0.5d);
return (float) (1.0d - reduction);
}
/**
* Tempo gain/multiplier from Willpower.
* Other mods can plug this into stamina/tempo/sprint systems.
*
* - Starts at 1.0
* - Increases up to +40%.
*/
public static float getWillpowerTempoMultiplier(int willpowerLevel) {
if (willpowerLevel <= 0) return 1.0f;
double bonus = Math.min(willpowerLevel * 0.004d, 0.4d); // up to +40%
double bonus = Math.min(willpowerLevel * 0.004d * scale, 0.4d); // up to +40%
return (float) (1.0d + bonus);
}
/** Convenience: from player capability → sanity multiplier. */
public static float getPlayerSanityMultiplier(Player player) {
public static float getPlayerSanityMultiplier(Player player) {
return player.getCapability(PlayerStatsProvider.PLAYER_STATS)
.map(stats -> getWillpowerSanityMultiplier(stats.getAttributeLevel("willpower")))
.orElse(1.0f);
}
/** Convenience: from player capability → tempo multiplier. */
public static float getPlayerTempoMultiplier(Player player) {
return player.getCapability(PlayerStatsProvider.PLAYER_STATS)
.map(stats -> getWillpowerTempoMultiplier(stats.getAttributeLevel("willpower")))
.orElse(1.0f);
}
// ===== Intelligence external helper (for your mana system) =====
/**
* Example mana bonus: 1 mana per INT, capped by caller if needed.
*/
public static int getIntelligenceManaBonus(int intelligenceLevel) {
return Math.max(0, intelligenceLevel);
}
// ===== Charisma helper (used in trade discount handler) =====
/**
* Charisma → trade discount fraction.
* Returns value in [0, max], e.g. 0.0 = no discount, 0.25 = 25% cheaper.
*/
public static double getCharismaTradeDiscount(int charismaLevel) {
if (charismaLevel <= 0) return 0.0d;
double perPoint = 0.01d;
double max = 0.30d;
return Math.min(charismaLevel * perPoint, max);
return Math.min(charismaLevel * perPoint * scale, max);
}
public static double getWeaponScalingMultiplier(int scalingLevel) {