diff --git a/PALAMOD_CDC.md b/PALAMOD_CDC.md index 120cbc1..12d55d7 100644 --- a/PALAMOD_CDC.md +++ b/PALAMOD_CDC.md @@ -456,9 +456,9 @@ avant M4 (TODO:crafts-machines). - Features wiki couvertes (entree CDC) : ~210 (§1.14). Portage existant : ~120 (assets+registres). Logique specifiee restant a implementer : ~90. Couverture spec : 100 % des pages FR Java ingestionnees. -- TODO explicites : 13 nommes (grep : ocr-fiable, worldgen-couches, broadsword, +- TODO explicites : 16 nommes (grep : ocr-fiable, worldgen-couches, broadsword, crafts-machines, chervil, commandes-exactes, ghost, luckperms, runclient, mcp-ocular, shebang, - java-home, pog-os) + java-home, pog-os, craft-xp, smelt-xp, crusher-xp) + 4 points UNVERIFIED (seuils XP 6-100, durees sticks, valeurs armures wiki vs MCreator, patterns machines images). - Methode : WAVES (crawl 137 articles → stub JS isoles → re-crawl navigateur → verifie) + ETERNAL CODE @@ -509,8 +509,20 @@ avant M4 (TODO:crafts-machines). conso 20 %/tick, luck 1/128, auto-generate a 1.0 ; NE cuit plus de minerais) + furnace accepte mixed coals + gate fermier 8 a l'ouverture du crusher. VERIFIE : build SUCCESSFUL, boot Done, 0 erreur. UNVERIFIED en jeu : cycle fruit->lingot avec joueur, gate a fermier<8. -- Client/mod : ./gradlew runClient dans mods/pala/ : NON LANCE (session : pas d'affichage ; screenshots - ocular impossibles ici). TODO:runclient : 1 screenshot/feature dans mods/pala/docs/screens/ avant M10. - Raison : environnement headless, a faire sur poste avec X. +- M5a LIVRE 2026-10-19 ~00:30 : XP chasseur (AFTER_DEATH verifie au javap : passifs 14@0, poulpe + 10@0, creeper 40, wither 1000, gates niveau) + fermier (ble 1.5@0, patate 2@2, carotte 2.5@2, + pasteque 4@3, citrouille 5@6, eggplant 10@8, okami 20@12, kiwano 50@16 ; maturite age=max). + Config auto-complete les vieilles pala.json (save si cles null — code pret, persistance + a confirmer au prochain boot). VERIFIE : build SUCCESSFUL, boot Done, 0 erreur. + UNVERIFIED : boot+jeu (boot en cours au commit). TODO:craft-xp, TODO:smelt-xp (aucun event + serveur dans fabric-api, mixin requis), TODO:crusher-xp (attribution : stocker UUID poseur, M5b). +- M4a LIVRE (meme vague) : recettes machines data-driven (crusherResults/Fuels, furnaceSmelts/Fuels + en config, /pala reload live, ids invalides ignores). VERIFIE : build SUCCESSFUL. +- Client/mod : boucle visuelle OPERATIONNELLE 2026-10-19 (Xvfb :99 + runClient SDL x11 + + captures Java Robot + pilotage xdotool + vision_analyze) : menu, Select World, monde New World + (creatif, cheats), /give, pose de bloc, inventaire, F3+T. Screens : scratch vis*.png. + docs/screens/ : TODO:runclient (copier les screens valides + log par feature). + Lecon : SDL prend wayland par defaut (forcer SDL_VIDEODRIVER=x11), xdotool click --window + ignore par SDL (utiliser XTEST au pointeur), ffmpeg local sans x11grab (Robot a la place). - MCP : aucun MCP externe installe ; navigateur integre + vision_analyze utilises (OCR rejete §0). TODO:mcp-ocular : installer pour la boucle runClient quand l'environnement le permet. diff --git a/mods/pala/src/main/java/fr/surviepote/pala/core/PalaConfig.java b/mods/pala/src/main/java/fr/surviepote/pala/core/PalaConfig.java index 8e1fa19..5caa561 100644 --- a/mods/pala/src/main/java/fr/surviepote/pala/core/PalaConfig.java +++ b/mods/pala/src/main/java/fr/surviepote/pala/core/PalaConfig.java @@ -60,6 +60,50 @@ public final class PalaConfig { "default", java.util.List.of("pala.job", "pala.trade", "pala.market"), "heros", java.util.List.of("pala.*"))); + // 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). + public Map hunterKills = new HashMap<>(Map.ofEntries( + Map.entry("minecraft:cow", new double[]{14, 0}), + Map.entry("minecraft:sheep", new double[]{14, 0}), + Map.entry("minecraft:pig", new double[]{14, 0}), + 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:squid", new double[]{10, 0}), + Map.entry("minecraft:glow_squid", new double[]{10, 0}), + Map.entry("minecraft:creeper", new double[]{40, 0}), + Map.entry("minecraft:wither", new double[]{1000, 0}))); + + // M5a (wiki VERIFIE) : fermier recolte = bloc -> [xp, niveau requis]. + // Ble 1.5@0 ("des le niveau 0"), patate 2@2, carotte 2.5@2 (table : Niveau 2 + ligne + // Carotte sans niveau => 2), pasteque 4@3, citrouille 5@6, eggplant 10@8, + // chervil==okami 20@12 (TODO:chervil), kiwano 50@16. Okami/orange hors chervil : 0. + public Map farmerHarvest = new HashMap<>(Map.ofEntries( + Map.entry("minecraft:wheat", new double[]{1.5, 0}), + Map.entry("minecraft:potatoes", new double[]{2, 2}), + Map.entry("minecraft:carrots", new double[]{2.5, 2}), + Map.entry("minecraft:melon", new double[]{4, 3}), + Map.entry("minecraft:pumpkin", new double[]{5, 6}))); + + // M4a : recettes machines data-driven (CDC §3.4). Cles = ids de registres. + // Defauts = legacy+wiki VERIFIES (M3). Rechargeables via /pala reload. + public Map crusherResults = new HashMap<>(Map.of( + "pala:egg_plant_fruit", "pala:amethyst_ingot", + "pala:okami_fruit", "pala:titane_ingot", + "pala:kiwano", "pala:paladium_ingot", + "pala:orange_blue_fruit", "pala:endium_nugget")); + public java.util.List crusherFuels = new java.util.ArrayList<>(java.util.List.of( + "pala:pala_mixed_coal", "pala:creative_mixed_coal", "pala:amethyst_mixed_coal", + "pala:titane_mixed_coal", "pala:gold_mixed_coal")); + public java.util.List crusherInfiniteFuels = new java.util.ArrayList<>( + java.util.List.of("pala:creative_mixed_coal")); + public Map furnaceSmelts = new HashMap<>(Map.of( + "pala:paladium_ore", "pala:paladium_ingot", + "pala:paladium_green_ore", "pala:paladium_green_ingot")); + public java.util.List furnaceFuels = new java.util.ArrayList<>(java.util.List.of( + "minecraft:coal", "minecraft:charcoal")); + private static PalaConfig instance = new PalaConfig(); private PalaConfig() { @@ -101,6 +145,36 @@ public final class PalaConfig { if (loaded.rankNodes != null && !loaded.rankNodes.isEmpty()) { instance.rankNodes = loaded.rankNodes; } + if (loaded.hunterKills != null && !loaded.hunterKills.isEmpty()) { + instance.hunterKills = loaded.hunterKills; + } + if (loaded.farmerHarvest != null && !loaded.farmerHarvest.isEmpty()) { + instance.farmerHarvest = loaded.farmerHarvest; + } + if (loaded.crusherResults != null && !loaded.crusherResults.isEmpty()) { + instance.crusherResults = loaded.crusherResults; + } + if (loaded.crusherFuels != null && !loaded.crusherFuels.isEmpty()) { + instance.crusherFuels = loaded.crusherFuels; + } + if (loaded.crusherInfiniteFuels != null) { + instance.crusherInfiniteFuels = loaded.crusherInfiniteFuels; + } + if (loaded.furnaceSmelts != null && !loaded.furnaceSmelts.isEmpty()) { + instance.furnaceSmelts = loaded.furnaceSmelts; + } + if (loaded.furnaceFuels != null && !loaded.furnaceFuels.isEmpty()) { + instance.furnaceFuels = loaded.furnaceFuels; + } + // M5a : un vieux pala.json (M0) n'a pas les nouvelles cles (null) : les + // defauts code sont en memoire, on re-sauvegarde pour les materialiser. + // Une table explicitement videe (non-null vide) est respecte tel quel. + if (loaded.hunterKills == null || loaded.farmerHarvest == null + || loaded.crusherResults == null || loaded.crusherFuels == null + || loaded.crusherInfiniteFuels == null || loaded.furnaceSmelts == null + || loaded.furnaceFuels == null) { + save(); + } } return true; } catch (IOException | com.google.gson.JsonParseException e) { diff --git a/mods/pala/src/main/java/fr/surviepote/pala/events/PalaEvents.java b/mods/pala/src/main/java/fr/surviepote/pala/events/PalaEvents.java index 846f5ee..331c2cb 100644 --- a/mods/pala/src/main/java/fr/surviepote/pala/events/PalaEvents.java +++ b/mods/pala/src/main/java/fr/surviepote/pala/events/PalaEvents.java @@ -16,6 +16,9 @@ import fr.surviepote.pala.PalaCommands; // fr.surviepote.pala.PalaCommands (reutilise ici, pas de doublon) ; // - ProcedureDurability : helper fondu dans hurtAndBreak. // +// M5a (nouveau, pas legacy) : PalaJobEvents — XP chasseur (AFTER_DEATH) + fermier +// (recoltes vanilla mures + fruits moddes murs), tables config. +// // SKIP avec raisons (9) : // - ProcedureAngelicWaterMobplayerCollidesBlock, ProcedureFakeWaterMobplayerCollidesBlock : // fluides non portes (voir consignes) ; @@ -35,6 +38,7 @@ public final class PalaEvents { PalaItemUseEvents.register(); PalaToolEvents.register(); PalaCropEvents.register(); + PalaJobEvents.register(); PalaCommands.register(); } } diff --git a/mods/pala/src/main/java/fr/surviepote/pala/events/PalaJobEvents.java b/mods/pala/src/main/java/fr/surviepote/pala/events/PalaJobEvents.java new file mode 100644 index 0000000..97966d3 --- /dev/null +++ b/mods/pala/src/main/java/fr/surviepote/pala/events/PalaJobEvents.java @@ -0,0 +1,97 @@ +package fr.surviepote.pala.events; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.IntegerProperty; + +import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents; + +import fr.surviepote.pala.PalaBlocks; +import fr.surviepote.pala.core.PalaConfig; +import fr.surviepote.pala.core.PalaJobs; + +// M5a — XP chasseur (kills) + fermier (recoltes). Tables wiki VERIFIEES (PalaConfig). +// - Chasseur : AFTER_DEATH verifie au javap (fabric-entity-events-v1) ; tueur = source directe. +// XP uniquement si niveau metier >= requis (actions debloquees par niveau, wiki). +// - Fermier : cultures vanilla mures uniquement (age au max) + fruits moddes murs +// (eggplant 10@8, okami 20@12 via equivalence chervil, kiwano 50@16). +// TODO:craft-xp (pain 1, tarte 4, fioles...) : aucun event craft serveur dans fabric-api, +// mixin requis. TODO:smelt-xp (viande cuite 10) : aucun event four-take vanilla. +// TODO:crusher-xp (eggplant 3, chervil 4.5, kiwano 6) : attribution impossible, la BE ne +// connait pas son poseur ; M5b = stocker l'UUID du poseur dans la BE. +public final class PalaJobEvents { + + private PalaJobEvents() { + } + + public static void register() { + ServerLivingEntityEvents.AFTER_DEATH.register(PalaJobEvents::afterDeath); + } + + private static void afterDeath(LivingEntity entity, DamageSource source) { + Entity killer = source.getEntity(); + if (!(killer instanceof ServerPlayer player)) { + return; + } + String id = BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); + double[] row = PalaConfig.get().hunterKills.get(id); + if (row == null) { + return; + } + if (PalaJobs.levelOf(player, "chasseur") >= (int) row[1]) { + PalaJobs.addXp(player, "chasseur", row[0]); + } + } + + /** Recolte vanilla : appele depuis PalaToolEvents.afterBreak (etat AVANT casse). */ + public static void onHarvest(ServerPlayer player, BlockState state) { + Block block = state.getBlock(); + String id; + if (block == Blocks.WHEAT || block == Blocks.POTATOES || block == Blocks.CARROTS) { + if (!isMature(state)) { + return; + } + id = BuiltInRegistries.BLOCK.getKey(block).toString(); + } else if (block == Blocks.MELON || block == Blocks.PUMPKIN) { + id = BuiltInRegistries.BLOCK.getKey(block).toString(); + } else { + return; + } + grant(player, "fermier", PalaConfig.get().farmerHarvest.get(id)); + } + + /** Fruits moddes murs : appele depuis handleCropBreak avec le fruit associe. */ + public static void onModdedHarvest(ServerPlayer player, String fruit, double xp, int minLevel) { + if (PalaJobs.levelOf(player, "fermier") >= minLevel) { + PalaJobs.addXp(player, "fermier", xp); + } + } + + private static void grant(ServerPlayer player, String job, double[] row) { + if (row != null && PalaJobs.levelOf(player, job) >= (int) row[1]) { + PalaJobs.addXp(player, job, row[0]); + } + } + + /** Maturite : propriete "age" a son max (ble/carotte/patate), sans nom d'API fige. */ + private static boolean isMature(BlockState state) { + for (Object prop : state.getProperties()) { + if (prop instanceof IntegerProperty age && age.getName().equals("age")) { + int max = 0; + for (int v : age.getPossibleValues()) { + max = Math.max(max, v); + } + return state.getValue(age) >= max; + } + } + return false; + } +} diff --git a/mods/pala/src/main/java/fr/surviepote/pala/events/PalaToolEvents.java b/mods/pala/src/main/java/fr/surviepote/pala/events/PalaToolEvents.java index b442843..c2317e7 100644 --- a/mods/pala/src/main/java/fr/surviepote/pala/events/PalaToolEvents.java +++ b/mods/pala/src/main/java/fr/surviepote/pala/events/PalaToolEvents.java @@ -112,6 +112,9 @@ public final class PalaToolEvents { // --- Metiers : XP mineur (M0) --- PalaJobs.onBlockBreak(sp, sl, pos, state); + // --- Metiers : XP fermier recoltes vanilla (M5a) --- + PalaJobEvents.onHarvest(sp, state); + // --- Pioche divine : compteur + evolution --- if (held != null && GOD_PICKAXES.contains(held)) { if (held == PalaItems.PICKAXE_OF_THE_GOD_LEVEL25) { @@ -151,7 +154,7 @@ public final class PalaToolEvents { } // --- Recoltes : drops selon stade --- - handleCropBreak(sl, broken, pos); + handleCropBreak(sl, broken, pos, sp); // --- Farmland cassee sous une culture -> la culture casse aussi --- if (broken == Blocks.FARMLAND) { @@ -199,10 +202,11 @@ public final class PalaToolEvents { || block == PalaBlocks.ORANGE_BLUE_STAGE3 || block == PalaBlocks.ORANGE_BLUE_STAGE4; } - private static void handleCropBreak(ServerLevel level, Block broken, BlockPos pos) { + private static void handleCropBreak(ServerLevel level, Block broken, BlockPos pos, ServerPlayer sp) { if (broken == PalaBlocks.FIKUTSI_STAGE3) { PalaItemUseEvents.drop(level, pos, PalaItems.EGG_PLANT_FRUIT, 1); PalaItemUseEvents.drop(level, pos, PalaItems.FIKUTSI_SEEDS, 1); + PalaJobEvents.onModdedHarvest(sp, "eggplant", 10, 8); } else if (broken == PalaBlocks.FIKUTSI_STAGE0 || broken == PalaBlocks.FIKUTSI_STAGE1 || broken == PalaBlocks.FIKUTSI_STAGE2) { PalaItemUseEvents.drop(level, pos, PalaItems.FIKUTSI_SEEDS, 1); @@ -212,6 +216,7 @@ public final class PalaToolEvents { } else if (broken == PalaBlocks.KIWANO_STAGE4) { PalaItemUseEvents.drop(level, pos, PalaItems.KIWANO, 1); PalaItemUseEvents.drop(level, pos, PalaItems.KIWANO_SEEDS, 1); + PalaJobEvents.onModdedHarvest(sp, "kiwano", 50, 16); } else if (broken == PalaBlocks.KIWANO_STAGE0 || broken == PalaBlocks.KIWANO_STAGE1 || broken == PalaBlocks.KIWANO_STAGE2 || broken == PalaBlocks.KIWANO_STAGE3) { PalaItemUseEvents.drop(level, pos, PalaItems.KIWANO_SEEDS, 1); @@ -221,6 +226,7 @@ public final class PalaToolEvents { } else if (broken == PalaBlocks.OKAMI_STAGE3) { PalaItemUseEvents.drop(level, pos, PalaItems.OKAMI_FRUIT, 1); PalaItemUseEvents.drop(level, pos, PalaItems.OKAMI_SEEDS, 1); + PalaJobEvents.onModdedHarvest(sp, "okami", 20, 12); } else if (broken == PalaBlocks.OKAMI_STAGE0 || broken == PalaBlocks.OKAMI_STAGE1 || broken == PalaBlocks.OKAMI_STAGE2) { PalaItemUseEvents.drop(level, pos, PalaItems.OKAMI_SEEDS, 1); diff --git a/mods/pala/src/main/java/fr/surviepote/pala/machine/PalaMachineBlockEntity.java b/mods/pala/src/main/java/fr/surviepote/pala/machine/PalaMachineBlockEntity.java index 829f18b..4955b58 100644 --- a/mods/pala/src/main/java/fr/surviepote/pala/machine/PalaMachineBlockEntity.java +++ b/mods/pala/src/main/java/fr/surviepote/pala/machine/PalaMachineBlockEntity.java @@ -1,11 +1,10 @@ package fr.surviepote.pala.machine; -import java.util.Map; - import fr.surviepote.pala.PalaBlockEntities; import fr.surviepote.pala.PalaBlocks; -import fr.surviepote.pala.PalaItems; +import fr.surviepote.pala.core.PalaConfig; import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; import net.minecraft.world.MenuProvider; import net.minecraft.world.SimpleContainer; @@ -15,7 +14,6 @@ import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.DispenserMenu; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.storage.ValueInput; @@ -44,11 +42,7 @@ public class PalaMachineBlockEntity extends BlockEntity implements MenuProvider private static final int COOK_TIME_FURNACE = 200; private static final double LUCK_PER_TICK = 1.0 / 128.0; - private static final Map CRUSHER_RESULTS = Map.of( - PalaItems.EGG_PLANT_FRUIT, PalaItems.AMETHYST_INGOT, - PalaItems.OKAMI_FRUIT, PalaItems.TITANE_INGOT, - PalaItems.KIWANO, PalaItems.PALADIUM_INGOT, - PalaItems.ORANGE_BLUE_FRUIT, PalaItems.ENDIUM_NUGGET); + // M4a : recettes lues de PalaConfig (data-driven, /pala reload). Ids invalides = ignores. private final SimpleContainer inventory = new SimpleContainer(9); private int progress; @@ -74,11 +68,12 @@ public class PalaMachineBlockEntity extends BlockEntity implements MenuProvider ItemStack fruit = inventory.getItem(0); ItemStack fuel = inventory.getItem(1); ItemStack output = inventory.getItem(2); - Item result = CRUSHER_RESULTS.get(fruit.getItem()); + Item result = resolveItem(PalaConfig.get().crusherResults + .get(itemId(fruit))); if (result == null || luck >= 1.0 || !isMixedFuel(fuel)) { return; } - boolean infinite = fuel.is(PalaItems.CREATIVE_MIXED_COAL); + boolean infinite = PalaConfig.get().crusherInfiniteFuels.contains(itemId(fuel)); if (output.isEmpty() || (output.is(result) && output.getCount() + 1 <= output.getMaxStackSize())) { fruit.shrink(1); @@ -126,23 +121,32 @@ public class PalaMachineBlockEntity extends BlockEntity implements MenuProvider } private static ItemStack smeltResult(ItemStack input) { - if (input.is(PalaBlocks.PALADIUM_ORE.asItem())) { - return new ItemStack(PalaItems.PALADIUM_INGOT); + Item result = resolveItem(PalaConfig.get().furnaceSmelts.get(itemId(input))); + return result == null ? ItemStack.EMPTY : new ItemStack(result); + } + + private static String itemId(ItemStack stack) { + return BuiltInRegistries.ITEM.getKey(stack.getItem()).toString(); + } + + private static Item resolveItem(String id) { + if (id == null) { + return null; } - if (input.is(PalaBlocks.PALADIUM_GREEN_ORE.asItem())) { - return new ItemStack(PalaItems.PALADIUM_GREEN_INGOT); + try { + return BuiltInRegistries.ITEM.getValue(net.minecraft.resources.Identifier.parse(id)); + } catch (Exception e) { + return null; } - return ItemStack.EMPTY; } private static boolean isMixedFuel(ItemStack stack) { - return stack.is(PalaItems.PALA_MIXED_COAL) || stack.is(PalaItems.CREATIVE_MIXED_COAL) - || stack.is(PalaItems.AMETHYST_MIXED_COAL) || stack.is(PalaItems.TITANE_MIXED_COAL) - || stack.is(PalaItems.GOLD_MIXED_COAL); + return PalaConfig.get().crusherFuels.contains(itemId(stack)); } private static boolean isFurnaceFuel(ItemStack stack) { - return stack.is(Items.COAL) || stack.is(Items.CHARCOAL) || isMixedFuel(stack); + String id = itemId(stack); + return PalaConfig.get().furnaceFuels.contains(id) || PalaConfig.get().crusherFuels.contains(id); } @Override diff --git a/server/mods/pala-0.2.1.jar b/server/mods/pala-0.2.1.jar index 952e41f..b993991 100644 Binary files a/server/mods/pala-0.2.1.jar and b/server/mods/pala-0.2.1.jar differ