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
+1
View File
@@ -0,0 +1 @@
bin
+6
View File
@@ -56,6 +56,12 @@ tasks.register('downloadMcLibs') {
new File(outDir, 'fastutil-8.5.18.jar')) 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', 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')) 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', 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')) 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', 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 @Override
public void onInitialize() { public void onInitialize() {
fr.surviepote.pala.core.PalaCore.register(); fr.surviepote.pala.core.PalaCore.register();
fr.surviepote.pala.network.PalaNetwork.register();
PalaRegistries.registerAll(); PalaRegistries.registerAll();
fr.surviepote.pala.events.PalaEvents.registerAll(); fr.surviepote.pala.events.PalaEvents.registerAll();
LOGGER.info("[SurviePote] Pala initialisé."); LOGGER.info("[SurviePote] Pala initialisé.");
@@ -1,9 +1,19 @@
package fr.surviepote.pala; 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.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.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.renderer.block.FluidModel;
import net.minecraft.client.resources.model.sprite.Material; import net.minecraft.client.resources.model.sprite.Material;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier; import net.minecraft.resources.Identifier;
public class PalaClient implements ClientModInitializer { public class PalaClient implements ClientModInitializer {
@@ -23,5 +33,25 @@ public class PalaClient implements ClientModInitializer {
fluidModel("sulfuricwater_still", "sulfuricwater_flowing")); fluidModel("sulfuricwater_still", "sulfuricwater_flowing"));
FluidRenderingRegistry.setBlockTransparency(PalaFluids.ANGELIC_WATER_BLOCK, false); FluidRenderingRegistry.setBlockTransparency(PalaFluids.ANGELIC_WATER_BLOCK, false);
FluidRenderingRegistry.setBlockTransparency(PalaFluids.FAKE_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.Level;
import net.minecraft.world.level.portal.TeleportTransition; import net.minecraft.world.level.portal.TeleportTransition;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.minecraft.world.item.ItemStack;
import fr.surviepote.pala.core.PalaConfig; import fr.surviepote.pala.core.PalaConfig;
import fr.surviepote.pala.core.PalaJobs; 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 // 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 // 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). // craft portable, wiki commandes), /furnace (cuisson directe du stack en main), /spawn (tp spawn overworld,
// Voir CDC §3.8. // delai 3 s sans grade, wiki). Voir CDC §3.8.
// Origine : CommandEnd/Nether/Overworld + procedures *CommandExecuted qui // Origine : CommandEnd/Nether/Overworld + procedures *CommandExecuted qui
// teleportaient le joueur au spawn de la dimension visee. Ici via // teleportaient le joueur au spawn de la dimension visee. Ici via
// TeleportTransition (API 26.x), sans le Teleporter custom du legacy. // 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) { 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() { private PalaCommands() {
} }
@@ -90,6 +120,8 @@ public final class PalaCommands {
.executes(command -> jobs(command.getSource()))); .executes(command -> jobs(command.getSource())));
dispatcher.register(Commands.literal("craft") dispatcher.register(Commands.literal("craft")
.executes(command -> craft(command.getSource()))); .executes(command -> craft(command.getSource())));
dispatcher.register(Commands.literal("furnace")
.executes(command -> furnace(command.getSource())));
dispatcher.register(Commands.literal("spawn") dispatcher.register(Commands.literal("spawn")
.executes(command -> spawn(command.getSource()))); .executes(command -> spawn(command.getSource())));
dispatcher.register(Commands.literal("minage") dispatcher.register(Commands.literal("minage")
@@ -827,15 +859,15 @@ public final class PalaCommands {
source.sendFailure(Component.literal("§cAucun home defini (/f sethome).")); source.sendFailure(Component.literal("§cAucun home defini (/f sethome)."));
return 0; return 0;
} }
String[] parts = faction.home.split(":"); ZonePos home = parseZone(faction.home);
if (parts.length != 4) { if (home == null) {
source.sendFailure(Component.literal("§cHome corrompu.")); source.sendFailure(Component.literal("§cHome corrompu."));
return 0; return 0;
} }
net.minecraft.server.MinecraftServer server = source.getServer(); net.minecraft.server.MinecraftServer server = source.getServer();
net.minecraft.server.level.ServerLevel target = null; net.minecraft.server.level.ServerLevel target = null;
for (net.minecraft.server.level.ServerLevel level : server.getAllLevels()) { 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; target = level;
break; break;
} }
@@ -844,19 +876,8 @@ public final class PalaCommands {
source.sendFailure(Component.literal("§cDimension inconnue.")); source.sendFailure(Component.literal("§cDimension inconnue."));
return 0; 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, 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; return Command.SINGLE_SUCCESS;
} catch (com.mojang.brigadier.exceptions.CommandSyntaxException e) { } catch (com.mojang.brigadier.exceptions.CommandSyntaxException e) {
return 0; return 0;
@@ -1114,15 +1135,15 @@ public final class PalaCommands {
"§cZone " + label + " non configuree (admin : config pala.json).")); "§cZone " + label + " non configuree (admin : config pala.json)."));
return 0; return 0;
} }
String[] parts = zone.split(":"); ZonePos parsed = parseZone(zone);
if (parts.length != 4) { if (parsed == null) {
source.sendFailure(Component.literal("§cZone " + label + " corrompue.")); source.sendFailure(Component.literal("§cZone " + label + " corrompue."));
return 0; return 0;
} }
net.minecraft.server.MinecraftServer server = source.getServer(); net.minecraft.server.MinecraftServer server = source.getServer();
net.minecraft.server.level.ServerLevel target = null; net.minecraft.server.level.ServerLevel target = null;
for (net.minecraft.server.level.ServerLevel level : server.getAllLevels()) { 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; target = level;
break; break;
} }
@@ -1131,19 +1152,8 @@ public final class PalaCommands {
source.sendFailure(Component.literal("§cDimension inconnue.")); source.sendFailure(Component.literal("§cDimension inconnue."));
return 0; 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, 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; return Command.SINGLE_SUCCESS;
} catch (com.mojang.brigadier.exceptions.CommandSyntaxException e) { } catch (com.mojang.brigadier.exceptions.CommandSyntaxException e) {
return 0; 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 // /spawn : tp au spawn overworld avec delai wiki 3 s (60 ticks). Pas d'annulation sur
// mouvement cote wiki (teleportation simple apres delai). // mouvement cote wiki (teleportation simple apres delai).
private static int spawn(CommandSourceStack source) { private static int spawn(CommandSourceStack source) {
@@ -204,10 +204,6 @@ public final class PalaTabs {
output.accept(PalaBlocks.CAULDRON_TANK_SUPPORT); output.accept(PalaBlocks.CAULDRON_TANK_SUPPORT);
output.accept(PalaBlocks.MYSTICAL_BOOKSHELF); output.accept(PalaBlocks.MYSTICAL_BOOKSHELF);
output.accept(PalaBlocks.SLEEPING_BAG); 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);
output.accept(PalaBlocks.FINDIUM_ORE_BLOCK); output.accept(PalaBlocks.FINDIUM_ORE_BLOCK);
output.accept(PalaBlocks.PALAFORGE); output.accept(PalaBlocks.PALAFORGE);
@@ -216,20 +212,6 @@ public final class PalaTabs {
output.accept(PalaBlocks.GRINDER_FRAME); output.accept(PalaBlocks.GRINDER_FRAME);
output.accept(PalaBlocks.KISAKAY_ORE); output.accept(PalaBlocks.KISAKAY_ORE);
output.accept(PalaBlocks.KISAKAY_ORE_BLOCK); 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_CRUSHER);
output.accept(PalaBlocks.PALADIUM_FURNACE); output.accept(PalaBlocks.PALADIUM_FURNACE);
output.accept(PalaBlocks.PALADIUM_ORE); 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. // Cles = ids de registres (vanilla ou pala:). Map.ofEntries : Map.of limite a 10 paires.
public Map<String, Double> minerXp = new HashMap<>(Map.ofEntries( public Map<String, Double> minerXp = new HashMap<>(Map.ofEntries(
Map.entry("minecraft:stone", 0.5), 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: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: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:nether_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: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: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: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: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("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:amethyst_ore", 12.0), Map.entry("pala:titane_ore", 16.0),
Map.entry("pala:paladium_ore", 20.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:paladium_green_ore", 20.0), Map.entry("pala:trixium_ore", 10.0),
Map.entry("pala:findium_ore", 10.0))); Map.entry("pala:findium_ore", 10.0)));
// M-mineur-xp (wiki Evolution mineur, VERIFIE) : niveaux requis par bloc. // M-mineur-xp : niveaux requis par bloc. Les blocs de base sont accessibles des le
// Niv.1 : roche/diorite/charbon/quartz/obsidienne/lapis/redstone/emeraude ; // niveau 0 (sinon un nouveau joueur ne peut jamais gagner son premier XP mineur) ;
// niv.5 : fer/or/diamant/amethyste/titane (+ minerais pala par interpretation // les minerais de fer/or/diamant/pala restent verrouilles au niveau 5 (wiki).
// "Particules paladium 20@5", ECART documente).
public Map<String, Integer> minerLevels = new HashMap<>(Map.ofEntries( public Map<String, Integer> minerLevels = new HashMap<>(Map.ofEntries(
Map.entry("minecraft:stone", 1), Map.entry("minecraft:stone", 0),
Map.entry("minecraft:diorite", 1), Map.entry("minecraft:andesite", 1), Map.entry("minecraft:cobblestone", 0), Map.entry("minecraft:deepslate", 0),
Map.entry("minecraft:coal_ore", 1), Map.entry("minecraft:deepslate_coal_ore", 1), Map.entry("minecraft:tuff", 0),
Map.entry("minecraft:quartz_ore", 1), Map.entry("minecraft:obsidian", 1), Map.entry("minecraft:diorite", 0), Map.entry("minecraft:andesite", 0),
Map.entry("minecraft:lapis_ore", 1), Map.entry("minecraft:redstone_ore", 1), Map.entry("minecraft:coal_ore", 0), Map.entry("minecraft:deepslate_coal_ore", 0),
Map.entry("minecraft:emerald_ore", 1), Map.entry("minecraft:deepslate_emerald_ore", 1), 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: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: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("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: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:paladium_ore", 5), Map.entry("pala:paladium_green_ore", 5),
Map.entry("pala:trixium_ore", 5), Map.entry("pala:findium_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 // Courbe XP : formule par defaut xp(n) = xpBase * n^xpExponent. Les anciens seuils
// s'applique aux 4 metiers (seule table publiee ; ECART documente). // 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 xpBase = 100.0;
public double xpExponent = 1.5; public double xpExponent = 1.5;
public Map<Integer, Long> levelThresholds = new HashMap<>(Map.ofEntries( public Map<Integer, Long> levelThresholds = new HashMap<>();
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)));
// Niveau mineur requis pour /nether (wiki recompenses mineur niv.3, VERIFIE). // Niveau mineur requis pour /nether (wiki recompenses mineur niv.3, VERIFIE).
public int netherMinerLevel = 3; public int netherMinerLevel = 3;
@@ -118,6 +124,8 @@ public final class PalaConfig {
// M5a (wiki VERIFIE) : chasseur = entite -> [xp, niveau requis]. // M5a (wiki VERIFIE) : chasseur = entite -> [xp, niveau requis].
// Passifs 14@0, poulpe 10@0 (page : "des le niveau 0/1"), creeper 40, wither 1000. // 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). // 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( public Map<String, double[]> hunterKills = new HashMap<>(Map.ofEntries(
Map.entry("minecraft:cow", new double[]{14, 0}), Map.entry("minecraft:cow", new double[]{14, 0}),
Map.entry("minecraft:sheep", 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:chicken", new double[]{14, 0}),
Map.entry("minecraft:rabbit", new double[]{14, 0}), Map.entry("minecraft:rabbit", new double[]{14, 0}),
Map.entry("minecraft:horse", 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:squid", new double[]{10, 0}),
Map.entry("minecraft:glow_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: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:wither", new double[]{1000, 0}),
Map.entry("minecraft:ender_dragon", new double[]{5000, 0}),
// M-hunter-3 (wiki evolution chasseur, VERIFIE) : passifs vanilla // M-hunter-3 (wiki evolution chasseur, VERIFIE) : passifs vanilla
// supplementaires (creatures moddees escargot/crabe/serpent/meduse/ // supplementaires (creatures moddees escargot/crabe/serpent/meduse/
// elephant SKIP M8, absentes ; peche "tres variable" + QTE SKIP). // 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:egg_plant_fruit", new double[]{3, 8},
"pala:okami_fruit", new double[]{4.5, 12}, "pala:okami_fruit", new double[]{4.5, 12},
"pala:kiwano", new double[]{6, 16})); "pala:kiwano", new double[]{6, 16}));
// M-alchi-1 (wiki Evolution alchimiste VERIFIE) : XP au craft = item -> [xp, niveau]. // M-alchi-1 : XP au craft = item -> [xp, niveau]. La fiole est accessible des le
// Fiole 0.2@1. Extracteur/lightning/glueballs/portails : items/crafts en M-alchi-2+. // 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( 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. // M4a : recettes machines data-driven (CDC §3.4). Cles = ids de registres.
// Defauts = legacy+wiki VERIFIES (M3). Rechargeables via /pala reload. // Defauts = legacy+wiki VERIFIES (M3). Rechargeables via /pala reload.
public Map<String, String> crusherResults = new HashMap<>(Map.of( public Map<String, String> crusherResults = new HashMap<>(Map.of(
@@ -224,6 +286,7 @@ public final class PalaConfig {
try (Reader reader = Files.newBufferedReader(path)) { try (Reader reader = Files.newBufferedReader(path)) {
PalaConfig loaded = GSON.fromJson(reader, PalaConfig.class); PalaConfig loaded = GSON.fromJson(reader, PalaConfig.class);
if (loaded != null) { if (loaded != null) {
boolean migrateBalance = loaded.configVersion < 3;
if (loaded.minerXp != null && !loaded.minerXp.isEmpty()) { if (loaded.minerXp != null && !loaded.minerXp.isEmpty()) {
instance.minerXp = loaded.minerXp; instance.minerXp = loaded.minerXp;
} }
@@ -236,6 +299,12 @@ public final class PalaConfig {
if (loaded.xpExponent > 0) { if (loaded.xpExponent > 0) {
instance.xpExponent = loaded.xpExponent; instance.xpExponent = loaded.xpExponent;
} }
if (loaded.levelThresholds != null) {
instance.levelThresholds = loaded.levelThresholds;
}
if (loaded.alchemistBrewXp > 0) {
instance.alchemistBrewXp = loaded.alchemistBrewXp;
}
if (loaded.netherMinerLevel >= 0) { if (loaded.netherMinerLevel >= 0) {
instance.netherMinerLevel = loaded.netherMinerLevel; instance.netherMinerLevel = loaded.netherMinerLevel;
} }
@@ -256,6 +325,16 @@ public final class PalaConfig {
instance.farmerHarvest = loaded.farmerHarvest; instance.farmerHarvest = loaded.farmerHarvest;
mergedDefaultsApplied |= mergeDefaults(instance.farmerHarvest, __def); 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()) { if (loaded.crusherResults != null && !loaded.crusherResults.isEmpty()) {
java.util.Map __def = new java.util.HashMap<>(instance.crusherResults); java.util.Map __def = new java.util.HashMap<>(instance.crusherResults);
instance.crusherResults = loaded.crusherResults; instance.crusherResults = loaded.crusherResults;
@@ -275,15 +354,38 @@ public final class PalaConfig {
if (loaded.furnaceFuels != null && !loaded.furnaceFuels.isEmpty()) { if (loaded.furnaceFuels != null && !loaded.furnaceFuels.isEmpty()) {
instance.furnaceFuels = loaded.furnaceFuels; 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 // 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 // code sont en memoire (Gson utilise le constructeur no-args, les champs
// absents gardent leurs valeurs par defaut, jamais null), on re-sauvegarde // absents gardent leurs valeurs par defaut, jamais null), on re-sauvegarde
// pour les materialiser si une cle attendue manque. Une table explicitement // pour les materialiser si une cle attendue manque. Une table explicitement
// videe (cle presente, tableau vide) est respectee telle quelle. // 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", "xpBase", "xpExponent", "levelThresholds", "netherMinerLevel", "stickCooldowns",
"rankNodes", "hunterKills", "farmerHarvest", "crusherXp", "rankNodes", "hunterKills", "farmerHarvest", "crusherXp",
"alchemistCraft", "alchemistCraft", "alchemistBrewXp",
"crusherResults", "crusherFuels", "crusherInfiniteFuels", "crusherResults", "crusherFuels", "crusherInfiniteFuels",
"furnaceSmelts", "furnaceFuels", "furnaceSmelts", "furnaceFuels",
"serverName", "wikiUrl", "discordUrl", "shopUrl", "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) { public long xpForLevel(int level) {
Long exact = levelThresholds.get(level); Long exact = levelThresholds.get(level);
if (exact != null) { if (exact != null) {
@@ -1,6 +1,9 @@
package fr.surviepote.pala.core; package fr.surviepote.pala.core;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.UUID;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component; 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"); 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() { private PalaJobs() {
} }
@@ -46,9 +53,32 @@ public final class PalaJobs {
PalaRewards.grant(player, job, data.level); PalaRewards.grant(player, job, data.level);
} }
PalaStorage.get().markDirty(); PalaStorage.get().markDirty();
sendHud(player, job, data);
return data.level; 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). */ /** Hook casse-bloc : XP mineur selon la table config + niveau requis (wiki). */
public static void onBlockBreak(ServerPlayer player, ServerLevel level, BlockPos pos, BlockState state) { public static void onBlockBreak(ServerPlayer player, ServerLevel level, BlockPos pos, BlockState state) {
Identifier id = level.registryAccess() Identifier id = level.registryAccess()
@@ -160,7 +160,16 @@ public final class PalaJobEvents {
} }
private static void afterDeath(LivingEntity entity, DamageSource source) { 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)) { if (!(killer instanceof ServerPlayer player)) {
return; 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", "item.pala.job_candy": "Job Candy",
"block.pala.palaforge": "Palaforge", "block.pala.palaforge": "Palaforge",
"item.pala.palaforge": "Palaforge", "item.pala.palaforge": "Palaforge",
"container.pala.palaforge": "Palaforge" "container.pala.palaforge": "Palaforge",
"screen.pala.jobs": "Jobs"
} }
@@ -2,30 +2,13 @@
"type": "minecraft:block", "type": "minecraft:block",
"pools": [ "pools": [
{ {
"condition": {
"type": "minecraft:survives_explosion"
},
"entries": [ "entries": [
{ {
"type": "minecraft:alternatives", "type": "minecraft:item",
"children": [ "name": "pala:amethyst_ore"
{
"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"
}
]
} }
], ],
"rolls": 1 "rolls": 1
@@ -2,30 +2,13 @@
"type": "minecraft:block", "type": "minecraft:block",
"pools": [ "pools": [
{ {
"condition": {
"type": "minecraft:survives_explosion"
},
"entries": [ "entries": [
{ {
"type": "minecraft:alternatives", "type": "minecraft:item",
"children": [ "name": "pala:findium_ore"
{
"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"
}
]
} }
], ],
"rolls": 1 "rolls": 1
@@ -2,30 +2,13 @@
"type": "minecraft:block", "type": "minecraft:block",
"pools": [ "pools": [
{ {
"condition": {
"type": "minecraft:survives_explosion"
},
"entries": [ "entries": [
{ {
"type": "minecraft:alternatives", "type": "minecraft:item",
"children": [ "name": "pala:kisakay_ore"
{
"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"
}
]
} }
], ],
"rolls": 1 "rolls": 1
@@ -2,30 +2,13 @@
"type": "minecraft:block", "type": "minecraft:block",
"pools": [ "pools": [
{ {
"condition": {
"type": "minecraft:survives_explosion"
},
"entries": [ "entries": [
{ {
"type": "minecraft:alternatives", "type": "minecraft:item",
"children": [ "name": "pala:paladium_green_ore"
{
"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"
}
]
} }
], ],
"rolls": 1 "rolls": 1
@@ -2,30 +2,13 @@
"type": "minecraft:block", "type": "minecraft:block",
"pools": [ "pools": [
{ {
"condition": {
"type": "minecraft:survives_explosion"
},
"entries": [ "entries": [
{ {
"type": "minecraft:alternatives", "type": "minecraft:item",
"children": [ "name": "pala:paladium_ore"
{
"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"
}
]
} }
], ],
"rolls": 1 "rolls": 1
@@ -2,30 +2,13 @@
"type": "minecraft:block", "type": "minecraft:block",
"pools": [ "pools": [
{ {
"condition": {
"type": "minecraft:survives_explosion"
},
"entries": [ "entries": [
{ {
"type": "minecraft:alternatives", "type": "minecraft:item",
"children": [ "name": "pala:titane_ore"
{
"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"
}
]
} }
], ],
"rolls": 1 "rolls": 1
@@ -3,7 +3,7 @@
"placement": [ "placement": [
{ {
"type": "minecraft:count", "type": "minecraft:count",
"count": 3 "count": 4
}, },
{ {
"type": "minecraft:in_square" "type": "minecraft:in_square"
@@ -3,7 +3,9 @@
"package": "fr.surviepote.pala.mixin", "package": "fr.surviepote.pala.mixin",
"mixins": [ "mixins": [
"PalaCraftXpMixin", "PalaCraftXpMixin",
"PalaSmeltXpMixin" "PalaSmeltXpMixin",
"PalaBrewXpMixin",
"PalaBrewTakeXpMixin"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1
Binary file not shown.
+14
View File
@@ -0,0 +1,14 @@
Zone home corrompue
/furnace manquant
Les minerais doivent drop leur bloc (sauf trixium)
Le système de métier, doit avoir un bouton dans l'inventaire pour voir leur progression
Les tree de kiwano, eggplant etc ne doievent pas être dans l'inventaire admin
augmenter un peu la luck des fillons de minerais de paladium
-----
Les boom boots doivent faire tomber bien plus vite, et en fonction de la hauteur/vitesse, faire une explosion +- grosse