Phase 5: Cobblebase jobs take back gated work, dig only zone ores
From Adrian's Mining test (2026-10-31): - A gated job assigned to a pastured Pokemon that doesn't pass its gate is taken back at once (BaseManager assignment cleared, Cobblebase's assignment sync sent, owner told what the Pokemon needs), so it no longer sticks or shows under Buffs -> Active. - Mining, Ore Finder and Evolution-item Finder only give the ores and evolution stones of the pasture's zone: new loot condition cobblesurvival:in_zone, the pack's versions of the 12 Cobblebase tables (tools/cobblebase_loot.py), Cobblebase's built-in copies removed at server start (ore/BaseLoot.kt). Mining adds quartz and the evolution stones, drops the type gems. - Mining's loot reaching the chest needs an Item Gatherer in the same pasture (Cobblebase's design); documented, not changed. - Self-tests gated_jobs_are_taken_back, base_jobs_dig_their_zones_ores; docs, balance table, licenses, Phase 5 checklist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
70cc2cad50
commit
11a583db01
27 files changed
+1458
-28
No files matched your search
@@ -74,6 +74,11 @@ python3 tools/zone_ores.py
|
|||||||
```
|
```
|
||||||
Writes Regenerating Ore Veins' config (`pack/config/regenerating_ore_veins/`) and the companion mod's ore node table from `world/zones.json` → `ores`: each ore tier's surface or cave-floor nodes and underground veins only in its zone, plus `world/vein-sources.json` for the field guide. Run after changing the ore tiers or the zone grid; the template must be rebuilt for new veins.
|
Writes Regenerating Ore Veins' config (`pack/config/regenerating_ore_veins/`) and the companion mod's ore node table from `world/zones.json` → `ores`: each ore tier's surface or cave-floor nodes and underground veins only in its zone, plus `world/vein-sources.json` for the field guide. Run after changing the ore tiers or the zone grid; the template must be rebuilt for new veins.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 tools/cobblebase_loot.py
|
||||||
|
```
|
||||||
|
Writes the pack's versions of Cobblebase's Mining, Ore Finder and Evolution-item Finder loot tables (`datapacks/core/data/cobblebase/loot_table/`): ores and evolution stones only from the pasture's zone, by `world/zones.json` → `ores`, and no type gems from Mining. Reads Cobblebase's own tables from its jar in `build/server/mods`; run after changing the ore tiers or updating Cobblebase.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 tools/drop_audit.py
|
python3 tools/drop_audit.py
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:chest",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"rolls": 1,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:fire_stone",
|
||||||
|
"weight": 10,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"sunscorch"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:water_stone",
|
||||||
|
"weight": 10,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"mire"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:thunder_stone",
|
||||||
|
"weight": 10,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"westmarch"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:leaf_stone",
|
||||||
|
"weight": 10,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"jungle"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:moon_stone",
|
||||||
|
"weight": 10,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"dripstone"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:sun_stone",
|
||||||
|
"weight": 10,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"highlands"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:chest",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"rolls": 1,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:metal_coat",
|
||||||
|
"weight": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:kings_rock",
|
||||||
|
"weight": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:dragon_scale",
|
||||||
|
"weight": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:upgrade",
|
||||||
|
"weight": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:dubious_disc",
|
||||||
|
"weight": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:prism_scale",
|
||||||
|
"weight": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:protector",
|
||||||
|
"weight": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:reaper_cloth",
|
||||||
|
"weight": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:deep_sea_tooth",
|
||||||
|
"weight": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:deep_sea_scale",
|
||||||
|
"weight": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:chest",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"rolls": 1,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:rare_candy",
|
||||||
|
"weight": 5,
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"function": "set_count",
|
||||||
|
"count": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:exp_candy_xl",
|
||||||
|
"weight": 5,
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"function": "set_count",
|
||||||
|
"count": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:ability_patch",
|
||||||
|
"weight": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:ability_capsule",
|
||||||
|
"weight": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:chest",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"rolls": 1,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:dusk_stone",
|
||||||
|
"weight": 10,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"deep_dark"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:dawn_stone",
|
||||||
|
"weight": 10,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"frostpeak"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:shiny_stone",
|
||||||
|
"weight": 10,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"lush_caves"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:ice_stone",
|
||||||
|
"weight": 10,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"frostpeak"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:link_cable",
|
||||||
|
"weight": 8
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:chest",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"rolls": 1,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:raw_copper",
|
||||||
|
"weight": 10,
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"function": "set_count",
|
||||||
|
"count": {
|
||||||
|
"min": 2,
|
||||||
|
"max": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"southwold"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:raw_iron",
|
||||||
|
"weight": 8,
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"function": "set_count",
|
||||||
|
"count": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"westmarch"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:coal",
|
||||||
|
"weight": 10,
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"function": "set_count",
|
||||||
|
"count": {
|
||||||
|
"min": 2,
|
||||||
|
"max": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"button_isle"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:chest",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"rolls": 1,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:raw_gold",
|
||||||
|
"weight": 10,
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"function": "set_count",
|
||||||
|
"count": {
|
||||||
|
"min": 2,
|
||||||
|
"max": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"sunscorch"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:diamond",
|
||||||
|
"weight": 6,
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"function": "set_count",
|
||||||
|
"count": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"deep_dark"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:emerald",
|
||||||
|
"weight": 7,
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"function": "set_count",
|
||||||
|
"count": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"frostpeak"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:amethyst_shard",
|
||||||
|
"weight": 8,
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"function": "set_count",
|
||||||
|
"count": {
|
||||||
|
"min": 2,
|
||||||
|
"max": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"dripstone"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:chest",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"rolls": 1,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:diamond",
|
||||||
|
"weight": 8,
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"function": "set_count",
|
||||||
|
"count": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"deep_dark"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:ancient_debris",
|
||||||
|
"weight": 3,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"basalt"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:netherite_scrap",
|
||||||
|
"weight": 2,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"basalt"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:emerald",
|
||||||
|
"weight": 6,
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"function": "set_count",
|
||||||
|
"count": {
|
||||||
|
"min": 2,
|
||||||
|
"max": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"frostpeak"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:chest",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"rolls": 1,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:raw_iron",
|
||||||
|
"weight": 10,
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"function": "set_count",
|
||||||
|
"count": {
|
||||||
|
"min": 2,
|
||||||
|
"max": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"westmarch"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:raw_gold",
|
||||||
|
"weight": 8,
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"function": "set_count",
|
||||||
|
"count": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"sunscorch"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:lapis_lazuli",
|
||||||
|
"weight": 7,
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"function": "set_count",
|
||||||
|
"count": {
|
||||||
|
"min": 2,
|
||||||
|
"max": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"highlands"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:redstone",
|
||||||
|
"weight": 8,
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"function": "set_count",
|
||||||
|
"count": {
|
||||||
|
"min": 2,
|
||||||
|
"max": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"jungle"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:quartz",
|
||||||
|
"weight": 8,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"basalt"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:chest",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"rolls": 1,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:tumblestone",
|
||||||
|
"weight": 15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:raw_copper",
|
||||||
|
"weight": 12,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"southwold"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:raw_iron",
|
||||||
|
"weight": 10,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"westmarch"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:coal",
|
||||||
|
"weight": 12,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"button_isle"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:cobblestone",
|
||||||
|
"weight": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:gravel",
|
||||||
|
"weight": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:flint",
|
||||||
|
"weight": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:chest",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"rolls": 1,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:diamond",
|
||||||
|
"weight": 5,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"deep_dark"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:emerald",
|
||||||
|
"weight": 5,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"frostpeak"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:amethyst_shard",
|
||||||
|
"weight": 8,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"dripstone"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:helix_fossil",
|
||||||
|
"weight": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:dome_fossil",
|
||||||
|
"weight": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:old_amber_fossil",
|
||||||
|
"weight": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:root_fossil",
|
||||||
|
"weight": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:claw_fossil",
|
||||||
|
"weight": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:skull_fossil",
|
||||||
|
"weight": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:armor_fossil",
|
||||||
|
"weight": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:cover_fossil",
|
||||||
|
"weight": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:plume_fossil",
|
||||||
|
"weight": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:jaw_fossil",
|
||||||
|
"weight": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:sail_fossil",
|
||||||
|
"weight": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:dawn_stone",
|
||||||
|
"weight": 3,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"frostpeak"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:dusk_stone",
|
||||||
|
"weight": 3,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"deep_dark"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:fire_stone",
|
||||||
|
"weight": 3,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"sunscorch"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:ice_stone",
|
||||||
|
"weight": 3,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"frostpeak"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:leaf_stone",
|
||||||
|
"weight": 3,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"jungle"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:moon_stone",
|
||||||
|
"weight": 3,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"dripstone"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:shiny_stone",
|
||||||
|
"weight": 3,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"lush_caves"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:sun_stone",
|
||||||
|
"weight": 3,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"highlands"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:thunder_stone",
|
||||||
|
"weight": 3,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"westmarch"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:water_stone",
|
||||||
|
"weight": 3,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"mire"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:chest",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"rolls": 1,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:diamond",
|
||||||
|
"weight": 5,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"deep_dark"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:ancient_debris",
|
||||||
|
"weight": 2,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"basalt"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:netherite_scrap",
|
||||||
|
"weight": 1,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"basalt"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:chest",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"rolls": 1,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:black_tumblestone",
|
||||||
|
"weight": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "cobblemon:sky_tumblestone",
|
||||||
|
"weight": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:raw_gold",
|
||||||
|
"weight": 8,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"sunscorch"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:lapis_lazuli",
|
||||||
|
"weight": 8,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"highlands"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:redstone",
|
||||||
|
"weight": 8,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"jungle"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "minecraft:item",
|
||||||
|
"name": "minecraft:quartz",
|
||||||
|
"weight": 8,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"condition": "cobblesurvival:in_zone",
|
||||||
|
"zones": [
|
||||||
|
"basalt"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -42,6 +42,7 @@ Every tunable lives in a config file or in this table. **Placeholder** means gue
|
|||||||
| Power Block gate | none of its own: Poke Power's recipe takes 4 redstone blocks (Verdant Jungle ore), 4 iron blocks and an Electirizer | Placeholder | Poke Power's recipe | The Phase 4 guardian flag is gone (Adrian, 2026-10-31) |
|
| Power Block gate | none of its own: Poke Power's recipe takes 4 redstone blocks (Verdant Jungle ore), 4 iron blocks and an Electirizer | Placeholder | Poke Power's recipe | The Phase 4 guardian flag is gone (Adrian, 2026-10-31) |
|
||||||
| Soft gate levels and materials | a Pokémon passes a zone's gate at the top of its band (Button Isle 10, Southwold 18, Lush Caves 28, Westmarch 25, Jungle 30, Highlands 35, Mire 38, Dripstone 38, Sunscorch 45, Deep Sea 50, Frostpeak 55, Deep Dark 60, Basalt 70), or holding a material: coal; raw copper/copper ingot; shiny stone; raw iron/iron ingot/thunder stone; redstone/leaf stone; lapis/sun stone; water stone; amethyst shard/moon stone; raw gold/gold ingot/fire stone/Heat Rock; Mystic Water; emerald/ice stone/dawn stone; diamond/dusk stone; quartz/netherite scrap. A harder zone's material counts too | Placeholder | `progression_gates/base.json` → `zones` (an optional `level` overrides the band top) | Adrian, 2026-10-31: soft gates, no flags |
|
| Soft gate levels and materials | a Pokémon passes a zone's gate at the top of its band (Button Isle 10, Southwold 18, Lush Caves 28, Westmarch 25, Jungle 30, Highlands 35, Mire 38, Dripstone 38, Sunscorch 45, Deep Sea 50, Frostpeak 55, Deep Dark 60, Basalt 70), or holding a material: coal; raw copper/copper ingot; shiny stone; raw iron/iron ingot/thunder stone; redstone/leaf stone; lapis/sun stone; water stone; amethyst shard/moon stone; raw gold/gold ingot/fire stone/Heat Rock; Mystic Water; emerald/ice stone/dawn stone; diamond/dusk stone; quartz/netherite scrap. A harder zone's material counts too | Placeholder | `progression_gates/base.json` → `zones` (an optional `level` overrides the band top) | Adrian, 2026-10-31: soft gates, no flags |
|
||||||
| Gated field moves (Cobblemon HMs auras) | Flash: Dripstone; Dive, Mist: Mire; Rock Climb: Highlands; Magnet Rise: Sunscorch; Lucky Chant: Frostpeak; Lava Plume: Deep Dark | Placeholder | `progression_gates/base.json` → `field_moves` | Checked on the Pokémon giving the aura |
|
| Gated field moves (Cobblemon HMs auras) | Flash: Dripstone; Dive, Mist: Mire; Rock Climb: Highlands; Magnet Rise: Sunscorch; Lucky Chant: Frostpeak; Lava Plume: Deep Dark | Placeholder | `progression_gates/base.json` → `field_moves` | Checked on the Pokémon giving the aura |
|
||||||
|
| Base job loot (Cobblebase Mining, Ore Finder, Evolution-item Finder) | ores and evolution stones only from the pasture's zone (by the zone of their tier); Mining adds quartz (uncommon, weight 8) and the evolution stones (rare, weight 3) and drops the type gems; Cobblebase's other entries and weights unchanged | Placeholder | `tools/cobblebase_loot.py` → `datapacks/core/data/cobblebase/loot_table/` | Adrian, 2026-10-31. Rerun the tool after changing ore tiers in `world/zones.json` |
|
||||||
| Shoulder passives | Flying slow falling, Electric speed, Rock/Ground/Steel haste; gated: Fire warmth (Highlands), Ice frigidness (Jungle), Water water breathing (Mire), Ghost/Dark night vision (Dripstone); all level I | Placeholder | `datapacks/core/data/cobblesurvival/shoulder_passives/base.json` | Warmth and frigidness are Cold Sweat's effects |
|
| Shoulder passives | Flying slow falling, Electric speed, Rock/Ground/Steel haste; gated: Fire warmth (Highlands), Ice frigidness (Jungle), Water water breathing (Mire), Ghost/Dark night vision (Dripstone); all level I | Placeholder | `datapacks/core/data/cobblesurvival/shoulder_passives/base.json` | Warmth and frigidness are Cold Sweat's effects |
|
||||||
| Poke Power output | 50 FE/t per Pokémon level, block stores 120,000 FE, 16,000 FE/t out; cable 32,000 / 16,000 | The mod's defaults | `pack/config/poke_power/server.toml` | Nothing uses the energy before Phase 6 |
|
| Poke Power output | 50 FE/t per Pokémon level, block stores 120,000 FE, 16,000 FE/t out; cable 32,000 / 16,000 | The mod's defaults | `pack/config/poke_power/server.toml` | Nothing uses the energy before Phase 6 |
|
||||||
| Cobblebase jobs | the mod's defaults (finder cooldown 240 s, passive XP 5 % a minute with a Mentor, recruiter rates, buffs) | The mod's defaults | `pack/config/cobblebase.json` (skill cooldowns in its own skill data) | Tune after play-testing |
|
| Cobblebase jobs | the mod's defaults (finder cooldown 240 s, passive XP 5 % a minute with a Mentor, recruiter rates, buffs) | The mod's defaults | `pack/config/cobblebase.json` (skill cooldowns in its own skill data) | Tune after play-testing |
|
||||||
|
|||||||
@@ -12,9 +12,13 @@
|
|||||||
- **Rock Smash mines the island's ore:** the ore drops, and mined vein blocks turn into regenerators that grow back. Automated: `rock_smash_mines_veins`.
|
- **Rock Smash mines the island's ore:** the ore drops, and mined vein blocks turn into regenerators that grow back. Automated: `rock_smash_mines_veins`.
|
||||||
- **HMs' Trainer Ditto** (a visiting TM seller that is a wild Ditto underneath) neither hunts weak trainers nor despawns as a stray.
|
- **HMs' Trainer Ditto** (a visiting TM seller that is a wild Ditto underneath) neither hunts weak trainers nor despawns as a stray.
|
||||||
- **Soft gates** replace the guardian flags. A gate names a zone; the Pokémon doing the work passes it at the top of that zone's level band, or while holding one of its materials (or a harder zone's). Table in `docs/balance.md`.
|
- **Soft gates** replace the guardian flags. A gate names a zone; the Pokémon doing the work passes it at the top of that zone's level band, or while holding one of its materials (or a harder zone's). Table in `docs/balance.md`.
|
||||||
- Cobblebase skills: checked on the pastured Pokémon; its owner is told once what it needs. Automated: `cobblebase_skills_need_level_or_material`.
|
- Cobblebase skills: checked on the pastured Pokémon. Automated: `cobblebase_skills_need_level_or_material`.
|
||||||
- Power Block: no gate of its own, its recipe takes the Jungle's redstone blocks. Automated: `power_block_gated_by_its_recipe`.
|
- Power Block: no gate of its own, its recipe takes the Jungle's redstone blocks. Automated: `power_block_gated_by_its_recipe`.
|
||||||
- HMs auras that get around hazards (Flash, Dive, Mist, Rock Climb, Magnet Rise, Lucky Chant, Lava Plume): switched off, with a message, while the Pokémon giving them doesn't pass. Automated: `hazard_auras_need_level_or_material`.
|
- HMs auras that get around hazards (Flash, Dive, Mist, Rock Climb, Magnet Rise, Lucky Chant, Lava Plume): switched off, with a message, while the Pokémon giving them doesn't pass. Automated: `hazard_auras_need_level_or_material`.
|
||||||
|
- **From your Mining test (2026-10-31):**
|
||||||
|
- Mining drops its loot at the Pokémon's feet; an **Item Gatherer** in the same pasture carries it into the chest (Cobblebase's design). Without one, the loot despawns after 5 minutes.
|
||||||
|
- A gated job doesn't stick: assigned to a Pokémon that doesn't pass, it's taken back at once (gone from Buffs → Active) with a message saying what the Pokémon needs. Automated: `gated_jobs_are_taken_back`.
|
||||||
|
- Mining, the Ore Finder and the Evolution-item Finder only give the ores and evolution stones of the pasture's zone; Mining no longer gives type gems. Automated: `base_jobs_dig_their_zones_ores`.
|
||||||
- **Shoulder passives:** a shoulder Pokémon gives an effect by type (Flying slow falling, Electric speed, Rock/Ground/Steel haste; gated: Fire warmth, Ice frigidness, Water water breathing, Ghost/Dark night vision). Automated: `shoulder_pokemon_give_passives`.
|
- **Shoulder passives:** a shoulder Pokémon gives an effect by type (Flying slow falling, Electric speed, Rock/Ground/Steel haste; gated: Fire warmth, Ice frigidness, Water water breathing, Ghost/Dark night vision). Automated: `shoulder_pokemon_give_passives`.
|
||||||
|
|
||||||
## In game (Prism instance)
|
## In game (Prism instance)
|
||||||
@@ -22,12 +26,16 @@
|
|||||||
- [ ] Send out a Pokémon that knows Rock Smash, pick Rock Smash on its interaction wheel, and punch an ore node: the node breaks in one go, drops its ore and turns into a regenerator.
|
- [ ] Send out a Pokémon that knows Rock Smash, pick Rock Smash on its interaction wheel, and punch an ore node: the node breaks in one go, drops its ore and turns into a regenerator.
|
||||||
- [ ] Do the same on an underground vein that needs a better pickaxe than the one you hold (e.g. iron in Westmarch with no pickaxe): it still breaks.
|
- [ ] Do the same on an underground vein that needs a better pickaxe than the one you hold (e.g. iron in Westmarch with no pickaxe): it still breaks.
|
||||||
- [ ] Switch on Flash from a low-level Pokémon: it switches off again with a message saying what it needs. Give it an amethyst shard to hold (or a diamond), switch it on again: it stays on.
|
- [ ] Switch on Flash from a low-level Pokémon: it switches off again with a message saying what it needs. Give it an amethyst shard to hold (or a diamond), switch it on again: it stays on.
|
||||||
- [ ] Put a Pokémon below Lv 25 with the Mining skill in a pasture: you get a message that it needs Lv 25 or Westmarch ore. Give it an iron ingot to hold: it starts mining.
|
- [ ] Put a Pokémon below Lv 25 with the Mining skill in a pasture and assign it Mining: the job is taken back at once (Buffs → Active stays empty) with a message that it needs Lv 25 or Westmarch ore. Give it an iron ingot to hold and assign Mining again: it stays and mines.
|
||||||
|
- [ ] Add a Pokémon working as Item Gatherer to the same pasture, with a chest within a few blocks: Mining's loot ends up in the chest.
|
||||||
|
- [ ] With a pasture on Button Isle, Mining brings coal, tumblestones, cobblestone, gravel, flint and fossils, but no iron, copper, gold, diamonds or gems. In Westmarch it brings raw iron and thunder stones instead of coal.
|
||||||
- [ ] Craft and place a Power Block in survival: no guardian needed.
|
- [ ] Craft and place a Power Block in survival: no guardian needed.
|
||||||
- [ ] Put Pidgey (or another Flying type) on your shoulder: slow falling. A low-level Fire type on your shoulder: a message about warmth; with a sun stone or lapis held, Cold Sweat's warmth shows.
|
- [ ] Put Pidgey (or another Flying type) on your shoulder: slow falling. A low-level Fire type on your shoulder: a message about warmth; with a sun stone or lapis held, Cold Sweat's warmth shows.
|
||||||
- [ ] Look at the other abilities your Pokémon offer on the wheel (Cut, Strength, Surf boosts, ...) and note any you want switched off or tuned.
|
- [ ] Look at the other abilities your Pokémon offer on the wheel (Cut, Strength, Surf boosts, ...) and note any you want switched off or tuned.
|
||||||
|
|
||||||
## Ask Adrian
|
## Ask Adrian
|
||||||
|
|
||||||
|
- Gated jobs are taken back but their buttons still show in Cobblebase's Skills tab. Hiding them per Pokémon is possible (the screen knows each Pokémon's level and held item) but needs a client-side patch of Cobblebase's screen, which can break when Cobblebase updates. Want it?
|
||||||
|
- Mining should deliver straight into the chest instead of relying on an Item Gatherer?
|
||||||
- The gate levels, materials and which aura sits behind which zone are placeholders (`docs/balance.md`). Dive, Mist and Rock Climb were added to your four because they get around hazards the same way; say if any should stay open.
|
- The gate levels, materials and which aura sits behind which zone are placeholders (`docs/balance.md`). Dive, Mist and Rock Climb were added to your four because they get around hazards the same way; say if any should stay open.
|
||||||
- Phase 5's roadmap done-when asked for fire with `doFireTick`; there's no fire move any more (furnaces are Phase 6). Approve Phase 5 as it is?
|
- Phase 5's roadmap done-when asked for fire with `doFireTick`; there's no fire move any more (furnaces are Phase 6). Approve Phase 5 as it is?
|
||||||
@@ -196,6 +196,14 @@ Adrian's answers (2026-10-31): HMs' abilities stay open; the ones that get aroun
|
|||||||
|
|
||||||
Self-tests: `gates_cover_every_cobblebase_skill`, `cobblebase_skills_need_level_or_material`, `power_block_gated_by_its_recipe`, `hazard_auras_need_level_or_material`, `shoulder_pokemon_give_passives`.
|
Self-tests: `gates_cover_every_cobblebase_skill`, `cobblebase_skills_need_level_or_material`, `power_block_gated_by_its_recipe`, `hazard_auras_need_level_or_material`, `shoulder_pokemon_give_passives`.
|
||||||
|
|
||||||
|
**Cobblebase Mining, from Adrian's play-test (2026-10-31):**
|
||||||
|
|
||||||
|
- *Loot on the ground.* Cobblebase's Mining never fills a chest: it drops what it digs at the Pokémon's feet, tagged with its pasture. A second Pokémon in the same pasture working as **Item Gatherer** (open from the start) picks up items tagged with its pasture and puts them into the best container within 12 blocks of it. Untouched, the loot despawns after 5 minutes like any dropped item. Left as Cobblebase designed it; direct delivery would be a change in the companion mod.
|
||||||
|
- *Gated jobs don't stick.* Cobblebase lets the player assign one job per pastured Pokémon in its Skills tab, and the Buffs tab's "Active" list shows the assigned jobs. A job the Pokémon doesn't pass the gate for is now taken back the moment Cobblebase ticks it: the assignment is cleared, every client gets Cobblebase's assignment sync (so it leaves the Active list), and the owner is told what the Pokémon needs (`progress/CobblebaseGate.kt`). The job's button still shows in the Skills tab. Hiding it per Pokémon would need the gate tables sent to clients and a client mixin into Cobblebase's `SkillsPanel` (the screen already knows each pastured Pokémon's level and held item); not built, offered to Adrian.
|
||||||
|
- *Ores from the pasture's zone only* (Adrian: only the items of the zone the pasture is in). Mining, the Ore Finder and the Evolution-item Finder roll Cobblebase's tables `mining_*`, `finder_ore_*` and `finder_evo_*`. Cobblebase rolls its own built-in copy of a table (without loot conditions) and only falls back to the data pack's table when it has none, so the companion mod removes its copies of those twelve tables at server start (`ore/BaseLoot.kt`) and the pack ships its own versions (`datapacks/core/data/cobblebase/loot_table/`, written by `tools/cobblebase_loot.py` from Cobblebase's): every ore and evolution stone carries the loot condition `cobblesurvival:in_zone` (`zone/InZoneCondition.kt`) naming the zone of its tier in `world/zones.json`, checked at the Pokémon's position. Mining also gets the zone ores Cobblebase leaves out (quartz, the evolution stones) and loses the type gems, which are crafting shortcuts to gate materials (a Fire Gem makes a Heat Rock); ruins archaeology still has the gems. Cobblestone, gravel, flint, tumblestones and fossils stay everywhere. A table an operator edits in Cobblebase's own loot editor still wins.
|
||||||
|
|
||||||
|
Self-tests: `gated_jobs_are_taken_back`, `base_jobs_dig_their_zones_ores`.
|
||||||
|
|
||||||
## Notes for Phase 6 (researched 2026-10-31)
|
## Notes for Phase 6 (researched 2026-10-31)
|
||||||
|
|
||||||
Adrian asked for existing mods for the multiblocks, or at least their logic. Phase 6's machines need a pattern-checked structure with a controller, a Pokémon stored inside (keeping its data, visible), safe return when the structure breaks, and state that survives unload and restart. Power is already Poke Power's (Phase 4), so the first machine is the Large Furnace. Nothing on NeoForge 1.21.1 puts a Pokémon into a multiblock; the structure part has several options:
|
Adrian asked for existing mods for the multiblocks, or at least their logic. Phase 6's machines need a pattern-checked structure with a controller, a Pokémon stored inside (keeping its data, visible), safe return when the structure breaks, and state that survives unload and restart. Power is already Poke Power's (Phase 4), so the first machine is the Large Furnace. Nothing on NeoForge 1.21.1 puts a Pokémon into a multiblock; the structure part has several options:
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ Licenses of mods planned for later phases are in `docs/addon-status.md` and move
|
|||||||
| `pack/config/worldgen_editor/presets/cobblesurvival_island.json` | Field format of WorldGen Editor presets; our own values | CC BY-NC-SA 4.0 to be safe |
|
| `pack/config/worldgen_editor/presets/cobblesurvival_island.json` | Field format of WorldGen Editor presets; our own values | CC BY-NC-SA 4.0 to be safe |
|
||||||
| `datapacks/core/data/cobblemon/worldgen/placed_feature/ore/*.json` | Cobblemon's placed features (feature id kept, placement replaced) | MPL-2.0, like Cobblemon |
|
| `datapacks/core/data/cobblemon/worldgen/placed_feature/ore/*.json` | Cobblemon's placed features (feature id kept, placement replaced) | MPL-2.0, like Cobblemon |
|
||||||
| `datapacks/core/data/cobblemon/worldgen/processor_list/**`, `.../template_pool/**` | Cobblemon's processor lists with an ore-stripping rule appended, and Gimmighoul tower pools without their mob pieces (`tools/clean_structures.py`) | MPL-2.0, like Cobblemon |
|
| `datapacks/core/data/cobblemon/worldgen/processor_list/**`, `.../template_pool/**` | Cobblemon's processor lists with an ore-stripping rule appended, and Gimmighoul tower pools without their mob pieces (`tools/clean_structures.py`) | MPL-2.0, like Cobblemon |
|
||||||
|
| `datapacks/core/data/cobblebase/loot_table/*.json` | Cobblebase's mining, Ore Finder and Evolution-item Finder loot tables, with zone conditions added and type gems removed (`tools/cobblebase_loot.py`) | MPL-2.0, like Cobblebase |
|
||||||
| `datapacks/core/data/cobblesurvival/spawn_pool_world/*.json`, `docs/zone-species.md` | Cobblemon's spawn pool entries, regrouped by zone with levels clamped (`tools/zone_spawns.py`) | MPL-2.0, like Cobblemon |
|
| `datapacks/core/data/cobblesurvival/spawn_pool_world/*.json`, `docs/zone-species.md` | Cobblemon's spawn pool entries, regrouped by zone with levels clamped (`tools/zone_spawns.py`) | MPL-2.0, like Cobblemon |
|
||||||
| `datapacks/core/data/minecraft/worldgen/processor_list/fossil_*.json`, `datapacks/core/data/minecraft/tags/worldgen/biome/has_structure/*.json` | Vanilla fossil processor lists (ore rule appended) and structure biome tags (basalt deltas removed) | Mojang's data, adapted as data packs commonly do |
|
| `datapacks/core/data/minecraft/worldgen/processor_list/fossil_*.json`, `datapacks/core/data/minecraft/tags/worldgen/biome/has_structure/*.json` | Vanilla fossil processor lists (ore rule appended) and structure biome tags (basalt deltas removed) | Mojang's data, adapted as data packs commonly do |
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.mojang.logging.LogUtils
|
|||||||
import dev.cobblesurvival.catching.Catching
|
import dev.cobblesurvival.catching.Catching
|
||||||
import dev.cobblesurvival.dev.BiomePreview
|
import dev.cobblesurvival.dev.BiomePreview
|
||||||
import dev.cobblesurvival.dev.OreVeins
|
import dev.cobblesurvival.dev.OreVeins
|
||||||
|
import dev.cobblesurvival.ore.BaseLoot
|
||||||
import dev.cobblesurvival.ore.OreNodes
|
import dev.cobblesurvival.ore.OreNodes
|
||||||
import dev.cobblesurvival.permadeath.Permadeath
|
import dev.cobblesurvival.permadeath.Permadeath
|
||||||
import dev.cobblesurvival.party.ShoulderPassives
|
import dev.cobblesurvival.party.ShoulderPassives
|
||||||
@@ -14,6 +15,7 @@ import dev.cobblesurvival.selftest.SelfTest
|
|||||||
import dev.cobblesurvival.world.OnlyPokemon
|
import dev.cobblesurvival.world.OnlyPokemon
|
||||||
import dev.cobblesurvival.world.SharedLoot
|
import dev.cobblesurvival.world.SharedLoot
|
||||||
import dev.cobblesurvival.zone.Guardians
|
import dev.cobblesurvival.zone.Guardians
|
||||||
|
import dev.cobblesurvival.zone.InZoneCondition
|
||||||
import dev.cobblesurvival.zone.LevelPressure
|
import dev.cobblesurvival.zone.LevelPressure
|
||||||
import dev.cobblesurvival.zone.Wanderers
|
import dev.cobblesurvival.zone.Wanderers
|
||||||
import dev.cobblesurvival.zone.ZoneCondition
|
import dev.cobblesurvival.zone.ZoneCondition
|
||||||
@@ -57,6 +59,7 @@ class CobbleSurvival {
|
|||||||
init {
|
init {
|
||||||
LOGGER.info("Cobblemon Survival companion {} loaded (Cobblemon {})", version, Cobblemon.VERSION)
|
LOGGER.info("Cobblemon Survival companion {} loaded (Cobblemon {})", version, Cobblemon.VERSION)
|
||||||
MOD_BUS.addListener(::onAddPackFinders)
|
MOD_BUS.addListener(::onAddPackFinders)
|
||||||
|
InZoneCondition.register(MOD_BUS)
|
||||||
NeoForge.EVENT_BUS.addListener(::onRegisterCommands)
|
NeoForge.EVENT_BUS.addListener(::onRegisterCommands)
|
||||||
NeoForge.EVENT_BUS.addListener(::onServerStarted)
|
NeoForge.EVENT_BUS.addListener(::onServerStarted)
|
||||||
NeoForge.EVENT_BUS.addListener(::onPortalSpawn)
|
NeoForge.EVENT_BUS.addListener(::onPortalSpawn)
|
||||||
@@ -70,6 +73,7 @@ class CobbleSurvival {
|
|||||||
Gates.register()
|
Gates.register()
|
||||||
ShoulderPassives.register()
|
ShoulderPassives.register()
|
||||||
OreNodes.register()
|
OreNodes.register()
|
||||||
|
BaseLoot.register()
|
||||||
Catching.register()
|
Catching.register()
|
||||||
Wanderers.register()
|
Wanderers.register()
|
||||||
SharedLoot.register()
|
SharedLoot.register()
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package dev.cobblesurvival.ore
|
||||||
|
|
||||||
|
import dev.cobblesurvival.CobbleSurvival
|
||||||
|
import dev.cobblesurvival.progress.CobblebaseGate
|
||||||
|
import net.minecraft.core.BlockPos
|
||||||
|
import net.minecraft.server.level.ServerLevel
|
||||||
|
import net.minecraft.world.entity.Entity
|
||||||
|
import net.minecraft.world.item.ItemStack
|
||||||
|
import net.neoforged.neoforge.common.NeoForge
|
||||||
|
import net.neoforged.neoforge.event.server.ServerAboutToStartEvent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ores from Cobblebase's base jobs come from the pasture's zone only (Adrian, 2026-10-31). Mining, the Ore Finder and
|
||||||
|
* the Evolution-item Finder roll Cobblebase's tables `cobblebase:mining_*`, `finder_ore_*` and `finder_evo_*`.
|
||||||
|
* Cobblebase rolls its own built-in copy of a table, which knows no loot conditions, and only falls back to the data
|
||||||
|
* pack's loot table when it has none; so this takes its copies of those tables out when the server starts, and the
|
||||||
|
* pack's versions (`datapacks/core/data/cobblebase/loot_table/`, written by tools/cobblebase_loot.py with
|
||||||
|
* [dev.cobblesurvival.zone.InZoneCondition] on every ore and evolution stone) are rolled instead. A table an operator
|
||||||
|
* edits in Cobblebase's own loot editor still wins over both.
|
||||||
|
*/
|
||||||
|
object BaseLoot {
|
||||||
|
val FAMILIES = listOf("cobblebase:mining_", "cobblebase:finder_ore_", "cobblebase:finder_evo_")
|
||||||
|
|
||||||
|
private val registry by lazy { Class.forName("notlown.cobblebase.core.CobblebaseLootRegistry") }
|
||||||
|
|
||||||
|
fun register() {
|
||||||
|
NeoForge.EVENT_BUS.addListener<ServerAboutToStartEvent> { install() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun builtInTables(): MutableMap<String, Any> =
|
||||||
|
registry.getDeclaredField("tables").apply { isAccessible = true }.get(null) as MutableMap<String, Any>
|
||||||
|
|
||||||
|
fun install() {
|
||||||
|
if (!CobblebaseGate.installed) return
|
||||||
|
val tables = builtInTables()
|
||||||
|
val ours = tables.keys.filter { id -> FAMILIES.any(id::startsWith) }
|
||||||
|
ours.forEach(tables::remove)
|
||||||
|
CobbleSurvival.LOGGER.info("Cobblebase rolls the pack's zone-bound loot tables for {}", ours.sorted())
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The tables of [FAMILIES] that Cobblebase would still roll from its own copy. */
|
||||||
|
fun builtInLeft(): List<String> = builtInTables().keys.filter { id -> FAMILIES.any(id::startsWith) }
|
||||||
|
|
||||||
|
/** Rolls [table] the way Cobblebase's jobs do, for a Pokémon standing at [pos]. */
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun roll(table: String, level: ServerLevel, pos: BlockPos): List<ItemStack> {
|
||||||
|
val helper = Class.forName("notlown.cobblebase.core.LootHelper")
|
||||||
|
return helper.getMethod("generateLoot", String::class.java, ServerLevel::class.java, BlockPos::class.java, Entity::class.java)
|
||||||
|
.invoke(helper.getField("INSTANCE").get(null), table, level, pos, null) as List<ItemStack>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,17 +2,25 @@ package dev.cobblesurvival.progress
|
|||||||
|
|
||||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||||
import dev.cobblesurvival.CobbleSurvival
|
import dev.cobblesurvival.CobbleSurvival
|
||||||
|
import net.minecraft.network.protocol.common.custom.CustomPacketPayload
|
||||||
|
import net.minecraft.server.MinecraftServer
|
||||||
import net.neoforged.fml.ModList
|
import net.neoforged.fml.ModList
|
||||||
|
import net.neoforged.neoforge.network.PacketDistributor
|
||||||
import java.lang.reflect.InvocationHandler
|
import java.lang.reflect.InvocationHandler
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.lang.reflect.Proxy
|
import java.lang.reflect.Proxy
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Puts [Gates] in front of Cobblebase's skills, by reflection (Cobblebase is not a compile dependency): every skill
|
* Puts [Gates] in front of Cobblebase's skills, by reflection (Cobblebase is not a compile dependency): every skill
|
||||||
* executor in Cobblebase's `ExecutorRegistry` is replaced by a proxy that runs it only when the pastured Pokémon passes
|
* executor in Cobblebase's `ExecutorRegistry` is replaced by a proxy that runs it only when the pastured Pokémon passes
|
||||||
* that skill's soft gate (level or held material); otherwise its owner is told what it needs. Cobblebase looks the executor up on every tick, so the proxies take effect at once. One
|
* that skill's soft gate (level or held material). Cobblebase looks the executor up on every tick, so the proxies take
|
||||||
* executor can serve several skills (all the finders), so the check is per skill, not per executor.
|
* effect at once. One executor can serve several skills (all the finders), so the check is per skill, not per executor.
|
||||||
|
*
|
||||||
|
* A job the Pokémon is assigned to but doesn't pass the gate for is taken back at once (Adrian, 2026-10-31: a gated job
|
||||||
|
* shouldn't stick, nor show among the base's active jobs), and the owner is told what the Pokémon needs. A gated passive
|
||||||
|
* buff just stays off; its owner hears about it once ([Gates.noticeIdle]).
|
||||||
*/
|
*/
|
||||||
object CobblebaseGate {
|
object CobblebaseGate {
|
||||||
private const val CORE = "notlown.cobblebase.core"
|
private const val CORE = "notlown.cobblebase.core"
|
||||||
@@ -25,6 +33,7 @@ object CobblebaseGate {
|
|||||||
private val skillRegistry by lazy { Class.forName("$CORE.SkillRegistry").let { it to it.getField("INSTANCE").get(null) } }
|
private val skillRegistry by lazy { Class.forName("$CORE.SkillRegistry").let { it to it.getField("INSTANCE").get(null) } }
|
||||||
internal val skillDef by lazy { Class.forName("$CORE.SkillDef") }
|
internal val skillDef by lazy { Class.forName("$CORE.SkillDef") }
|
||||||
private val skillId by lazy { skillDef.getMethod("getId") }
|
private val skillId by lazy { skillDef.getMethod("getId") }
|
||||||
|
private val baseManager by lazy { Class.forName("$CORE.BaseManager").let { it to it.getField("INSTANCE").get(null) } }
|
||||||
|
|
||||||
/** Skill id → its definition (Cobblebase's SkillDef). */
|
/** Skill id → its definition (Cobblebase's SkillDef). */
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@@ -58,6 +67,39 @@ object CobblebaseGate {
|
|||||||
CobbleSurvival.LOGGER.info("Progression gates in front of {} Cobblebase skill executors", wrapped)
|
CobbleSurvival.LOGGER.info("Progression gates in front of {} Cobblebase skill executors", wrapped)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The job Cobblebase has the pastured Pokémon [pokemon] assigned to, or null. */
|
||||||
|
fun assignment(pokemon: UUID): String? {
|
||||||
|
val (type, instance) = baseManager
|
||||||
|
return type.getMethod("getAssignment", UUID::class.java).invoke(instance, pokemon) as String?
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assign(pokemon: UUID, skill: String?) {
|
||||||
|
val (type, instance) = baseManager
|
||||||
|
type.getMethod("assignSkill", UUID::class.java, String::class.java).invoke(instance, pokemon, skill)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Takes the job [skill] back from a pastured Pokémon that doesn't pass its gate and tells its owner why. */
|
||||||
|
fun takeBack(entity: PokemonEntity, skill: String) {
|
||||||
|
assign(entity.pokemon.uuid, null)
|
||||||
|
entity.server?.let(::syncAssignments)
|
||||||
|
entity.pokemon.getOwnerPlayer()?.sendSystemMessage(Gates.jobRefused(entity.pokemon, skill))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sends every player Cobblebase's assignments, as Cobblebase does after a change, so its screens show the new state. */
|
||||||
|
private fun syncAssignments(server: MinecraftServer) {
|
||||||
|
val (type, instance) = baseManager
|
||||||
|
val all = type.getMethod("getAllAssignments").invoke(instance) as Map<*, *>
|
||||||
|
val packet = Class.forName("$CORE.net.SkillAssignmentSyncS2CPacket").getConstructor(Map::class.java)
|
||||||
|
.newInstance(all) as CustomPacketPayload
|
||||||
|
for (player in server.playerList.players) {
|
||||||
|
try {
|
||||||
|
PacketDistributor.sendToPlayer(player, packet)
|
||||||
|
} catch (e: RuntimeException) {
|
||||||
|
CobbleSurvival.LOGGER.debug("No Cobblebase assignment sync for {}: {}", player.gameProfile.name, e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun isGated(executor: Any): Boolean =
|
fun isGated(executor: Any): Boolean =
|
||||||
Proxy.isProxyClass(executor.javaClass) && Proxy.getInvocationHandler(executor) is Gate
|
Proxy.isProxyClass(executor.javaClass) && Proxy.getInvocationHandler(executor) is Gate
|
||||||
|
|
||||||
@@ -70,7 +112,7 @@ object CobblebaseGate {
|
|||||||
val entity = args[2] as PokemonEntity
|
val entity = args[2] as PokemonEntity
|
||||||
val skill = skillId.invoke(args[3]) as String
|
val skill = skillId.invoke(args[3]) as String
|
||||||
if (!Gates.skillOpen(entity.pokemon, skill)) {
|
if (!Gates.skillOpen(entity.pokemon, skill)) {
|
||||||
Gates.noticeIdle(entity, skill)
|
if (assignment(entity.pokemon.uuid) == skill) takeBack(entity, skill) else Gates.noticeIdle(entity, skill)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,6 +112,12 @@ object Gates : SimpleJsonResourceReloadListener(Gson(), "progression_gates") {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** "Geodude can't take the Mining job yet. It needs Lv 25, or to hold ...". */
|
||||||
|
fun jobRefused(pokemon: Pokemon, skill: String): Component = Component.translatableWithFallback(
|
||||||
|
"message.cobblesurvival.gate.job", "%s can't take the %s job yet. It needs %s.",
|
||||||
|
pokemon.getDisplayName(), CobblebaseGate.skillName(skill) ?: skill, requirement(skills[skill] ?: ""),
|
||||||
|
).withStyle(ChatFormatting.YELLOW)
|
||||||
|
|
||||||
/** Tells a pastured Pokémon's owner, once, why it doesn't do [skill]; only when the owner is near. */
|
/** Tells a pastured Pokémon's owner, once, why it doesn't do [skill]; only when the owner is near. */
|
||||||
fun noticeIdle(entity: PokemonEntity, skill: String) {
|
fun noticeIdle(entity: PokemonEntity, skill: String) {
|
||||||
val pokemon = entity.pokemon
|
val pokemon = entity.pokemon
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package dev.cobblesurvival.selftest
|
||||||
|
|
||||||
|
import dev.cobblesurvival.ore.BaseLoot
|
||||||
|
import dev.cobblesurvival.zone.InZoneCondition
|
||||||
|
import dev.cobblesurvival.zone.Zones
|
||||||
|
import net.minecraft.core.BlockPos
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries
|
||||||
|
import net.minecraft.gametest.framework.GameTestAssertException
|
||||||
|
import net.minecraft.world.level.storage.loot.LootContext
|
||||||
|
import net.minecraft.world.level.storage.loot.LootParams
|
||||||
|
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets
|
||||||
|
import net.minecraft.world.level.storage.loot.parameters.LootContextParams
|
||||||
|
import net.minecraft.world.phys.Vec3
|
||||||
|
import java.util.Optional
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cobblebase's base jobs only dig up the pasture zone's ores (Adrian, 2026-10-31): Cobblebase's built-in copies of the
|
||||||
|
* mining and finder tables are gone, `cobblesurvival:in_zone` holds only inside its zones, and Mining's common table
|
||||||
|
* gives Button Isle coal but no iron and Westmarch iron but no coal (world/zones.json: coal is Button Isle's only ore,
|
||||||
|
* raw iron comes from Westmarch).
|
||||||
|
*/
|
||||||
|
object BaseLootTests {
|
||||||
|
private const val ROLLS = 200
|
||||||
|
|
||||||
|
fun all() = listOf(SelfTestCase("base_jobs_dig_their_zones_ores", 40, ::baseJobsDigTheirZonesOres))
|
||||||
|
|
||||||
|
/** A spot in [zone], high above the ground so no cave zone lies there. */
|
||||||
|
private fun spotIn(s: Scenario, zone: String): BlockPos {
|
||||||
|
val sample = Zones.samples[zone] ?: throw GameTestAssertException("no sample point for $zone")
|
||||||
|
val pos = BlockPos(sample.x, 250, sample.z)
|
||||||
|
s.check(Zones.at(s.level, pos)?.id == zone) { "the sample point of $zone lies in ${Zones.at(s.level, pos)?.id}" }
|
||||||
|
return pos
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun baseJobsDigTheirZonesOres(s: Scenario) {
|
||||||
|
val level = s.level
|
||||||
|
s.check(BaseLoot.builtInLeft().isEmpty()) { "Cobblebase still rolls its own copies of ${BaseLoot.builtInLeft()}" }
|
||||||
|
val isle = spotIn(s, "button_isle")
|
||||||
|
val westmarch = spotIn(s, "westmarch")
|
||||||
|
|
||||||
|
val params = LootParams.Builder(level).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(isle)).create(LootContextParamSets.CHEST)
|
||||||
|
val context = LootContext.Builder(params).create(Optional.empty())
|
||||||
|
s.check(InZoneCondition(listOf("button_isle")).test(context)) { "in_zone [button_isle] is false on Button Isle" }
|
||||||
|
s.check(!InZoneCondition(listOf("westmarch")).test(context)) { "in_zone [westmarch] is true on Button Isle" }
|
||||||
|
|
||||||
|
fun dug(pos: BlockPos, table: String) = (1..ROLLS).flatMap { BaseLoot.roll(table, level, pos) }
|
||||||
|
.map { BuiltInRegistries.ITEM.getKey(it.item).path }.toSet()
|
||||||
|
val onIsle = dug(isle, "cobblebase:mining_common")
|
||||||
|
s.check("coal" in onIsle && "raw_iron" !in onIsle && "raw_copper" !in onIsle) { "Mining on Button Isle dug up $onIsle" }
|
||||||
|
val inWestmarch = dug(westmarch, "cobblebase:mining_common")
|
||||||
|
s.check("raw_iron" in inWestmarch && "coal" !in inWestmarch) { "Mining in Westmarch dug up $inWestmarch" }
|
||||||
|
val gems = (dug(isle, "cobblebase:mining_uncommon") + dug(isle, "cobblebase:mining_ultra_rare")).filter { it.endsWith("_gem") }
|
||||||
|
s.check(gems.isEmpty()) { "Mining still digs up type gems: $gems" }
|
||||||
|
val stones = dug(isle, "cobblebase:finder_evo_common")
|
||||||
|
s.check(stones.isEmpty()) { "the Evolution-item Finder found $stones on Button Isle, which has no evolution stones" }
|
||||||
|
s.helper.succeed()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ import java.lang.reflect.Proxy
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Soft gates (Adrian, 2026-10-31): Cobblebase's base skills open up through the pastured Pokémon's level or a material it
|
* Soft gates (Adrian, 2026-10-31): Cobblebase's base skills open up through the pastured Pokémon's level or a material it
|
||||||
* holds, never through flags (`data/cobblesurvival/progression_gates/`). Poke Power's Power Block has no gate of its
|
* holds, never through flags (`data/cobblesurvival/progression_gates/`); a gated job doesn't stay assigned. Poke Power's Power Block has no gate of its
|
||||||
* own: its recipe already takes the Verdant Jungle's redstone.
|
* own: its recipe already takes the Verdant Jungle's redstone.
|
||||||
*/
|
*/
|
||||||
object ProgressionTests {
|
object ProgressionTests {
|
||||||
@@ -29,6 +29,7 @@ object ProgressionTests {
|
|||||||
fun all() = listOf(
|
fun all() = listOf(
|
||||||
SelfTestCase("gates_cover_every_cobblebase_skill", 20, ::gatesCoverEverySkill),
|
SelfTestCase("gates_cover_every_cobblebase_skill", 20, ::gatesCoverEverySkill),
|
||||||
SelfTestCase("cobblebase_skills_need_level_or_material", 20, ::skillsNeedLevelOrMaterial),
|
SelfTestCase("cobblebase_skills_need_level_or_material", 20, ::skillsNeedLevelOrMaterial),
|
||||||
|
SelfTestCase("gated_jobs_are_taken_back", 20, ::gatedJobsAreTakenBack),
|
||||||
SelfTestCase("power_block_gated_by_its_recipe", 20, ::powerBlockGatedByRecipe),
|
SelfTestCase("power_block_gated_by_its_recipe", 20, ::powerBlockGatedByRecipe),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,6 +51,26 @@ object ProgressionTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** A probe behind the same gate as Cobblebase's executors, called the way Cobblebase calls them. */
|
/** A probe behind the same gate as Cobblebase's executors, called the way Cobblebase calls them. */
|
||||||
|
private class Probe(private val s: Scenario, private val entity: PokemonEntity) {
|
||||||
|
private var calls = 0
|
||||||
|
private val type = CobblebaseGate.executorType
|
||||||
|
private val wrapped = CobblebaseGate.wrap(
|
||||||
|
Proxy.newProxyInstance(type.classLoader, arrayOf(type)) { _, method, _ -> if (method.name == "tick") calls++; null }
|
||||||
|
)
|
||||||
|
private val tick = type.getMethod("tick", Level::class.java, BlockPos::class.java, PokemonEntity::class.java,
|
||||||
|
CobblebaseGate.skillDef, CobblebaseGate.skillEntry)
|
||||||
|
private val entry = CobblebaseGate.skillEntry.getConstructor(String::class.java, Int::class.javaPrimitiveType)
|
||||||
|
|
||||||
|
/** Ticks [skill] for the Pokémon, holding [holding]; whether the skill ran. */
|
||||||
|
fun runs(skill: String, holding: Item? = null): Boolean {
|
||||||
|
entity.pokemon.swapHeldItem(holding?.let(::ItemStack) ?: ItemStack.EMPTY, false)
|
||||||
|
val def = CobblebaseGate.skills()[skill] ?: throw GameTestAssertException("Cobblebase has no skill $skill")
|
||||||
|
val before = calls
|
||||||
|
tick.invoke(wrapped, s.level, entity.blockPosition(), entity, def, entry.newInstance(skill, 1))
|
||||||
|
return calls > before
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun skillsNeedLevelOrMaterial(s: Scenario) {
|
private fun skillsNeedLevelOrMaterial(s: Scenario) {
|
||||||
val level = s.level
|
val level = s.level
|
||||||
val player = s.player()
|
val player = s.player()
|
||||||
@@ -62,32 +83,37 @@ object ProgressionTests {
|
|||||||
val easier = Gates.zones.values.filter { it.level < gate.level && it.items.isNotEmpty() }.minByOrNull { it.level }
|
val easier = Gates.zones.values.filter { it.level < gate.level && it.items.isNotEmpty() }.minByOrNull { it.level }
|
||||||
val harder = Gates.zones.values.filter { it.level > gate.level && it.items.isNotEmpty() }.maxByOrNull { it.level }
|
val harder = Gates.zones.values.filter { it.level > gate.level && it.items.isNotEmpty() }.maxByOrNull { it.level }
|
||||||
|
|
||||||
var calls = 0
|
val probe = Probe(s, entity)
|
||||||
val type = CobblebaseGate.executorType
|
|
||||||
val probe = Proxy.newProxyInstance(type.classLoader, arrayOf(type)) { _, method, _ -> if (method.name == "tick") calls++; null }
|
|
||||||
val wrapped = CobblebaseGate.wrap(probe)
|
|
||||||
val tick = type.getMethod("tick", Level::class.java, BlockPos::class.java, PokemonEntity::class.java,
|
|
||||||
CobblebaseGate.skillDef, CobblebaseGate.skillEntry)
|
|
||||||
val entry = CobblebaseGate.skillEntry.getConstructor(String::class.java, Int::class.javaPrimitiveType)
|
|
||||||
fun runs(skill: String, holding: Item? = null): Boolean {
|
|
||||||
pokemon.swapHeldItem(holding?.let(::ItemStack) ?: ItemStack.EMPTY, false)
|
|
||||||
val def = CobblebaseGate.skills()[skill] ?: throw GameTestAssertException("Cobblebase has no skill $skill")
|
|
||||||
val before = calls
|
|
||||||
tick.invoke(wrapped, level, entity.blockPosition(), entity, def, entry.newInstance(skill, 1))
|
|
||||||
return calls > before
|
|
||||||
}
|
|
||||||
|
|
||||||
s.check(runs(openSkill)) { "$openSkill is open from the start but didn't run" }
|
s.check(probe.runs(openSkill)) { "$openSkill is open from the start but didn't run" }
|
||||||
s.check(!runs(lockedSkill)) { "$lockedSkill (gate: ${gate.zone}) ran for a Lv 1 Pokémon holding nothing" }
|
s.check(!probe.runs(lockedSkill)) { "$lockedSkill (gate: ${gate.zone}) ran for a Lv 1 Pokémon holding nothing" }
|
||||||
s.check(runs(lockedSkill, gate.items.first())) { "$lockedSkill didn't run for a Pokémon holding ${gate.items.first()} (${gate.zone})" }
|
s.check(probe.runs(lockedSkill, gate.items.first())) { "$lockedSkill didn't run for a Pokémon holding ${gate.items.first()} (${gate.zone})" }
|
||||||
if (harder != null) {
|
if (harder != null) {
|
||||||
s.check(runs(lockedSkill, harder.items.first())) { "$lockedSkill didn't run with ${harder.items.first()} from the harder ${harder.zone}" }
|
s.check(probe.runs(lockedSkill, harder.items.first())) { "$lockedSkill didn't run with ${harder.items.first()} from the harder ${harder.zone}" }
|
||||||
}
|
}
|
||||||
if (easier != null) {
|
if (easier != null) {
|
||||||
s.check(!runs(lockedSkill, easier.items.first())) { "$lockedSkill ran with ${easier.items.first()} from the easier ${easier.zone}" }
|
s.check(!probe.runs(lockedSkill, easier.items.first())) { "$lockedSkill ran with ${easier.items.first()} from the easier ${easier.zone}" }
|
||||||
}
|
}
|
||||||
pokemon.level = gate.level
|
pokemon.level = gate.level
|
||||||
s.check(runs(lockedSkill)) { "$lockedSkill didn't run for a Lv ${gate.level} Pokémon (${gate.zone})" }
|
s.check(probe.runs(lockedSkill)) { "$lockedSkill didn't run for a Lv ${gate.level} Pokémon (${gate.zone})" }
|
||||||
|
s.helper.succeed()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A gated job assigned to a Pokémon that doesn't pass is taken back at once; one it passes for stays. */
|
||||||
|
private fun gatedJobsAreTakenBack(s: Scenario) {
|
||||||
|
val player = s.player()
|
||||||
|
val pokemon = s.give(player, s.pokemon("pikachu", 1))
|
||||||
|
val probe = Probe(s, PokemonEntity(s.level, pokemon))
|
||||||
|
val (job, zone) = Gates.skills.entries.firstOrNull { it.value != null } ?: throw GameTestAssertException("no skill is gated")
|
||||||
|
val material = Gates.zones.getValue(zone!!).items.first()
|
||||||
|
s.afterTest { CobblebaseGate.assign(pokemon.uuid, null) }
|
||||||
|
|
||||||
|
CobblebaseGate.assign(pokemon.uuid, job)
|
||||||
|
probe.runs(job)
|
||||||
|
s.check(CobblebaseGate.assignment(pokemon.uuid) == null) { "$job stayed assigned to a Lv 1 Pokémon holding nothing (gate: $zone)" }
|
||||||
|
CobblebaseGate.assign(pokemon.uuid, job)
|
||||||
|
s.check(probe.runs(job, material)) { "$job didn't run for a Pokémon holding $material" }
|
||||||
|
s.check(CobblebaseGate.assignment(pokemon.uuid) == job) { "$job was taken back from a Pokémon holding $material" }
|
||||||
s.helper.succeed()
|
s.helper.succeed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ object SelfTest {
|
|||||||
private val cases: List<SelfTestCase> get() =
|
private val cases: List<SelfTestCase> get() =
|
||||||
PermadeathTests.all() + SurvivalRulesTests.all() + ZoneTests.all() + HazardTests.all() + GuardianTests.all() +
|
PermadeathTests.all() + SurvivalRulesTests.all() + ZoneTests.all() + HazardTests.all() + GuardianTests.all() +
|
||||||
VeinTests.all() + OreNodeTests.all() + DropTests.all() + ProgressionTests.all() +
|
VeinTests.all() + OreNodeTests.all() + DropTests.all() + ProgressionTests.all() +
|
||||||
CatchingTests.all() + WorldRulesTests.all() + FieldMoveTests.all() + ShoulderTests.all()
|
CatchingTests.all() + WorldRulesTests.all() + FieldMoveTests.all() + ShoulderTests.all() + BaseLootTests.all()
|
||||||
|
|
||||||
private var running: List<Scenario> = emptyList()
|
private var running: List<Scenario> = emptyList()
|
||||||
private var forced: List<ChunkPos> = emptyList()
|
private var forced: List<ChunkPos> = emptyList()
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package dev.cobblesurvival.zone
|
||||||
|
|
||||||
|
import com.mojang.serialization.Codec
|
||||||
|
import com.mojang.serialization.MapCodec
|
||||||
|
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||||
|
import dev.cobblesurvival.CobbleSurvival
|
||||||
|
import net.minecraft.core.BlockPos
|
||||||
|
import net.minecraft.core.registries.Registries
|
||||||
|
import net.minecraft.world.level.storage.loot.LootContext
|
||||||
|
import net.minecraft.world.level.storage.loot.parameters.LootContextParam
|
||||||
|
import net.minecraft.world.level.storage.loot.parameters.LootContextParams
|
||||||
|
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition
|
||||||
|
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType
|
||||||
|
import net.neoforged.bus.api.IEventBus
|
||||||
|
import net.neoforged.neoforge.registries.DeferredHolder
|
||||||
|
import net.neoforged.neoforge.registries.DeferredRegister
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loot condition `{"condition": "cobblesurvival:in_zone", "zones": ["westmarch", ...]}`: true when the loot's origin lies
|
||||||
|
* in one of the zones ([Zones.at]). The pack's Cobblebase mining and finder tables put it on every ore, so a pasture only
|
||||||
|
* yields its own zone's ores (Adrian, 2026-10-31; tools/cobblebase_loot.py).
|
||||||
|
*/
|
||||||
|
class InZoneCondition(val zones: List<String>) : LootItemCondition {
|
||||||
|
override fun getType(): LootItemConditionType = TYPE.get()
|
||||||
|
|
||||||
|
override fun getReferencedContextParams(): Set<LootContextParam<*>> = setOf(LootContextParams.ORIGIN)
|
||||||
|
|
||||||
|
override fun test(context: LootContext): Boolean {
|
||||||
|
val origin = context.getParamOrNull(LootContextParams.ORIGIN) ?: return false
|
||||||
|
val zone = Zones.at(context.level, BlockPos.containing(origin)) ?: return false
|
||||||
|
return zone.id in zones
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val CODEC: MapCodec<InZoneCondition> = RecordCodecBuilder.mapCodec { builder ->
|
||||||
|
builder.group(Codec.STRING.listOf().fieldOf("zones").forGetter(InZoneCondition::zones)).apply(builder, ::InZoneCondition)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val TYPES = DeferredRegister.create(Registries.LOOT_CONDITION_TYPE, CobbleSurvival.MOD_ID)
|
||||||
|
val TYPE: DeferredHolder<LootItemConditionType, LootItemConditionType> = TYPES.register("in_zone") { -> LootItemConditionType(CODEC) }
|
||||||
|
|
||||||
|
fun register(bus: IEventBus) = TYPES.register(bus)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,5 +6,6 @@
|
|||||||
"message.cobblesurvival.hazard.mire": "The Mire's fumes are poisoning you. Mire Waders would keep them off.",
|
"message.cobblesurvival.hazard.mire": "The Mire's fumes are poisoning you. Mire Waders would keep them off.",
|
||||||
"message.cobblesurvival.gate.requirement": "Lv %s, or to hold %s (%s) or a material of a harder zone",
|
"message.cobblesurvival.gate.requirement": "Lv %s, or to hold %s (%s) or a material of a harder zone",
|
||||||
"message.cobblesurvival.gate.skill": "%s won't do %s at your base yet. It needs %s.",
|
"message.cobblesurvival.gate.skill": "%s won't do %s at your base yet. It needs %s.",
|
||||||
"message.cobblesurvival.gate.field_move": "%s can't give you %s yet. It needs %s."
|
"message.cobblesurvival.gate.field_move": "%s can't give you %s yet. It needs %s.",
|
||||||
|
"message.cobblesurvival.gate.job": "%s can't take the %s job yet. It needs %s."
|
||||||
}
|
}
|
||||||
Executable
+128
@@ -0,0 +1,128 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Write the pack's versions of Cobblebase's mining and finder loot tables: a pasture only yields its zone's ores.
|
||||||
|
|
||||||
|
Adrian (2026-10-31): Cobblebase's Mining skill should only dig up the ores of the zone its pasture is in. Its tables,
|
||||||
|
and those of the Ore Finder and the Evolution-item Finder, which hand out the same ores and evolution stones, ignore
|
||||||
|
the island's zones. This copies them from the Cobblebase jar in build/server/mods and puts the loot condition
|
||||||
|
`cobblesurvival:in_zone` on every ore and evolution stone, naming the zone of its tier in world/zones.json. The mining
|
||||||
|
tables also lose the type gems (crafting shortcuts to gate materials such as the Heat Rock; ruins archaeology still
|
||||||
|
has them) and gain the zone ores Cobblebase leaves out (quartz, the evolution stones). The companion mod takes
|
||||||
|
Cobblebase's built-in copies of these tables out at server start (ore/BaseLoot.kt), so these files are what is rolled.
|
||||||
|
|
||||||
|
Output: datapacks/core/data/cobblebase/loot_table/<table>.json (derived from Cobblebase, MPL-2.0).
|
||||||
|
|
||||||
|
Usage: tools/cobblebase_loot.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import zipfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
OUT = ROOT / "datapacks/core/data/cobblebase/loot_table"
|
||||||
|
CONDITION = "cobblesurvival:in_zone"
|
||||||
|
|
||||||
|
# What an ore tier of world/zones.json drops; evolution-stone tiers drop their stone.
|
||||||
|
TIER_ITEMS = {
|
||||||
|
"coal": ["minecraft:coal"],
|
||||||
|
"copper": ["minecraft:raw_copper"],
|
||||||
|
"iron": ["minecraft:raw_iron"],
|
||||||
|
"gold": ["minecraft:raw_gold"],
|
||||||
|
"redstone": ["minecraft:redstone"],
|
||||||
|
"lapis": ["minecraft:lapis_lazuli"],
|
||||||
|
"emerald": ["minecraft:emerald"],
|
||||||
|
"amethyst": ["minecraft:amethyst_shard"],
|
||||||
|
"diamond": ["minecraft:diamond"],
|
||||||
|
"quartz": ["minecraft:quartz"],
|
||||||
|
"netherite": ["minecraft:ancient_debris", "minecraft:netherite_scrap"],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Table families: which items get the zone condition, which are dropped, and where missing zone items are added
|
||||||
|
# (rarity suffix and weight, like Cobblebase's neighbouring entries).
|
||||||
|
FAMILIES = {
|
||||||
|
"mining_": {"zoned": ("ore", "stone"), "drop_gems": True, "add": {"ore": ("uncommon", 8), "stone": ("rare", 3)}},
|
||||||
|
"finder_ore_": {"zoned": ("ore",), "drop_gems": False, "add": {"ore": ("uncommon", 8)}},
|
||||||
|
"finder_evo_": {"zoned": ("stone",), "drop_gems": False, "add": {"stone": ("common", 10)}},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def cobblebase_jar():
|
||||||
|
jars = sorted((ROOT / "build/server/mods").glob("cobblebase-*.jar"))
|
||||||
|
if not jars:
|
||||||
|
sys.exit("no Cobblebase jar in build/server/mods: run tools/build.py and tools/selftest.py (or the quick check) first")
|
||||||
|
return jars[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def zone_items():
|
||||||
|
"""item id -> (kind, zone) for every ore tier of world/zones.json."""
|
||||||
|
ores = json.loads((ROOT / "world/zones.json").read_text())["ores"]
|
||||||
|
items = {}
|
||||||
|
for tier, ore in ores.items():
|
||||||
|
if tier.endswith("_stone"):
|
||||||
|
drops, kind = [f"cobblemon:{tier}"], "stone"
|
||||||
|
elif tier in TIER_ITEMS:
|
||||||
|
drops, kind = TIER_ITEMS[tier], "ore"
|
||||||
|
else:
|
||||||
|
sys.exit(f"ore tier {tier} has no drop in TIER_ITEMS")
|
||||||
|
for item in drops:
|
||||||
|
items[item] = (kind, ore["zone"])
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def rewrite(table, family, items):
|
||||||
|
spec = FAMILIES[family]
|
||||||
|
seen = set()
|
||||||
|
for pool in table["pools"]:
|
||||||
|
kept = []
|
||||||
|
for entry in pool["entries"]:
|
||||||
|
name = entry.get("name", "")
|
||||||
|
if spec["drop_gems"] and name.endswith("_gem"):
|
||||||
|
continue
|
||||||
|
kind, zone = items.get(name, (None, None))
|
||||||
|
if kind in spec["zoned"]:
|
||||||
|
entry = dict(entry, conditions=[{"condition": CONDITION, "zones": [zone]}])
|
||||||
|
seen.add(name)
|
||||||
|
kept.append(entry)
|
||||||
|
pool["entries"] = kept
|
||||||
|
return seen
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
items = zone_items()
|
||||||
|
jar = zipfile.ZipFile(cobblebase_jar())
|
||||||
|
prefix = "data/cobblebase/loot_table/"
|
||||||
|
names = sorted(n[len(prefix):-5] for n in jar.namelist() if n.startswith(prefix) and n.endswith(".json"))
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
written = []
|
||||||
|
for family, spec in FAMILIES.items():
|
||||||
|
tables = {name: json.loads(jar.read(f"{prefix}{name}.json")) for name in names if name.startswith(family)}
|
||||||
|
if not tables:
|
||||||
|
sys.exit(f"Cobblebase has no {family}* loot tables any more")
|
||||||
|
seen = set()
|
||||||
|
for table in tables.values():
|
||||||
|
seen |= rewrite(table, family, items)
|
||||||
|
for item, (kind, zone) in sorted(items.items()):
|
||||||
|
if kind not in spec["zoned"] or item in seen:
|
||||||
|
continue
|
||||||
|
rarity, weight = spec["add"][kind]
|
||||||
|
target = tables.get(f"{family}{rarity}")
|
||||||
|
if target is None:
|
||||||
|
sys.exit(f"no table {family}{rarity} to add {item} to")
|
||||||
|
target["pools"][0]["entries"].append({
|
||||||
|
"type": "minecraft:item", "name": item, "weight": weight,
|
||||||
|
"conditions": [{"condition": CONDITION, "zones": [zone]}],
|
||||||
|
})
|
||||||
|
for name, table in tables.items():
|
||||||
|
(OUT / f"{name}.json").write_text(json.dumps(table, indent=2, ensure_ascii=False) + "\n")
|
||||||
|
written.append(name)
|
||||||
|
print(f"wrote {len(written)} tables to {OUT.relative_to(ROOT)}: {', '.join(written)}")
|
||||||
|
by_zone = {}
|
||||||
|
for item, (kind, zone) in items.items():
|
||||||
|
by_zone.setdefault(zone, []).append(item.split(":")[1])
|
||||||
|
for zone, zoned in sorted(by_zone.items()):
|
||||||
|
print(f" {zone}: {', '.join(sorted(zoned))}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in new issue
Block a user