This commit is contained in:
k
2026-10-26 13:43:27 +01:00
parent 00e9ff003c
commit 98ff11a6c9
27 changed files with 644 additions and 220 deletions
+6
View File
@@ -56,6 +56,12 @@ tasks.register('downloadMcLibs') {
new File(outDir, 'fastutil-8.5.18.jar'))
dl('https://libraries.minecraft.net/com/google/guava/guava/33.6.0-jre/guava-33.6.0-jre.jar',
new File(outDir, 'guava-33.6.0-jre.jar'))
// Requis quand le code touche FriendlyByteBuf (codecs de paquets custom) :
// javac charge ByteBuf pour lire les annotations de type.
dl('https://libraries.minecraft.net/io/netty/netty-buffer/4.2.16.Final/netty-buffer-4.2.16.Final.jar',
new File(outDir, 'netty-buffer.jar'))
dl('https://libraries.minecraft.net/io/netty/netty-common/4.2.16.Final/netty-common-4.2.16.Final.jar',
new File(outDir, 'netty-common.jar'))
dl('https://libraries.minecraft.net/com/google/guava/failureaccess/1.0.3/failureaccess-1.0.3.jar',
new File(outDir, 'failureaccess-1.0.3.jar'))
dl('https://libraries.minecraft.net/com/google/code/gson/gson/2.14.0/gson-2.14.0.jar',
@@ -12,6 +12,7 @@ public class Pala implements ModInitializer {
@Override
public void onInitialize() {
fr.surviepote.pala.core.PalaCore.register();
fr.surviepote.pala.network.PalaNetwork.register();
PalaRegistries.registerAll();
fr.surviepote.pala.events.PalaEvents.registerAll();
LOGGER.info("[SurviePote] Pala initialisé.");
@@ -1,9 +1,19 @@
package fr.surviepote.pala;
import fr.surviepote.pala.client.PalaJobsScreen;
import fr.surviepote.pala.network.JobsDataPayload;
import fr.surviepote.pala.network.JobsRequestPayload;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderingRegistry;
import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
import net.fabricmc.fabric.api.client.screen.v1.Screens;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.gui.screens.inventory.InventoryScreen;
import net.minecraft.client.renderer.block.FluidModel;
import net.minecraft.client.resources.model.sprite.Material;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
public class PalaClient implements ClientModInitializer {
@@ -23,5 +33,25 @@ public class PalaClient implements ClientModInitializer {
fluidModel("sulfuricwater_still", "sulfuricwater_flowing"));
FluidRenderingRegistry.setBlockTransparency(PalaFluids.ANGELIC_WATER_BLOCK, false);
FluidRenderingRegistry.setBlockTransparency(PalaFluids.FAKE_WATER_BLOCK, false);
ClientPlayNetworking.registerGlobalReceiver(JobsDataPayload.TYPE,
(payload, context) -> context.client().execute(
() -> context.client().gui.setScreen(new PalaJobsScreen(payload.payload()))));
// Bouton "Metiers" a gauche de l'inventaire (progression des 4 metiers).
ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> {
if (!(screen instanceof InventoryScreen)) {
return;
}
int left = (screen.width - 176) / 2;
int top = (screen.height - 166) / 2;
Button jobs = Button.builder(
Component.literal("§6M"),
button -> ClientPlayNetworking.send(JobsRequestPayload.INSTANCE))
.bounds(left - 24, top + 6, 20, 20)
.tooltip(Tooltip.create(Component.literal("§6Métiers §7— progression")))
.build();
Screens.getWidgets(screen).add(jobs);
});
}
}
@@ -17,6 +17,7 @@ import net.minecraft.sounds.SoundSource;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.portal.TeleportTransition;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.item.ItemStack;
import fr.surviepote.pala.core.PalaConfig;
import fr.surviepote.pala.core.PalaJobs;
@@ -25,8 +26,8 @@ import fr.surviepote.pala.core.PalaStorage;
// Commandes Pala : /end /overworld (permission niveau 4, legacy), /nether (gate metier
// mineur niv.3, M0), /pala reload|info|rank (admin), /jobs (metiers), /craft (table de
// craft portable, wiki commandes), /spawn (tp spawn overworld, delai 3 s sans grade, wiki).
// Voir CDC §3.8.
// craft portable, wiki commandes), /furnace (cuisson directe du stack en main), /spawn (tp spawn overworld,
// delai 3 s sans grade, wiki). Voir CDC §3.8.
// Origine : CommandEnd/Nether/Overworld + procedures *CommandExecuted qui
// teleportaient le joueur au spawn de la dimension visee. Ici via
// TeleportTransition (API 26.x), sans le Teleporter custom du legacy.
@@ -49,6 +50,35 @@ public final class PalaCommands {
private record TpaTask(java.util.UUID target, long at) {
}
// Zone / home serialises "dimension:x:y:z". Le separateur ':' est aussi celui de
// l'identifiant de dimension (minecraft:overworld) : on decoupe donc a l'envers
// (3 derniers segments = coordonnees) au lieu d'un split(":") naif qui produisait
// 5 morceaux et faisait echouer /home, /mine, /farm et /f home.
private record ZonePos(String dimension, int x, int y, int z) {
}
private static ZonePos parseZone(String zone) {
if (zone == null || zone.isEmpty()) {
return null;
}
String[] parts = zone.split(":", 3);
if (parts.length != 3) {
return null;
}
String[] coords = parts[2].split(":");
if (coords.length != 3) {
return null;
}
try {
return new ZonePos(parts[0] + ":" + parts[1],
Integer.parseInt(coords[0]),
Integer.parseInt(coords[1]),
Integer.parseInt(coords[2]));
} catch (NumberFormatException e) {
return null;
}
}
private PalaCommands() {
}
@@ -90,6 +120,8 @@ public final class PalaCommands {
.executes(command -> jobs(command.getSource())));
dispatcher.register(Commands.literal("craft")
.executes(command -> craft(command.getSource())));
dispatcher.register(Commands.literal("furnace")
.executes(command -> furnace(command.getSource())));
dispatcher.register(Commands.literal("spawn")
.executes(command -> spawn(command.getSource())));
dispatcher.register(Commands.literal("minage")
@@ -827,15 +859,15 @@ public final class PalaCommands {
source.sendFailure(Component.literal("§cAucun home defini (/f sethome)."));
return 0;
}
String[] parts = faction.home.split(":");
if (parts.length != 4) {
ZonePos home = parseZone(faction.home);
if (home == null) {
source.sendFailure(Component.literal("§cHome corrompu."));
return 0;
}
net.minecraft.server.MinecraftServer server = source.getServer();
net.minecraft.server.level.ServerLevel target = null;
for (net.minecraft.server.level.ServerLevel level : server.getAllLevels()) {
if (level.dimension().identifier().toString().equals(parts[0])) {
if (level.dimension().identifier().toString().equals(home.dimension())) {
target = level;
break;
}
@@ -844,19 +876,8 @@ public final class PalaCommands {
source.sendFailure(Component.literal("§cDimension inconnue."));
return 0;
}
int x;
int y;
int z;
try {
x = Integer.parseInt(parts[1]);
y = Integer.parseInt(parts[2]);
z = Integer.parseInt(parts[3]);
} catch (NumberFormatException e) {
source.sendFailure(Component.literal("§cHome corrompu."));
return 0;
}
teleportPlayer(player, target,
new net.minecraft.core.BlockPos(x, y, z));
new net.minecraft.core.BlockPos(home.x(), home.y(), home.z()));
return Command.SINGLE_SUCCESS;
} catch (com.mojang.brigadier.exceptions.CommandSyntaxException e) {
return 0;
@@ -1114,15 +1135,15 @@ public final class PalaCommands {
"§cZone " + label + " non configuree (admin : config pala.json)."));
return 0;
}
String[] parts = zone.split(":");
if (parts.length != 4) {
ZonePos parsed = parseZone(zone);
if (parsed == null) {
source.sendFailure(Component.literal("§cZone " + label + " corrompue."));
return 0;
}
net.minecraft.server.MinecraftServer server = source.getServer();
net.minecraft.server.level.ServerLevel target = null;
for (net.minecraft.server.level.ServerLevel level : server.getAllLevels()) {
if (level.dimension().identifier().toString().equals(parts[0])) {
if (level.dimension().identifier().toString().equals(parsed.dimension())) {
target = level;
break;
}
@@ -1131,19 +1152,8 @@ public final class PalaCommands {
source.sendFailure(Component.literal("§cDimension inconnue."));
return 0;
}
int x;
int y;
int z;
try {
x = Integer.parseInt(parts[1]);
y = Integer.parseInt(parts[2]);
z = Integer.parseInt(parts[3]);
} catch (NumberFormatException e) {
source.sendFailure(Component.literal("§cZone " + label + " corrompue."));
return 0;
}
teleportPlayer(player, target,
new net.minecraft.core.BlockPos(x, y, z));
new net.minecraft.core.BlockPos(parsed.x(), parsed.y(), parsed.z()));
return Command.SINGLE_SUCCESS;
} catch (com.mojang.brigadier.exceptions.CommandSyntaxException e) {
return 0;
@@ -1394,6 +1404,60 @@ public final class PalaCommands {
}
}
// /furnace : cuit instantanement l'item (ou le stack) tenu en main, sans GUI.
// Vanilla XP de la recette de cuisson + XP metier via PalaJobs.onSmeltTake (meme
// comportement qu'une prise de resultat dans un four).
private static int furnace(CommandSourceStack source) {
try {
ServerPlayer player = source.getPlayerOrException();
net.minecraft.world.InteractionHand hand = net.minecraft.world.InteractionHand.MAIN_HAND;
ItemStack held = player.getMainHandItem();
if (held.isEmpty()) {
hand = net.minecraft.world.InteractionHand.OFF_HAND;
held = player.getOffhandItem();
}
if (held.isEmpty()) {
source.sendFailure(Component.literal("§cAucun item en main."));
return 0;
}
int count = held.getCount();
net.minecraft.world.item.crafting.SingleRecipeInput input =
new net.minecraft.world.item.crafting.SingleRecipeInput(held.copyWithCount(1));
java.util.Optional<net.minecraft.world.item.crafting.RecipeHolder<
net.minecraft.world.item.crafting.SmeltingRecipe>> found =
source.getServer().getRecipeManager().getRecipeFor(
net.minecraft.world.item.crafting.RecipeType.SMELTING,
input, player.level());
if (found.isEmpty()) {
source.sendFailure(Component.literal(
"§cCet item ne peut pas etre cuit : §e" + held.getHoverName().getString()));
return 0;
}
net.minecraft.world.item.crafting.SmeltingRecipe recipe = found.get().value();
ItemStack result = recipe.assemble(input);
ItemStack output = result.copyWithCount(result.getCount() * count);
float xp = recipe.experience() * count;
player.setItemInHand(hand, ItemStack.EMPTY);
player.getInventory().placeItemBackInInventory(output,
net.minecraft.util.Prediction.SERVER_ONLY);
int points = net.minecraft.util.Mth.floor(xp);
if (player.getRandom().nextFloat() < xp - points) {
points++;
}
if (points > 0) {
player.giveExperiencePoints(points);
}
fr.surviepote.pala.core.PalaJobs.onSmeltTake(player, output);
final int finalPoints = points;
source.sendSuccess(() -> Component.literal("§6[Pala] §7Cuit : §e"
+ output.getCount() + " x " + output.getHoverName().getString()
+ (finalPoints > 0 ? " §7(+§a" + finalPoints + " XP§7)" : "")), false);
return Command.SINGLE_SUCCESS;
} catch (com.mojang.brigadier.exceptions.CommandSyntaxException e) {
return 0;
}
}
// /spawn : tp au spawn overworld avec delai wiki 3 s (60 ticks). Pas d'annulation sur
// mouvement cote wiki (teleportation simple apres delai).
private static int spawn(CommandSourceStack source) {
@@ -204,10 +204,6 @@ public final class PalaTabs {
output.accept(PalaBlocks.CAULDRON_TANK_SUPPORT);
output.accept(PalaBlocks.MYSTICAL_BOOKSHELF);
output.accept(PalaBlocks.SLEEPING_BAG);
output.accept(PalaBlocks.FIKUTSI_STAGE0);
output.accept(PalaBlocks.FIKUTSI_STAGE1);
output.accept(PalaBlocks.FIKUTSI_STAGE2);
output.accept(PalaBlocks.FIKUTSI_STAGE3);
output.accept(PalaBlocks.FINDIUM_ORE);
output.accept(PalaBlocks.FINDIUM_ORE_BLOCK);
output.accept(PalaBlocks.PALAFORGE);
@@ -216,20 +212,6 @@ public final class PalaTabs {
output.accept(PalaBlocks.GRINDER_FRAME);
output.accept(PalaBlocks.KISAKAY_ORE);
output.accept(PalaBlocks.KISAKAY_ORE_BLOCK);
output.accept(PalaBlocks.KIWANO_STAGE0);
output.accept(PalaBlocks.KIWANO_STAGE1);
output.accept(PalaBlocks.KIWANO_STAGE2);
output.accept(PalaBlocks.KIWANO_STAGE3);
output.accept(PalaBlocks.KIWANO_STAGE4);
output.accept(PalaBlocks.OKAMI_STAGE0);
output.accept(PalaBlocks.OKAMI_STAGE1);
output.accept(PalaBlocks.OKAMI_STAGE2);
output.accept(PalaBlocks.OKAMI_STAGE3);
output.accept(PalaBlocks.ORANGE_BLUE_STAGE0);
output.accept(PalaBlocks.ORANGE_BLUE_STAGE1);
output.accept(PalaBlocks.ORANGE_BLUE_STAGE2);
output.accept(PalaBlocks.ORANGE_BLUE_STAGE3);
output.accept(PalaBlocks.ORANGE_BLUE_STAGE4);
output.accept(PalaBlocks.PALADIUM_CRUSHER);
output.accept(PalaBlocks.PALADIUM_FURNACE);
output.accept(PalaBlocks.PALADIUM_ORE);
@@ -0,0 +1,117 @@
package fr.surviepote.pala.client;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
// Ecran de progression des 4 metiers, ouvert par le bouton de l'inventaire.
// Donnees recues du serveur via JobsDataPayload.
public class PalaJobsScreen extends Screen {
private record JobEntry(String key, int level, long xp, long previous, long next) {
}
private final List<JobEntry> jobs = new ArrayList<>();
private int panelX;
private int panelY;
private int panelWidth = 280;
private int panelHeight;
public PalaJobsScreen(String payload) {
super(Component.translatable("screen.pala.jobs"));
if (payload != null) {
for (String line : payload.split("\n")) {
String[] parts = line.split("\\|");
if (parts.length != 5) {
continue;
}
try {
jobs.add(new JobEntry(parts[0], Integer.parseInt(parts[1]),
Long.parseLong(parts[2]), Long.parseLong(parts[3]),
Long.parseLong(parts[4])));
} catch (NumberFormatException ignored) {
// Ligne invalide : ignoree (payload tolerant).
}
}
}
}
@Override
protected void init() {
panelHeight = 44 + jobs.size() * 38 + 30;
panelX = (width - panelWidth) / 2;
panelY = (height - panelHeight) / 2;
addRenderableWidget(Button.builder(Component.literal("Fermer"), b -> onClose())
.bounds(panelX + panelWidth / 2 - 60, panelY + panelHeight - 26, 120, 20)
.build());
}
@Override
public void extractBackground(GuiGraphicsExtractor graphics, int mouseX, int mouseY,
float partialTick) {
super.extractBackground(graphics, mouseX, mouseY, partialTick);
graphics.fill(0, 0, width, height, 0x99000000);
graphics.fill(panelX, panelY, panelX + panelWidth, panelY + panelHeight, 0xF0181818);
graphics.fill(panelX, panelY, panelX + panelWidth, panelY + 2, 0xFFFFAA00);
}
@Override
public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY,
float partialTick) {
super.extractRenderState(graphics, mouseX, mouseY, partialTick);
graphics.centeredText(font, Component.literal("§6Métiers"), panelX + panelWidth / 2,
panelY + 12, 0xFFFFAA00);
int y = panelY + 32;
if (jobs.isEmpty()) {
graphics.text(font, Component.literal("§7Aucune donnée de métier reçue."),
panelX + 16, y, 0xFFAAAAAA);
}
for (JobEntry job : jobs) {
graphics.text(font, Component.literal(displayName(job.key()) + " §7— niveau §a"
+ job.level()), panelX + 16, y, 0xFFFFFFFF);
String xpLine;
if (job.next() <= 0) {
xpLine = "§7Niveau maximum";
} else {
xpLine = "§7" + job.xp() + " / " + job.next() + " XP";
}
graphics.text(font, Component.literal(xpLine), panelX + 16, y + 12, 0xFFAAAAAA);
drawBar(graphics, panelX + 150, y + 3, 112, 8, job);
y += 38;
}
}
private void drawBar(GuiGraphicsExtractor graphics, int x, int y, int w, int h,
JobEntry job) {
graphics.fill(x, y, x + w, y + h, 0xFF333333);
long next = job.next();
if (next <= 0) {
graphics.fill(x, y, x + w, y + h, 0xFF55FF55);
return;
}
long previous = job.previous();
long span = Math.max(1L, next - previous);
long done = Math.max(0L, job.xp() - previous);
int filled = (int) Math.min(w, (done * w) / span);
graphics.fill(x, y, x + filled, y + h, 0xFF55FF55);
}
private static String displayName(String key) {
return switch (key) {
case "mineur" -> "Mineur";
case "fermier" -> "Fermier";
case "chasseur" -> "Chasseur";
case "alchimiste" -> "Alchimiste";
default -> key;
};
}
@Override
public boolean isPauseScreen() {
return false;
}
}
@@ -30,48 +30,54 @@ public final class PalaConfig {
// Cles = ids de registres (vanilla ou pala:). Map.ofEntries : Map.of limite a 10 paires.
public Map<String, Double> minerXp = new HashMap<>(Map.ofEntries(
Map.entry("minecraft:stone", 0.5),
Map.entry("minecraft:cobblestone", 0.5), Map.entry("minecraft:deepslate", 0.5),
Map.entry("minecraft:tuff", 0.5),
Map.entry("minecraft:diorite", 3.0), Map.entry("minecraft:andesite", 3.0),
Map.entry("minecraft:coal_ore", 4.0), Map.entry("minecraft:deepslate_coal_ore", 4.0),
Map.entry("minecraft:quartz_ore", 6.0), Map.entry("minecraft:obsidian", 6.0),
Map.entry("minecraft:lapis_ore", 15.0), Map.entry("minecraft:redstone_ore", 15.0),
Map.entry("minecraft:nether_quartz_ore", 6.0), Map.entry("minecraft:obsidian", 6.0),
Map.entry("minecraft:lapis_ore", 15.0), Map.entry("minecraft:deepslate_lapis_ore", 15.0),
Map.entry("minecraft:redstone_ore", 15.0),
Map.entry("minecraft:deepslate_redstone_ore", 15.0),
Map.entry("minecraft:emerald_ore", 50.0),
Map.entry("minecraft:deepslate_emerald_ore", 50.0),
Map.entry("minecraft:iron_ore", 2.0), Map.entry("minecraft:deepslate_iron_ore", 2.0),
Map.entry("minecraft:gold_ore", 4.0), Map.entry("minecraft:deepslate_gold_ore", 4.0),
Map.entry("minecraft:nether_gold_ore", 4.0),
Map.entry("minecraft:diamond_ore", 8.0), Map.entry("minecraft:deepslate_diamond_ore", 8.0),
Map.entry("pala:amethyst_ore", 12.0), Map.entry("pala:titane_ore", 16.0),
Map.entry("pala:paladium_ore", 20.0),
Map.entry("pala:paladium_green_ore", 20.0), Map.entry("pala:trixium_ore", 10.0),
Map.entry("pala:findium_ore", 10.0)));
// M-mineur-xp (wiki Evolution mineur, VERIFIE) : niveaux requis par bloc.
// Niv.1 : roche/diorite/charbon/quartz/obsidienne/lapis/redstone/emeraude ;
// niv.5 : fer/or/diamant/amethyste/titane (+ minerais pala par interpretation
// "Particules paladium 20@5", ECART documente).
// M-mineur-xp : niveaux requis par bloc. Les blocs de base sont accessibles des le
// niveau 0 (sinon un nouveau joueur ne peut jamais gagner son premier XP mineur) ;
// les minerais de fer/or/diamant/pala restent verrouilles au niveau 5 (wiki).
public Map<String, Integer> minerLevels = new HashMap<>(Map.ofEntries(
Map.entry("minecraft:stone", 1),
Map.entry("minecraft:diorite", 1), Map.entry("minecraft:andesite", 1),
Map.entry("minecraft:coal_ore", 1), Map.entry("minecraft:deepslate_coal_ore", 1),
Map.entry("minecraft:quartz_ore", 1), Map.entry("minecraft:obsidian", 1),
Map.entry("minecraft:lapis_ore", 1), Map.entry("minecraft:redstone_ore", 1),
Map.entry("minecraft:emerald_ore", 1), Map.entry("minecraft:deepslate_emerald_ore", 1),
Map.entry("minecraft:stone", 0),
Map.entry("minecraft:cobblestone", 0), Map.entry("minecraft:deepslate", 0),
Map.entry("minecraft:tuff", 0),
Map.entry("minecraft:diorite", 0), Map.entry("minecraft:andesite", 0),
Map.entry("minecraft:coal_ore", 0), Map.entry("minecraft:deepslate_coal_ore", 0),
Map.entry("minecraft:nether_quartz_ore", 0), Map.entry("minecraft:obsidian", 0),
Map.entry("minecraft:lapis_ore", 0), Map.entry("minecraft:deepslate_lapis_ore", 0),
Map.entry("minecraft:redstone_ore", 0),
Map.entry("minecraft:deepslate_redstone_ore", 0),
Map.entry("minecraft:emerald_ore", 0), Map.entry("minecraft:deepslate_emerald_ore", 0),
Map.entry("minecraft:iron_ore", 5), Map.entry("minecraft:deepslate_iron_ore", 5),
Map.entry("minecraft:gold_ore", 5), Map.entry("minecraft:deepslate_gold_ore", 5),
Map.entry("minecraft:nether_gold_ore", 5),
Map.entry("minecraft:diamond_ore", 5), Map.entry("minecraft:deepslate_diamond_ore", 5),
Map.entry("pala:amethyst_ore", 5), Map.entry("pala:titane_ore", 5),
Map.entry("pala:paladium_ore", 5), Map.entry("pala:paladium_green_ore", 5),
Map.entry("pala:trixium_ore", 5), Map.entry("pala:findium_ore", 5)));
// Courbe XP (M-xpcurve) : seuils wiki VERIFIES 2-20, formule au-dela. La table mineur
// s'applique aux 4 metiers (seule table publiee ; ECART documente).
// Courbe XP : formule par defaut xp(n) = xpBase * n^xpExponent. Les anciens seuils
// wiki (22 123 XP pour le niveau 2) rendaient les premiers niveaux inatteignables en
// jeu normal ; ils sont remplaces par une courbe progressive. Surcharge possible via
// levelThresholds dans pala.json (niveau -> XP total cumule).
public int configVersion = 3;
public double xpBase = 100.0;
public double xpExponent = 1.5;
public Map<Integer, Long> levelThresholds = new HashMap<>(Map.ofEntries(
Map.entry(2, 22123L), Map.entry(3, 40390L), Map.entry(4, 73751L),
Map.entry(5, 118886L), Map.entry(6, 176611L), Map.entry(7, 247616L),
Map.entry(8, 332507L), Map.entry(9, 431826L), Map.entry(10, 546062L),
Map.entry(11, 675669L), Map.entry(12, 821062L), Map.entry(13, 982632L),
Map.entry(14, 1160743L), Map.entry(15, 1355741L), Map.entry(16, 1567953L),
Map.entry(17, 1797690L), Map.entry(18, 2045248L), Map.entry(19, 2310912L),
Map.entry(20, 2594953L)));
public Map<Integer, Long> levelThresholds = new HashMap<>();
// Niveau mineur requis pour /nether (wiki recompenses mineur niv.3, VERIFIE).
public int netherMinerLevel = 3;
@@ -118,6 +124,8 @@ public final class PalaConfig {
// M5a (wiki VERIFIE) : chasseur = entite -> [xp, niveau requis].
// Passifs 14@0, poulpe 10@0 (page : "des le niveau 0/1"), creeper 40, wither 1000.
// Moddees (chevre 20@1, escargot 25@2...) : mobs absents du portage => non listees (M8/donjons).
// SurviePote : hostiles vanilla ajoutes (zombie/squelette/araignee/etc.) car la table
// wiki ne couvrait que les passifs + creeper, donc tuer des monstres ne donnait rien.
public Map<String, double[]> hunterKills = new HashMap<>(Map.ofEntries(
Map.entry("minecraft:cow", new double[]{14, 0}),
Map.entry("minecraft:sheep", new double[]{14, 0}),
@@ -125,10 +133,60 @@ public final class PalaConfig {
Map.entry("minecraft:chicken", new double[]{14, 0}),
Map.entry("minecraft:rabbit", new double[]{14, 0}),
Map.entry("minecraft:horse", new double[]{14, 0}),
Map.entry("minecraft:mooshroom", new double[]{14, 0}),
Map.entry("minecraft:wolf", new double[]{14, 0}),
Map.entry("minecraft:fox", new double[]{14, 0}),
Map.entry("minecraft:cat", new double[]{10, 0}),
Map.entry("minecraft:llama", new double[]{14, 0}),
Map.entry("minecraft:trader_llama", new double[]{14, 0}),
Map.entry("minecraft:camel", new double[]{14, 0}),
Map.entry("minecraft:squid", new double[]{10, 0}),
Map.entry("minecraft:glow_squid", new double[]{10, 0}),
Map.entry("minecraft:bat", new double[]{5, 0}),
Map.entry("minecraft:bee", new double[]{10, 0}),
Map.entry("minecraft:frog", new double[]{10, 0}),
Map.entry("minecraft:axolotl", new double[]{15, 0}),
Map.entry("minecraft:armadillo", new double[]{15, 0}),
Map.entry("minecraft:sniffer", new double[]{30, 0}),
Map.entry("minecraft:allay", new double[]{20, 0}),
Map.entry("minecraft:polar_bear", new double[]{25, 0}),
Map.entry("minecraft:creeper", new double[]{40, 0}),
Map.entry("minecraft:zombie", new double[]{15, 0}),
Map.entry("minecraft:husk", new double[]{15, 0}),
Map.entry("minecraft:drowned", new double[]{15, 0}),
Map.entry("minecraft:skeleton", new double[]{15, 0}),
Map.entry("minecraft:stray", new double[]{15, 0}),
Map.entry("minecraft:bogged", new double[]{15, 0}),
Map.entry("minecraft:spider", new double[]{12, 0}),
Map.entry("minecraft:cave_spider", new double[]{15, 0}),
Map.entry("minecraft:silverfish", new double[]{5, 0}),
Map.entry("minecraft:endermite", new double[]{8, 0}),
Map.entry("minecraft:slime", new double[]{10, 0}),
Map.entry("minecraft:magma_cube", new double[]{20, 0}),
Map.entry("minecraft:enderman", new double[]{30, 0}),
Map.entry("minecraft:witch", new double[]{25, 0}),
Map.entry("minecraft:blaze", new double[]{30, 0}),
Map.entry("minecraft:ghast", new double[]{50, 0}),
Map.entry("minecraft:guardian", new double[]{20, 0}),
Map.entry("minecraft:elder_guardian", new double[]{100, 0}),
Map.entry("minecraft:shulker", new double[]{40, 0}),
Map.entry("minecraft:phantom", new double[]{20, 0}),
Map.entry("minecraft:pillager", new double[]{20, 0}),
Map.entry("minecraft:vindicator", new double[]{25, 0}),
Map.entry("minecraft:evoker", new double[]{50, 0}),
Map.entry("minecraft:illusioner", new double[]{50, 0}),
Map.entry("minecraft:ravager", new double[]{80, 0}),
Map.entry("minecraft:vex", new double[]{20, 0}),
Map.entry("minecraft:hoglin", new double[]{30, 0}),
Map.entry("minecraft:zoglin", new double[]{30, 0}),
Map.entry("minecraft:piglin", new double[]{20, 0}),
Map.entry("minecraft:piglin_brute", new double[]{30, 0}),
Map.entry("minecraft:zombified_piglin", new double[]{15, 0}),
Map.entry("minecraft:wither_skeleton", new double[]{40, 0}),
Map.entry("minecraft:breeze", new double[]{30, 0}),
Map.entry("minecraft:warden", new double[]{200, 0}),
Map.entry("minecraft:wither", new double[]{1000, 0}),
Map.entry("minecraft:ender_dragon", new double[]{5000, 0}),
// M-hunter-3 (wiki evolution chasseur, VERIFIE) : passifs vanilla
// supplementaires (creatures moddees escargot/crabe/serpent/meduse/
// elephant SKIP M8, absentes ; peche "tres variable" + QTE SKIP).
@@ -156,10 +214,14 @@ public final class PalaConfig {
"pala:egg_plant_fruit", new double[]{3, 8},
"pala:okami_fruit", new double[]{4.5, 12},
"pala:kiwano", new double[]{6, 16}));
// M-alchi-1 (wiki Evolution alchimiste VERIFIE) : XP au craft = item -> [xp, niveau].
// Fiole 0.2@1. Extracteur/lightning/glueballs/portails : items/crafts en M-alchi-2+.
// M-alchi-1 : XP au craft = item -> [xp, niveau]. La fiole est accessible des le
// niveau 0 (source d'entree) ; valeur remontee de 0.2 (wiki) a 1.0 pour que le
// craft de fioles reste une alternative viable au brassage.
public Map<String, double[]> alchemistCraft = new HashMap<>(Map.of(
"pala:flask", new double[]{0.2, 1}));
"pala:flask", new double[]{1.0, 0}));
// SurviePote : XP alchimiste par potion reellement brassee et recuperee du
// chaudron (mixin BrewingStand). Source principale pour demarrer le metier.
public double alchemistBrewXp = 10.0;
// M4a : recettes machines data-driven (CDC §3.4). Cles = ids de registres.
// Defauts = legacy+wiki VERIFIES (M3). Rechargeables via /pala reload.
public Map<String, String> crusherResults = new HashMap<>(Map.of(
@@ -224,6 +286,7 @@ public final class PalaConfig {
try (Reader reader = Files.newBufferedReader(path)) {
PalaConfig loaded = GSON.fromJson(reader, PalaConfig.class);
if (loaded != null) {
boolean migrateBalance = loaded.configVersion < 3;
if (loaded.minerXp != null && !loaded.minerXp.isEmpty()) {
instance.minerXp = loaded.minerXp;
}
@@ -236,6 +299,12 @@ public final class PalaConfig {
if (loaded.xpExponent > 0) {
instance.xpExponent = loaded.xpExponent;
}
if (loaded.levelThresholds != null) {
instance.levelThresholds = loaded.levelThresholds;
}
if (loaded.alchemistBrewXp > 0) {
instance.alchemistBrewXp = loaded.alchemistBrewXp;
}
if (loaded.netherMinerLevel >= 0) {
instance.netherMinerLevel = loaded.netherMinerLevel;
}
@@ -256,6 +325,16 @@ public final class PalaConfig {
instance.farmerHarvest = loaded.farmerHarvest;
mergedDefaultsApplied |= mergeDefaults(instance.farmerHarvest, __def);
}
if (loaded.crusherXp != null && !loaded.crusherXp.isEmpty()) {
java.util.Map __def = new java.util.HashMap<>(instance.crusherXp);
instance.crusherXp = loaded.crusherXp;
mergedDefaultsApplied |= mergeDefaults(instance.crusherXp, __def);
}
if (loaded.alchemistCraft != null && !loaded.alchemistCraft.isEmpty()) {
java.util.Map __def = new java.util.HashMap<>(instance.alchemistCraft);
instance.alchemistCraft = loaded.alchemistCraft;
mergedDefaultsApplied |= mergeDefaults(instance.alchemistCraft, __def);
}
if (loaded.crusherResults != null && !loaded.crusherResults.isEmpty()) {
java.util.Map __def = new java.util.HashMap<>(instance.crusherResults);
instance.crusherResults = loaded.crusherResults;
@@ -275,15 +354,38 @@ public final class PalaConfig {
if (loaded.furnaceFuels != null && !loaded.furnaceFuels.isEmpty()) {
instance.furnaceFuels = loaded.furnaceFuels;
}
// Migration v3 : les anciennes tables de balance (seuils wiki 22k+,
// blocs mineur verrouilles niv.1, alchimiste niv.1) rendaient les
// metiers impossibles a demarrer. On remet les defauts code une fois.
if (migrateBalance) {
PalaConfig defaults = new PalaConfig();
instance.minerXp = defaults.minerXp;
instance.minerLevels = defaults.minerLevels;
instance.hunterKills = defaults.hunterKills;
instance.farmerHarvest = defaults.farmerHarvest;
instance.crusherXp = defaults.crusherXp;
instance.alchemistCraft = defaults.alchemistCraft;
instance.alchemistBrewXp = defaults.alchemistBrewXp;
instance.levelThresholds = defaults.levelThresholds;
instance.xpBase = defaults.xpBase;
instance.xpExponent = defaults.xpExponent;
instance.configVersion = 3;
mergedDefaultsApplied = true;
Pala.LOGGER.info(
"[SurviePote] Config metiers migree v2 -> v3 (courbe XP et tables rebalancees).");
} else {
instance.configVersion = loaded.configVersion;
}
// M5a : un vieux pala.json (M0) n'a pas les nouvelles cles : les defauts
// code sont en memoire (Gson utilise le constructeur no-args, les champs
// absents gardent leurs valeurs par defaut, jamais null), on re-sauvegarde
// pour les materialiser si une cle attendue manque. Une table explicitement
// videe (cle presente, tableau vide) est respectee telle quelle.
java.util.Set<String> expected = java.util.Set.of("minerXp", "minerLevels",
java.util.Set<String> expected = java.util.Set.of("configVersion",
"minerXp", "minerLevels",
"xpBase", "xpExponent", "levelThresholds", "netherMinerLevel", "stickCooldowns",
"rankNodes", "hunterKills", "farmerHarvest", "crusherXp",
"alchemistCraft",
"alchemistCraft", "alchemistBrewXp",
"crusherResults", "crusherFuels", "crusherInfiniteFuels",
"furnaceSmelts", "furnaceFuels",
"serverName", "wikiUrl", "discordUrl", "shopUrl",
@@ -326,7 +428,7 @@ public final class PalaConfig {
}
}
/** Cout XP total pour atteindre le niveau n (n >= 1) : table wiki 2-20, formule au-dela. */
/** Cout XP total pour atteindre le niveau n (n >= 1), surchargeable par levelThresholds. */
public long xpForLevel(int level) {
Long exact = levelThresholds.get(level);
if (exact != null) {
@@ -1,6 +1,9 @@
package fr.surviepote.pala.core;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
@@ -17,6 +20,10 @@ public final class PalaJobs {
public static final List<String> JOBS = List.of("mineur", "fermier", "chasseur", "alchimiste");
// Action bar : dernier affichage par joueur (throttle 1 s pour eviter le spam
// pendant le minage).
private static final Map<UUID, Long> LAST_HUD = new HashMap<>();
private PalaJobs() {
}
@@ -46,9 +53,32 @@ public final class PalaJobs {
PalaRewards.grant(player, job, data.level);
}
PalaStorage.get().markDirty();
sendHud(player, job, data);
return data.level;
}
// Affiche la progression courante dans l'action bar (1 fois par seconde max).
// Permet de voir que l'XP monte sans rouvrir l'ecran Metiers.
private static void sendHud(ServerPlayer player, String job, PalaStorage.JobData data) {
long now = System.currentTimeMillis();
Long last = LAST_HUD.get(player.getUUID());
if (last != null && now - last < 1000L) {
return;
}
LAST_HUD.put(player.getUUID(), now);
PalaConfig config = PalaConfig.get();
long next = data.level >= 100 ? 0L : config.xpForLevel(data.level + 1);
String text;
if (next <= 0L) {
text = "§6[Metier] §e" + job + " §7niveau maximum";
} else {
text = "§6[Metier] §e" + job + " §7: §e"
+ String.format(java.util.Locale.ROOT, "%.1f", data.xp)
+ " §7/ §e" + next + " XP §7(niv. " + data.level + ")";
}
player.sendOverlayMessage(Component.literal(text));
}
/** Hook casse-bloc : XP mineur selon la table config + niveau requis (wiki). */
public static void onBlockBreak(ServerPlayer player, ServerLevel level, BlockPos pos, BlockState state) {
Identifier id = level.registryAccess()
@@ -160,7 +160,16 @@ public final class PalaJobEvents {
}
private static void afterDeath(LivingEntity entity, DamageSource source) {
Entity killer = source.getEntity();
// getKillCredit() gere deja les projectiles et les loups apprivoises (retourne
// le proprietaire). Fallback sur la source directe pour les cas exotiques.
Entity killer = entity.getKillCredit();
if (killer == null) {
killer = source.getEntity();
}
if (killer instanceof net.minecraft.world.entity.projectile.Projectile projectile
&& projectile.getOwner() instanceof ServerPlayer owner) {
killer = owner;
}
if (!(killer instanceof ServerPlayer player)) {
return;
}
@@ -0,0 +1,40 @@
package fr.surviepote.pala.mixin;
import net.minecraft.core.component.DataComponents;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.CustomData;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import fr.surviepote.pala.core.PalaJobs;
// SurviePote : XP alchimiste quand un joueur recupere une potion fraichement brassee.
// Le marqueur "pala:brewed_xp" est pose par PalaBrewXpMixin a la fin du brassage puis
// retire ici, donc une potion deja prise (ou sortie par un hopper) ne rapporte plus rien.
@Mixin(targets = "net.minecraft.world.inventory.BrewingStandMenu$PotionSlot")
public class PalaBrewTakeXpMixin {
@Inject(method = "onTake", at = @At("TAIL"))
private void pala$brewedPotionXp(Player player, ItemStack stack, CallbackInfo ci) {
if (!(player instanceof ServerPlayer sp) || stack.isEmpty()) {
return;
}
CustomData data = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
if (!data.copyTag().contains("pala:brewed_xp")) {
return;
}
double xp = data.copyTag().getDoubleOr("pala:brewed_xp", 0.0);
CustomData.update(DataComponents.CUSTOM_DATA, stack,
tag -> tag.remove("pala:brewed_xp"));
if (stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).isEmpty()) {
stack.remove(DataComponents.CUSTOM_DATA);
}
if (xp > 0.0) {
PalaJobs.addXp(sp, "alchimiste", xp);
}
}
}
@@ -0,0 +1,41 @@
package fr.surviepote.pala.mixin;
import net.minecraft.core.BlockPos;
import net.minecraft.core.component.DataComponents;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.level.block.entity.BrewingStandBlockEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import fr.surviepote.pala.core.PalaConfig;
// SurviePote : marque les potions sorties d'un chaudron avec la valeur d'XP alchimiste
// a attribuer au joueur qui les recupere. Le marqueur est lu puis retire dans
// PalaBrewTakeXpMixin (PotionSlot.onTake), ce qui evite le farm en remettant une potion
// deja recuperee dans le chaudron.
@Mixin(BrewingStandBlockEntity.class)
public class PalaBrewXpMixin {
@Inject(method = "doBrew", at = @At("TAIL"))
private static void pala$markBrewedPotions(ServerLevel level, BlockPos pos,
BrewingStandBlockEntity stand, CallbackInfo ci) {
double xp = PalaConfig.get().alchemistBrewXp;
if (xp <= 0.0) {
return;
}
// Slots 0-2 = potions de sortie.
for (int i = 0; i < 3; i++) {
ItemStack stack = stand.getItem(i);
if (stack.isEmpty()) {
continue;
}
CustomData.update(DataComponents.CUSTOM_DATA, stack,
tag -> tag.putDouble("pala:brewed_xp", xp));
}
stand.setChanged();
}
}
@@ -0,0 +1,23 @@
package fr.surviepote.pala.network;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.Identifier;
// Reponse serveur -> client : une ligne par metier "job|niveau|xp|xp_suivant".
public record JobsDataPayload(String payload) implements CustomPacketPayload {
public static final Type<JobsDataPayload> TYPE = new Type<>(
Identifier.fromNamespaceAndPath("pala", "jobs_data"));
public static final StreamCodec<FriendlyByteBuf, JobsDataPayload> CODEC =
StreamCodec.of(
(buf, value) -> buf.writeUtf(value.payload, 32767),
buf -> new JobsDataPayload(buf.readUtf(32767)));
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
}
@@ -0,0 +1,23 @@
package fr.surviepote.pala.network;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.Identifier;
// Requete client -> serveur envoyee par le bouton "Metiers" de l'inventaire.
public record JobsRequestPayload() implements CustomPacketPayload {
public static final JobsRequestPayload INSTANCE = new JobsRequestPayload();
public static final Type<JobsRequestPayload> TYPE = new Type<>(
Identifier.fromNamespaceAndPath("pala", "jobs_request"));
public static final StreamCodec<FriendlyByteBuf, JobsRequestPayload> CODEC =
StreamCodec.unit(INSTANCE);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
}
@@ -0,0 +1,40 @@
package fr.surviepote.pala.network;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.minecraft.server.level.ServerPlayer;
import fr.surviepote.pala.core.PalaConfig;
import fr.surviepote.pala.core.PalaJobs;
// Synchronisation a la demande de la progression des metiers (bouton inventaire).
public final class PalaNetwork {
private PalaNetwork() {
}
public static void register() {
PayloadTypeRegistry.serverboundPlay().register(JobsRequestPayload.TYPE, JobsRequestPayload.CODEC);
PayloadTypeRegistry.clientboundPlay().register(JobsDataPayload.TYPE, JobsDataPayload.CODEC);
ServerPlayNetworking.registerGlobalReceiver(JobsRequestPayload.TYPE,
(payload, context) -> {
ServerPlayer player = context.player();
ServerPlayNetworking.send(player, new JobsDataPayload(build(player)));
});
}
private static String build(ServerPlayer player) {
StringBuilder sb = new StringBuilder();
for (String job : PalaJobs.JOBS) {
int level = PalaJobs.levelOf(player, job);
long xp = (long) PalaJobs.xpOf(player, job);
long previous = level <= 0 ? 0L : PalaConfig.get().xpForLevel(level);
long next = level >= 100 ? 0L : PalaConfig.get().xpForLevel(level + 1);
sb.append(job).append('|').append(level).append('|')
.append(xp).append('|').append(previous).append('|')
.append(next).append('\n');
}
return sb.toString();
}
}
@@ -264,5 +264,6 @@
"item.pala.job_candy": "Job Candy",
"block.pala.palaforge": "Palaforge",
"item.pala.palaforge": "Palaforge",
"container.pala.palaforge": "Palaforge"
"container.pala.palaforge": "Palaforge",
"screen.pala.jobs": "Jobs"
}
@@ -2,34 +2,17 @@
"type": "minecraft:block",
"pools": [
{
"condition": {
"type": "minecraft:survives_explosion"
},
"entries": [
{
"type": "minecraft:alternatives",
"children": [
{
"type": "minecraft:item",
"condition": "minecraft:tool/can_silk_touch",
"name": "pala:amethyst_ore"
},
{
"type": "minecraft:item",
"modifier": [
{
"type": "minecraft:apply_bonus",
"enchantment": "minecraft:fortune",
"formula": "minecraft:ore_drops"
},
{
"type": "minecraft:explosion_decay"
}
],
"name": "pala:amethyst_ingot"
}
]
"type": "minecraft:item",
"name": "pala:amethyst_ore"
}
],
"rolls": 1
}
],
"random_sequence": "pala:blocks/amethyst_ore"
}
}
@@ -2,34 +2,17 @@
"type": "minecraft:block",
"pools": [
{
"condition": {
"type": "minecraft:survives_explosion"
},
"entries": [
{
"type": "minecraft:alternatives",
"children": [
{
"type": "minecraft:item",
"condition": "minecraft:tool/can_silk_touch",
"name": "pala:findium_ore"
},
{
"type": "minecraft:item",
"modifier": [
{
"type": "minecraft:apply_bonus",
"enchantment": "minecraft:fortune",
"formula": "minecraft:ore_drops"
},
{
"type": "minecraft:explosion_decay"
}
],
"name": "pala:findium_gem"
}
]
"type": "minecraft:item",
"name": "pala:findium_ore"
}
],
"rolls": 1
}
],
"random_sequence": "pala:blocks/findium_ore"
}
}
@@ -2,34 +2,17 @@
"type": "minecraft:block",
"pools": [
{
"condition": {
"type": "minecraft:survives_explosion"
},
"entries": [
{
"type": "minecraft:alternatives",
"children": [
{
"type": "minecraft:item",
"condition": "minecraft:tool/can_silk_touch",
"name": "pala:kisakay_ore"
},
{
"type": "minecraft:item",
"modifier": [
{
"type": "minecraft:apply_bonus",
"enchantment": "minecraft:fortune",
"formula": "minecraft:ore_drops"
},
{
"type": "minecraft:explosion_decay"
}
],
"name": "pala:kisakay_ingot"
}
]
"type": "minecraft:item",
"name": "pala:kisakay_ore"
}
],
"rolls": 1
}
],
"random_sequence": "pala:blocks/kisakay_ore"
}
}
@@ -2,34 +2,17 @@
"type": "minecraft:block",
"pools": [
{
"condition": {
"type": "minecraft:survives_explosion"
},
"entries": [
{
"type": "minecraft:alternatives",
"children": [
{
"type": "minecraft:item",
"condition": "minecraft:tool/can_silk_touch",
"name": "pala:paladium_green_ore"
},
{
"type": "minecraft:item",
"modifier": [
{
"type": "minecraft:apply_bonus",
"enchantment": "minecraft:fortune",
"formula": "minecraft:ore_drops"
},
{
"type": "minecraft:explosion_decay"
}
],
"name": "pala:paladium_green_ingot"
}
]
"type": "minecraft:item",
"name": "pala:paladium_green_ore"
}
],
"rolls": 1
}
],
"random_sequence": "pala:blocks/paladium_green_ore"
}
}
@@ -2,34 +2,17 @@
"type": "minecraft:block",
"pools": [
{
"condition": {
"type": "minecraft:survives_explosion"
},
"entries": [
{
"type": "minecraft:alternatives",
"children": [
{
"type": "minecraft:item",
"condition": "minecraft:tool/can_silk_touch",
"name": "pala:paladium_ore"
},
{
"type": "minecraft:item",
"modifier": [
{
"type": "minecraft:apply_bonus",
"enchantment": "minecraft:fortune",
"formula": "minecraft:ore_drops"
},
{
"type": "minecraft:explosion_decay"
}
],
"name": "pala:paladium_ingot"
}
]
"type": "minecraft:item",
"name": "pala:paladium_ore"
}
],
"rolls": 1
}
],
"random_sequence": "pala:blocks/paladium_ore"
}
}
@@ -2,34 +2,17 @@
"type": "minecraft:block",
"pools": [
{
"condition": {
"type": "minecraft:survives_explosion"
},
"entries": [
{
"type": "minecraft:alternatives",
"children": [
{
"type": "minecraft:item",
"condition": "minecraft:tool/can_silk_touch",
"name": "pala:titane_ore"
},
{
"type": "minecraft:item",
"modifier": [
{
"type": "minecraft:apply_bonus",
"enchantment": "minecraft:fortune",
"formula": "minecraft:ore_drops"
},
{
"type": "minecraft:explosion_decay"
}
],
"name": "pala:titane_ingot"
}
]
"type": "minecraft:item",
"name": "pala:titane_ore"
}
],
"rolls": 1
}
],
"random_sequence": "pala:blocks/titane_ore"
}
}
@@ -3,7 +3,7 @@
"placement": [
{
"type": "minecraft:count",
"count": 3
"count": 4
},
{
"type": "minecraft:in_square"
@@ -3,7 +3,9 @@
"package": "fr.surviepote.pala.mixin",
"mixins": [
"PalaCraftXpMixin",
"PalaSmeltXpMixin"
"PalaSmeltXpMixin",
"PalaBrewXpMixin",
"PalaBrewTakeXpMixin"
],
"injectors": {
"defaultRequire": 1