Phase 4: Cobblebase and Poke Power, gated behind progression
Adrian (2026-10-30): Cobblebase's skills stay in but gated behind progression, power from an existing mod (Poke Power) also gated, evolution stones as ores fine for now, balance fine and configurable. Progression is the zones whose Alpha guardian a player helped beat (Progress saved data; a guardian win records it and lists the unlocks). The gate table is data (progression_gates/base.json): each of Cobblebase's 44 skills needs a zone (or none), the Power Block needs the Verdant Jungle. The companion mod wraps every Cobblebase skill executor in a gate by reflection; a skill missing from the table stays off. Operator command /cobblesurvival progress. Cobblebase's and Poke Power's configs ship in the pack. Self-tests: gate coverage, a locked skill only runs after its guardian, the Power Block waits for the Jungle guardian, a guardian win counts as progress. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1a1fb93da8
commit
362d06aa4b
22 files changed
+618
-20
No files matched your search
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"_comment": "Progression gates (Adrian, 2026-10-30): a Cobblebase skill works for a pastured Pokémon, and a block can be placed, only once its owner has beaten that zone's Alpha guardian; null = from the start. A Cobblebase skill missing here stays off. Placeholder stages; see docs/balance.md. Read by the companion mod (progress/Gates.kt); later files in this folder override earlier ones.",
|
||||||
|
"cobblebase_skills": {
|
||||||
|
"cobblebase:gatherer": null,
|
||||||
|
"cobblebase:harvester": null,
|
||||||
|
"cobblebase:irrigator": null,
|
||||||
|
"cobblebase:producer": null,
|
||||||
|
"cobblebase:furnace_fuel": null,
|
||||||
|
"cobblebase:water_fill": null,
|
||||||
|
"cobblebase:extinguisher": null,
|
||||||
|
"cobblebase:scout": null,
|
||||||
|
"cobblebase:guard": null,
|
||||||
|
"cobblebase:fishing": "button_isle",
|
||||||
|
"cobblebase:finder_food": "button_isle",
|
||||||
|
"cobblebase:finder_see": "button_isle",
|
||||||
|
"cobblebase:finder_bal": "button_isle",
|
||||||
|
"cobblebase:snow_fill": "button_isle",
|
||||||
|
"cobblebase:brew_fuel": "button_isle",
|
||||||
|
"cobblebase:craftsman": "button_isle",
|
||||||
|
"cobblebase:finder_hea": "southwold",
|
||||||
|
"cobblebase:finder_exp": "southwold",
|
||||||
|
"cobblebase:mentor": "southwold",
|
||||||
|
"cobblebase:lava_fill": "southwold",
|
||||||
|
"cobblebase:mining": "westmarch",
|
||||||
|
"cobblebase:finder_ore": "westmarch",
|
||||||
|
"cobblebase:finder_bui": "westmarch",
|
||||||
|
"cobblebase:finder_held": "westmarch",
|
||||||
|
"cobblebase:diving": "westmarch",
|
||||||
|
"cobblebase:finder_evo": "jungle",
|
||||||
|
"cobblebase:finder_stat": "jungle",
|
||||||
|
"cobblebase:finder_smith": "jungle",
|
||||||
|
"cobblebase:finder_treasure": "jungle",
|
||||||
|
"cobblebase:growth_aura": "jungle",
|
||||||
|
"cobblebase:speed_boost": "highlands",
|
||||||
|
"cobblebase:jump_boost": "highlands",
|
||||||
|
"cobblebase:haste_boost": "highlands",
|
||||||
|
"cobblebase:night_vision": "highlands",
|
||||||
|
"cobblebase:water_breathing": "highlands",
|
||||||
|
"cobblebase:saturation_boost": "highlands",
|
||||||
|
"cobblebase:resistance_boost": "mire",
|
||||||
|
"cobblebase:strength_boost": "mire",
|
||||||
|
"cobblebase:friend_recruiter": "sunscorch",
|
||||||
|
"cobblebase:aura_boost": "sunscorch",
|
||||||
|
"cobblebase:healer": "frostpeak",
|
||||||
|
"cobblebase:lucky_charm": "frostpeak",
|
||||||
|
"cobblebase:breeder": "deep_dark",
|
||||||
|
"cobblebase:egg_hatcher": "deep_dark"
|
||||||
|
},
|
||||||
|
"blocks": {
|
||||||
|
"poke_power:power_block": "jungle"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,8 +52,8 @@ JEI declares Minecraft `[1.21, 1.21.1)` in every 1.21.1 build, which a strict re
|
|||||||
| Chunky | 1 (build tool only, not in the pack) | 1.4.23 (MR `LuFhm4eU`, CF project `485681`) | MR + CF | n/a. `tools/make_world.py` adds it to the test server only while pregenerating |
|
| Chunky | 1 (build tool only, not in the pack) | 1.4.23 (MR `LuFhm4eU`, CF project `485681`) | MR + CF | n/a. `tools/make_world.py` adds it to the test server only while pregenerating |
|
||||||
| Cold Sweat | 3 | 2.4.4 (MR `UGoodVYD`, CF `9215368`) | MR + CF | **In the pack** (2026-10-29); same jar on both platforms |
|
| Cold Sweat | 3 | 2.4.4 (MR `UGoodVYD`, CF `9215368`) | MR + CF | **In the pack** (2026-10-29); same jar on both platforms |
|
||||||
| Regenerating Ore Veins | 4 | 1.4.0 (CF `8066055`) | **CF only** | **In the pack** (2026-10-30); n/a (no Cobblemon dependency). Left out of the Modrinth export |
|
| Regenerating Ore Veins | 4 | 1.4.0 (CF `8066055`) | **CF only** | **In the pack** (2026-10-30); n/a (no Cobblemon dependency). Left out of the Modrinth export |
|
||||||
| Cobblebase | 4 | 2.0.1+1.8.0 (MR `pFYNQ6W2`) | MR has the 1.8 build; **CF only has 2.0.0+1.7.0** | ✅ 2.0.1 requires ≥ 1.8.0 and fixes a startup crash on 1.8. Modrinth lists Xaero's Minimap, Cobbreeding and Cloth Config as required, but the jar declares only Cobblemon and Kotlin for Forge. **Tried 2026-10-30:** loads with the pack without Cobbreeding and Cloth Config, all self-tests pass; MPL-2.0. Left out until Adrian decides which skills to keep (`docs/checklists/phase-4.md`) |
|
| Cobblebase | 4 | 2.0.1+1.8.0 (MR `pFYNQ6W2`) | MR has the 1.8 build; **CF only has 2.0.0+1.7.0** | ✅ 2.0.1 requires ≥ 1.8.0 and fixes a startup crash on 1.8. Modrinth lists Xaero's Minimap, Cobbreeding and Cloth Config as required, but the jar declares only Cobblemon and Kotlin for Forge. **In the pack** (2026-10-30) without Cobbreeding and Cloth Config; every skill gated behind progression (companion mod, `progression_gates`). Breeder and Egg Hatcher do nothing without Cobbreeding |
|
||||||
| Cobblemon Poke Power | 4 (or own generator, Phase 6) | 1.3.1 (MR `vGpaTxmN`) | **MR only** | ✅ Declares `[1.8.0,)`. Needs Matthiesen Core |
|
| Cobblemon Poke Power | 4 | 1.3.1 (MR `vGpaTxmN`) | **MR only** | ✅ Declares `[1.8.0,)`. Needs Matthiesen Core. **In the pack** (2026-10-30, Adrian: use the existing option, gated): the Power Block opens with the Verdant Jungle guardian. Jade is an optional extra it supports |
|
||||||
| Cobblemon Quests (FTB Quests integration) | 8 | 1.2.0 (MR `8KeptEg2`; CF project `922979`) | MR + CF | ❌ Targets 1.7. Its own changelog says it does not work with FTB Quests ≥ 2101.1.19; the pack has 2101.1.37. Blocked until updated |
|
| Cobblemon Quests (FTB Quests integration) | 8 | 1.2.0 (MR `8KeptEg2`; CF project `922979`) | MR + CF | ❌ Targets 1.7. Its own changelog says it does not work with FTB Quests ≥ 2101.1.19; the pack has 2101.1.37. Blocked until updated |
|
||||||
| Quests & Teams Fixes (flexible-mode soft-lock fix) | 8 | — | MR | ❌ Only 1.20.1 builds exist. Test the soft-lock on FTB Quests 2101.1.37 ourselves |
|
| Quests & Teams Fixes (flexible-mode soft-lock fix) | 8 | — | MR | ❌ Only 1.20.1 builds exist. Test the soft-lock on FTB Quests 2101.1.37 ourselves |
|
||||||
| spark | 9 | 1.10.124 (MR `v5qtqRQi`, CF `6225208`) | MR + CF | n/a |
|
| spark | 9 | 1.10.124 (MR `v5qtqRQi`, CF `6225208`) | MR + CF | n/a |
|
||||||
@@ -76,4 +76,4 @@ No single platform hosts everything the roadmap names:
|
|||||||
- **CurseForge only:** FTB Quests, FTB Library, FTB Teams, Ore Generation Configuration, Regenerating Ore Veins (and the 1.8 build of Cobblebase is Modrinth only).
|
- **CurseForge only:** FTB Quests, FTB Library, FTB Teams, Ore Generation Configuration, Regenerating Ore Veins (and the 1.8 build of Cobblebase is Modrinth only).
|
||||||
- **Modrinth only:** CobbleHardcoreMon, Matthiesen Core, Cobblemon Poke Power, Cobblebase 2.0.1.
|
- **Modrinth only:** CobbleHardcoreMon, Matthiesen Core, Cobblemon Poke Power, Cobblebase 2.0.1.
|
||||||
|
|
||||||
`tools/build.py` leaves each mod out of the export for the platform that doesn't host it and lists what it left out. Today the `.mrpack` lacks the three FTB mods and Regenerating Ore Veins. Once Phases 1–4 land, the CurseForge zip will lack permadeath, and the Modrinth pack will lack quests and ore veins. This needs a decision (see `docs/design.md`, open decisions).
|
`tools/build.py` leaves each mod out of the export for the platform that doesn't host it and lists what it left out. Today the `.mrpack` lacks the three FTB mods and Regenerating Ore Veins; the CurseForge zip lacks CobbleHardcoreMon, Matthiesen Core, Poke Power and Cobblebase. Once Phases 1–4 land, the CurseForge zip will lack permadeath, and the Modrinth pack will lack quests and ore veins. This needs a decision (see `docs/design.md`, open decisions).
|
||||||
+11
-7
@@ -30,12 +30,16 @@ Every tunable lives in a config file or in this table. **Placeholder** means gue
|
|||||||
| Level pressure | full experience up to 5 levels above the zone's band, then 10 % less per level, at least 10 % (Exp. Candy and commands exempt) | Decided (Adrian, 2026-10-30) | `world/zones.json` → `level_pressure` | E.g. on Button Isle (up to Lv 10): Lv 15 full, Lv 20 half, Lv 24+ a tenth |
|
| Level pressure | full experience up to 5 levels above the zone's band, then 10 % less per level, at least 10 % (Exp. Candy and commands exempt) | Decided (Adrian, 2026-10-30) | `world/zones.json` → `level_pressure` | E.g. on Button Isle (up to Lv 10): Lv 15 full, Lv 20 half, Lv 24+ a tenth |
|
||||||
| Strong flyer | ridable through the air with top speed ≥ 60 or top stamina ≥ 85 (Cobblemon ride stats) | Decided (Adrian, 2026-10-30) | `tools/zone_spawns.py` (`STRONG_FLYER_*`) | 29 species |
|
| Strong flyer | ridable through the air with top speed ≥ 60 or top stamina ≥ 85 (Cobblemon ride stats) | Decided (Adrian, 2026-10-30) | `tools/zone_spawns.py` (`STRONG_FLYER_*`) | 29 species |
|
||||||
| High zone for strong flyers | band starts at Lv 30 or more (Sunscorch, Frostpeak, Deep Sea, Deep Dark, Basalt Furnace) | Decided (Adrian, 2026-10-30) | `tools/zone_spawns.py` (`HIGH_ZONE_LEVEL`) | |
|
| High zone for strong flyers | band starts at Lv 30 or more (Sunscorch, Frostpeak, Deep Sea, Deep Dark, Basalt Furnace) | Decided (Adrian, 2026-10-30) | `tools/zone_spawns.py` (`HIGH_ZONE_LEVEL`) | |
|
||||||
| Ore tiers per zone | Button Isle: coal; Southwold: copper; Westmarch: iron, thunder stone; Verdant Jungle: redstone, leaf stone; Highland Meadows: lapis, sun stone; Sunscorch: gold, fire stone; Frostpeak Range: emerald, ice stone, dawn stone; Dripstone Depths: amethyst, moon stone; Deep Dark: diamond, dusk stone; Basalt Furnace: quartz, netherite; Lush Caves: shiny stone; Mangrove Mire: water stone | Placeholder | `world/zones.json` → `ores` → `tools/zone_ores.py` | Each tier in exactly one zone; evolution stones are tiers (their natural ores are off) |
|
| Ore tiers per zone | Button Isle: coal; Southwold: copper; Westmarch: iron, thunder stone; Verdant Jungle: redstone, leaf stone; Highland Meadows: lapis, sun stone; Sunscorch: gold, fire stone; Frostpeak Range: emerald, ice stone, dawn stone; Dripstone Depths: amethyst, moon stone; Deep Dark: diamond, dusk stone; Basalt Furnace: quartz, netherite; Lush Caves: shiny stone; Mangrove Mire: water stone | Fine for now (Adrian, 2026-10-30) | `world/zones.json` → `ores` → `tools/zone_ores.py` | Each tier in exactly one zone; evolution stones are tiers (their natural ores are off) |
|
||||||
| Vein regrowth | 20 min: coal; 30 min: copper; 40 min: iron; 45 min: redstone, lapis; 60 min: gold, quartz, thunder stone, shiny stone, leaf stone, water stone, moon stone, sun stone, fire stone, ice stone; 75 min: emerald, dawn stone, dusk stone; 90 min: diamond; 120 min: amethyst; 180 min: netherite; ±10 % jitter; real time | Placeholder | `world/zones.json` → `ores` (`regen_minutes`, `ore_defaults.jitter`) | Counts while the server is off too |
|
| Vein regrowth | 20 min: coal; 30 min: copper; 40 min: iron; 45 min: redstone, lapis; 60 min: gold, quartz, thunder stone, shiny stone, leaf stone, water stone, moon stone, sun stone, fire stone, ice stone; 75 min: emerald, dawn stone, dusk stone; 90 min: diamond; 120 min: amethyst; 180 min: netherite; ±10 % jitter; real time | Fine for now (Adrian, 2026-10-30) | `world/zones.json` → `ores` (`regen_minutes`, `ore_defaults.jitter`) | Counts while the server is off too |
|
||||||
| Vein spacing, size, tries | spacing 4–14 chunks between candidate chunks, radius 0–2, 70 % filled, 1–4 tries per candidate chunk (per tier in `world/zones.json`) | Placeholder | `world/zones.json` → `ores`, `ore_defaults` | Ore blocks in the template (2026-10-30): copper 10,400, iron 6,356, quartz 2,397, lapis 2,236, coal 2,222, redstone 1,044, gold 815, diamond 559, shiny stone 466, thunder stone 239, sun stone 238, amethyst 238, dusk stone 222, emerald 181, leaf stone 125, ice stone 102, moon stone 100, fire stone 98, water stone 95, netherite 74, dawn stone 64. Per zone in `build/island-report.json` after `tools/make_world.py` |
|
| Vein spacing, size, tries | spacing 4–14 chunks between candidate chunks, radius 0–2, 70 % filled, 1–4 tries per candidate chunk (per tier in `world/zones.json`) | Fine for now (Adrian, 2026-10-30) | `world/zones.json` → `ores`, `ore_defaults` | Ore blocks in the template (2026-10-30): copper 10,400, iron 6,356, quartz 2,397, lapis 2,236, coal 2,222, redstone 1,044, gold 815, diamond 559, shiny stone 466, thunder stone 239, sun stone 238, amethyst 238, dusk stone 222, emerald 181, leaf stone 125, ice stone 102, moon stone 100, fire stone 98, water stone 95, netherite 74, dawn stone 64. Per zone in `build/island-report.json` after `tools/make_world.py` |
|
||||||
| Regenerators breakable by players | no; explosions don't hurt veins | Placeholder | `tools/zone_ores.py` → `global.json` | Otherwise a player could remove a vein for good |
|
| Regenerators breakable by players | no; explosions don't hurt veins | Fine for now (Adrian, 2026-10-30) | `tools/zone_ores.py` → `global.json` | Otherwise a player could remove a vein for good |
|
||||||
| Vein Locator | compass + copper ingot; 75 % chance to break per use (the mod's default) | Placeholder | `tools/zone_ores.py` → `vein_locator.json` | The mod's default recipe needs netherite |
|
| Vein Locator | compass + copper ingot; 75 % chance to break per use (the mod's default) | Fine for now (Adrian, 2026-10-30) | `tools/zone_ores.py` → `vein_locator.json` | The mod's default recipe needs netherite |
|
||||||
| Drops for mobs the island lacks | Blaze Rod: Carkol 10 %, Torkoal 25 % ×1–2; Ghast Tear: Drifloon 5 %, Dusclops 10 %; Breeze Rod: Whimsicott 10 %; Shulker Shell: Shuckle 10 %, Cloyster 10 %; Nether Star: Charizard 5 % | Placeholder | `datapacks/core/data/cobblesurvival/species_additions/` | Every drop and what consumes it: `docs/pokemon-drops.md` (`tools/drop_audit.py`) |
|
| Drops for mobs the island lacks | Blaze Rod: Carkol 10 %, Torkoal 25 % ×1–2; Ghast Tear: Drifloon 5 %, Dusclops 10 %; Breeze Rod: Whimsicott 10 %; Shulker Shell: Shuckle 10 %, Cloyster 10 %; Nether Star: Charizard 5 % | Fine for now (Adrian, 2026-10-30) | `datapacks/core/data/cobblesurvival/species_additions/` | Every drop and what consumes it: `docs/pokemon-drops.md` (`tools/drop_audit.py`) |
|
||||||
|
| Cobblebase skill gates | start: gatherer, harvester, irrigator, producer, furnace fuel, water fill, extinguisher, scout, guard; Button Isle: fishing, forager, botanist, collector, snow fill, brewing fuel, craftsman; Southwold: pharmacist, scholar, mentor (passive XP), lava fill; Westmarch: mining, excavator, architect, armorer, diving; Verdant Jungle: alchemist, trainer, smith, prospector, growth aura; Highland Meadows: speed, jump, haste, night vision, water breathing, saturation buffs; Mangrove Mire: resistance, strength buffs; Sunscorch: friend recruiter, aura boost; Frostpeak: healer, lucky charm; Deep Dark: breeder, egg hatcher | Placeholder | `datapacks/core/data/cobblesurvival/progression_gates/base.json` | A zone means "after beating its Alpha guardian" |
|
||||||
|
| Power Block gate | after the Verdant Jungle guardian | Placeholder | same file, `blocks` | Recipe: Poke Power's own (iron blocks, redstone blocks, Electirizer) |
|
||||||
|
| 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 |
|
||||||
| Ride stats (speed, stamina, ...) | Cobblemon's own | To tune after play-testing | species `riding` via `species_additions` | |
|
| Ride stats (speed, stamina, ...) | Cobblemon's own | To tune after play-testing | species `riding` via `species_additions` | |
|
||||||
| Healing while sleeping | off (`healPercent` 0) | Decided (roadmap) | `config/cobblemon/main.json` | |
|
| Healing while sleeping | off (`healPercent` 0) | Decided (roadmap) | `config/cobblemon/main.json` | |
|
||||||
| Loot container refresh | 1 hour (72,000 ticks of game time) after first opened, all containers | Decided (Adrian, 2026-10-29) | `config/lootr-common.toml` | Tiering by structure/biome later |
|
| Loot container refresh | 1 hour (72,000 ticks of game time) after first opened, all containers | Decided (Adrian, 2026-10-29) | `config/lootr-common.toml` | Tiering by structure/biome later |
|
||||||
@@ -45,4 +49,4 @@ Every tunable lives in a config file or in this table. **Placeholder** means gue
|
|||||||
| Server heap for smoke test | 4 GB | Tooling | `tools/smoke_test.py` | Not a gameplay value |
|
| Server heap for smoke test | 4 GB | Tooling | `tools/smoke_test.py` | Not a gameplay value |
|
||||||
| Dev client heap (Prism) | 6 GB | Tooling | `tools/prism_instance.py --memory` | Not a gameplay value |
|
| Dev client heap (Prism) | 6 GB | Tooling | `tools/prism_instance.py --memory` | Not a gameplay value |
|
||||||
|
|
||||||
Sections to fill as phases land: pasture job cooldowns (Phase 4), hazard damage (Phase 3), field-move cooldowns and caps (Phase 5), machine output (Phase 6), boss attack damage and telegraph times (Phase 7).
|
Sections to fill as phases land: hazard damage (Phase 3), field-move cooldowns and caps (Phase 5), machine output (Phase 6), boss attack damage and telegraph times (Phase 7).
|
||||||
@@ -19,7 +19,20 @@ The new template is the save folder "Cobblemon Survival Island (Phase 4)"; the w
|
|||||||
|
|
||||||
## Ask Adrian: Phase 4 decisions
|
## Ask Adrian: Phase 4 decisions
|
||||||
|
|
||||||
1. **Evolution stones as vein tiers.** Their natural ores have been off since Phase 1, so stones had no source at all. Now each stone has veins in one zone (Leaf Stone in the jungle, Water Stone in the Mire, Ice and Dawn Stone on Frostpeak, ...). Keep, move stones, or get them some other way?
|
**Answered 2026-10-30:** (1) evolution stones as ores are fine for now; (2) Cobblebase's skills stay in, gated behind progression; (3) power from an existing mod, also gated: Cobblemon Poke Power; (4) the balance is fine for now and stays configurable.
|
||||||
2. **Cobblebase (pasture jobs).** 2.0.1+1.8.0 loads on Cobblemon 1.8 with the pack, and all self-tests still pass with it (tried 2026-10-30, then taken out again until you decide; Modrinth only, CurseForge has only the 1.7 build). Its config has a switch per skill group (healing, recruiting, passive XP, finders, buffs, ...). It's a whole Palworld-style job system with 47 skills, and several cut across the pack's rules: a Healer skill (healing friction), Excavator/Mining (ore outside the zone veins), Alchemist (evolution stones), Breeder/Egg Hatcher, Friend Recruiter (spawns new Pokémon at the base, outside the zones), passive XP (5 % a minute for pastured Pokémon). Modrinth lists Cobbreeding (breeding; license "MIT, no AI, no reupload") as required, though Cobblebase's jar doesn't need it. Options: (a) Cobblebase with only Producer and skills that don't bypass zones or healing, (b) Cobblebase as it comes, (c) skip it and do base production in the companion mod in Phase 6. And: breeding, yes or no?
|
|
||||||
3. **Power** (roadmap): Cobblemon Poke Power (Electric types output Forge Energy; Modrinth only, needs Matthiesen Core, already in the pack) or our own generator in Phase 6?
|
## Base jobs and power (after the answers)
|
||||||
4. **Placeholders** in `docs/balance.md`: the ore tier table, vein regrowth times (real time), vein spacing and size, unbreakable regenerators, the Vein Locator recipe, and the drop picks (Carkol/Torkoal blaze rods, Drifloon/Dusclops ghast tears, Whimsicott breeze rods, Shuckle/Cloyster shulker shells, Charizard nether stars).
|
|
||||||
|
- **Cobblebase**, with each of its skills unlocked by beating a zone's Alpha guardian (table in `docs/balance.md`, data in `datapacks/core/data/cobblesurvival/progression_gates/base.json`). Automated: `gates_cover_every_cobblebase_skill`, `cobblebase_skills_wait_for_guardians`, and the guardian test checks that a win counts.
|
||||||
|
- **Poke Power**: the Power Block can only be placed after the Verdant Jungle guardian. Automated: `power_block_waits_for_guardian`.
|
||||||
|
|
||||||
|
In game:
|
||||||
|
|
||||||
|
- [ ] Put a Pokémon with a starting skill (e.g. Pikachu: Producer, Item Gatherer) in a pasture near a chest: it works from the start. A Pokémon whose skills are all gated later (check its skill list in Cobblebase's screen) just sits there.
|
||||||
|
- [ ] `/cobblesurvival progress grant <you> westmarch`, then a Mining Pokémon in the pasture starts digging up ore; `/cobblesurvival progress revoke <you> westmarch` stops it again.
|
||||||
|
- [ ] Beat a guardian: the chat lists what it unlocked at your base.
|
||||||
|
- [ ] Try to place a Power Block in survival before the Jungle guardian: it's refused with a message; after `/cobblesurvival progress grant <you> jungle` it places, and an Electric-type Pokémon inside makes energy (the block's screen and Jade show it).
|
||||||
|
|
||||||
|
## Ask Adrian: approve Phase 4 before Phase 5
|
||||||
|
|
||||||
|
The roadmap's done-when holds (every ore tier in exactly one zone, veins regrow after restart and unload, every Pokémon-sourced recipe input has a source), and base jobs and power are in, gated. Remaining placeholders: the skill stages and the Power Block gate (`docs/balance.md`).
|
||||||
+11
-2
@@ -128,13 +128,15 @@ Order of play the bands suggest: Button Isle → Southwold → Westmarch or Jung
|
|||||||
- Cold Sweat's own animals (chameleons) can't join, like vanilla mobs: the only-Pokémon rule now lets in Cobblemon's entities only.
|
- Cold Sweat's own animals (chameleons) can't join, like vanilla mobs: the only-Pokémon rule now lets in Cobblemon's entities only.
|
||||||
- Tested on the island itself (`cold_needs_fur`, `heat_needs_hide`, `lowlands_are_mild` in `tools/quick_check.py`): players stand at real spots in each zone, their body drift is turned into minutes until it hurts, and a frozen player without the grace period takes damage.
|
- Tested on the island itself (`cold_needs_fur`, `heat_needs_hide`, `lowlands_are_mild` in `tools/quick_check.py`): players stand at real spots in each zone, their body drift is turned into minutes until it hurts, and a frozen player without the grace period takes damage.
|
||||||
|
|
||||||
## Phase 4: resources (in progress, placeholders until Adrian approves)
|
## Phase 4: resources
|
||||||
|
|
||||||
|
**Adrian's answers (2026-10-30):** Cobblebase's skills look fun and stay in, gated behind progression; power comes from an existing mod, also gated (Cobblemon Poke Power); evolution stones as ores are fine for now; the balance values are fine for now and stay configurable, to tweak later.
|
||||||
|
|
||||||
**Ore veins.** Ore only comes from [Regenerating Ore Veins](https://www.curseforge.com/minecraft/mc-mods/regenerating-ore-veins) (plus chest loot): a mined vein block turns into a regenerator and grows back after its time, which counts in real time, unlike the guardians' game time. Each ore tier has veins in exactly one zone, higher tiers in higher zones (table in `docs/balance.md`). `tools/zone_ores.py` writes the mod's config (`pack/config/regenerating_ore_veins/`) from `world/zones.json` → `ores`:
|
**Ore veins.** Ore only comes from [Regenerating Ore Veins](https://www.curseforge.com/minecraft/mc-mods/regenerating-ore-veins) (plus chest loot): a mined vein block turns into a regenerator and grows back after its time, which counts in real time, unlike the guardians' game time. Each ore tier has veins in exactly one zone, higher tiers in higher zones (table in `docs/balance.md`). `tools/zone_ores.py` writes the mod's config (`pack/config/regenerating_ore_veins/`) from `world/zones.json` → `ores`:
|
||||||
|
|
||||||
- A surface zone is a set of boxes cut from the zone grid; the mod checks every block of a vein against them, so no vein block lies outside its zone. Cave biomes are zones of their own, so surface tiers stay out of them; the mod checks the biome only at a vein's centre, so a vein's edge can reach up to two blocks over a cave border. Cave tiers are limited to their cave biome and the depths where it lies (the Deep Dark mostly below y −24, the others between 0 and 40).
|
- A surface zone is a set of boxes cut from the zone grid; the mod checks every block of a vein against them, so no vein block lies outside its zone. Cave biomes are zones of their own, so surface tiers stay out of them; the mod checks the biome only at a vein's centre, so a vein's edge can reach up to two blocks over a cave border. Cave tiers are limited to their cave biome and the depths where it lies (the Deep Dark mostly below y −24, the others between 0 and 40).
|
||||||
- Tiers with an ordinary and a deepslate ore get two veins: the ordinary ore from y 0 up, the deepslate one below.
|
- Tiers with an ordinary and a deepslate ore get two veins: the ordinary ore from y 0 up, the deepslate one below.
|
||||||
- **Cobblemon's evolution stones are tiers too** (new in Phase 4, flagged): their natural ores have been off since Phase 1, so stones had no source. Each stone lives in one zone that fits it (Leaf Stone in the jungle, Water Stone in the Mire, Ice and Dawn Stone on Frostpeak, ...).
|
- **Cobblemon's evolution stones are tiers too** (approved for now, 2026-10-30): their natural ores have been off since Phase 1, so stones had no source. Each stone lives in one zone that fits it (Leaf Stone in the jungle, Water Stone in the Mire, Ice and Dawn Stone on Frostpeak, ...).
|
||||||
- Players can't break regenerators (`allow_breaking` false), so nobody removes a vein for good, and explosions don't hurt veins. The Vein Locator is crafted from a compass and a copper ingot instead of netherite.
|
- Players can't break regenerators (`allow_breaking` false), so nobody removes a vein for good, and explosions don't hurt veins. The Vein Locator is crafted from a compass and a copper ingot instead of netherite.
|
||||||
- The mod fills a new chunk with veins only while the chunk is loaded, 8 chunks a tick, and forgets the rest when the server stops. Pregeneration is faster than that, so `tools/make_world.py` loads the island again row by row until every chunk is filled (`/cobblesurvival veins unfilled` counts what's left), and the world report fails on any unfilled chunk. On a live server new chunks fill as players load them.
|
- The mod fills a new chunk with veins only while the chunk is loaded, 8 chunks a tick, and forgets the rest when the server stops. Pregeneration is faster than that, so `tools/make_world.py` loads the island again row by row until every chunk is filled (`/cobblesurvival veins unfilled` counts what's left), and the world report fails on any unfilled chunk. On a live server new chunks fill as players load them.
|
||||||
- One showcase vein per surface zone (its first tier) sticks out of the ground near the zone's sample point, so players see what a vein looks like before they dig.
|
- One showcase vein per surface zone (its first tier) sticks out of the ground near the zone's sample point, so players see what a vein looks like before they dig.
|
||||||
@@ -142,6 +144,13 @@ Order of play the bands suggest: Button Isle → Southwold → Westmarch or Jung
|
|||||||
|
|
||||||
**Unique drops.** Only Pokémon live on the island, so a mob's drop needs another source. `tools/drop_audit.py` checks every item a Pokémon or a vanilla mob drops that a recipe or brewing consumes, and writes `docs/pokemon-drops.md` (item, wild Pokémon with zone and chance, other sources, consumers). Gaps it found, filled through Cobblemon species additions (`datapacks/core/data/cobblesurvival/species_additions/`, placeholders): Blaze Rod (Carkol in Westmarch, Torkoal in the Basalt Furnace; needed for brewing stands, blaze powder and Eyes of Ender), Ghast Tear (Drifloon, Dusclops), Breeze Rod (Whimsicott), Shulker Shell (Shuckle, Cloyster), Nether Star (Charizard). The tide armour trim and the wither skeleton skull stay without a source (cosmetic here). A zone spawn with its own drops overrides the species' drops, so the audit also fails if an addition targets such a spawn (Magmortar's and Centiskorch's did). Self-test: `drop_additions_apply`.
|
**Unique drops.** Only Pokémon live on the island, so a mob's drop needs another source. `tools/drop_audit.py` checks every item a Pokémon or a vanilla mob drops that a recipe or brewing consumes, and writes `docs/pokemon-drops.md` (item, wild Pokémon with zone and chance, other sources, consumers). Gaps it found, filled through Cobblemon species additions (`datapacks/core/data/cobblesurvival/species_additions/`, placeholders): Blaze Rod (Carkol in Westmarch, Torkoal in the Basalt Furnace; needed for brewing stands, blaze powder and Eyes of Ender), Ghast Tear (Drifloon, Dusclops), Breeze Rod (Whimsicott), Shulker Shell (Shuckle, Cloyster), Nether Star (Charizard). The tide armour trim and the wither skeleton skull stay without a source (cosmetic here). A zone spawn with its own drops overrides the species' drops, so the audit also fails if an addition targets such a spawn (Magmortar's and Centiskorch's did). Self-test: `drop_additions_apply`.
|
||||||
|
|
||||||
|
**Base jobs and power, gated behind progression.** A player's progression is the zones whose Alpha guardian they have helped beat (`progress/Progress.kt`, saved with the world; the guardian win records it for every player in the battle and lists what it unlocked). The gate table is data, `datapacks/core/data/cobblesurvival/progression_gates/base.json`:
|
||||||
|
|
||||||
|
- [Cobblebase](https://modrinth.com/project/cobblebase) (Pokémon in a pasture work: harvesting, producing, finding, buffs, ...): each of its 44 skills works for a pastured Pokémon only once the Pokémon's owner has beaten the skill's zone. From the start: gathering, farming, producing, fuel, guarding, scouting; then step by step through the zones, with mining and the ore finder after Westmarch, the evolution-item finder after the Jungle, the player buffs after Highland Meadows and the Mire, the recruiter after Sunscorch and the healer after Frostpeak (table in `docs/balance.md`). The companion mod puts every Cobblebase skill executor behind the gate, by reflection, when the server starts (`progress/CobblebaseGate.kt`); a skill missing from the table stays off, so an update can't add ungated skills. Cobbreeding is not installed, so Breeder and Egg Hatcher do nothing.
|
||||||
|
- [Cobblemon Poke Power](https://modrinth.com/project/lIn7oRFm) (Electric-type Pokémon in a Power Block make Forge Energy): a survival player can place a Power Block only after beating the Verdant Jungle's guardian (the zone with the redstone its recipe needs). Its recipe is the mod's own (iron blocks, redstone blocks, an Electirizer); nothing in the pack uses the energy yet (Phase 6 machines).
|
||||||
|
- Operators: `/cobblesurvival progress <player>`, `... grant|revoke <player> <zone>`.
|
||||||
|
- Self-tests: `gates_cover_every_cobblebase_skill` (every skill has a gate, every gate zone has a guardian, every executor is gated), `cobblebase_skills_wait_for_guardians` and `power_block_waits_for_guardian`; `guardian_appears_and_returns` checks that the win counts as progress.
|
||||||
|
|
||||||
The field guide learned two things on the way: vanilla mobs count as absent (the companion mod lets only Cobblemon's entities join a world), and species additions replace the drops they set. The veins come in as sources from `world/vein-sources.json`.
|
The field guide learned two things on the way: vanilla mobs count as absent (the companion mod lets only Cobblemon's entities join a world), and species additions replace the drops they set. The veins come in as sources from `world/vein-sources.json`.
|
||||||
|
|
||||||
## Open decisions for Adrian (proceeding with the default, flagged)
|
## Open decisions for Adrian (proceeding with the default, flagged)
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ Read each license before adding a mod, and add a row here in the same change.
|
|||||||
| Cobblemon TP-Pastures | see Modrinth page | All Rights Reserved | [Modrinth](https://modrinth.com/mod/cobblemon_tp_pastures) | Phase 2 |
|
| Cobblemon TP-Pastures | see Modrinth page | All Rights Reserved | [Modrinth](https://modrinth.com/mod/cobblemon_tp_pastures) | Phase 2 |
|
||||||
| Lootr | noobanidus | MIT | [Modrinth](https://modrinth.com/mod/lootr) | Phase 2 |
|
| Lootr | noobanidus | MIT | [Modrinth](https://modrinth.com/mod/lootr) | Phase 2 |
|
||||||
| Cold Sweat | Momo Softworks (Mikul) | GPL-3.0-or-later | [Modrinth](https://modrinth.com/mod/cold-sweat) / [CurseForge](https://www.curseforge.com/minecraft/mc-mods/cold-sweat) | Phase 3: heat and cold as zone hazards (both) |
|
| Cold Sweat | Momo Softworks (Mikul) | GPL-3.0-or-later | [Modrinth](https://modrinth.com/mod/cold-sweat) / [CurseForge](https://www.curseforge.com/minecraft/mc-mods/cold-sweat) | Phase 3: heat and cold as zone hazards (both) |
|
||||||
|
| Cobblebase | notlown | MPL-2.0 | [Modrinth](https://modrinth.com/project/cobblebase) | Phase 4: Pokémon jobs at the base (both). The 1.8 build is Modrinth only |
|
||||||
|
| Cobblemon Poke Power | Matthiesen-dev (Adammatthiesen) | MIT | [Modrinth](https://modrinth.com/project/lIn7oRFm) | Phase 4: Pokémon-powered Forge Energy (both). Not on CurseForge |
|
||||||
| Regenerating Ore Veins | Stargazer | MIT (jar metadata) | [CurseForge](https://www.curseforge.com/minecraft/mc-mods/regenerating-ore-veins) | Phase 4: the only ore source besides chest loot (both). Not on Modrinth |
|
| Regenerating Ore Veins | Stargazer | MIT (jar metadata) | [CurseForge](https://www.curseforge.com/minecraft/mc-mods/regenerating-ore-veins) | Phase 4: the only ore source besides chest loot (both). Not on Modrinth |
|
||||||
| WorldGen Editor | waterflane | CC BY-NC-SA 4.0 | [Modrinth](https://modrinth.com/mod/worldgen-editor) / [CurseForge](https://www.curseforge.com/minecraft/mc-mods/worldgen-editor), [source](https://github.com/waterflane/WorldGen-Editor) | Non-commercial, share-alike. See the derived files below |
|
| WorldGen Editor | waterflane | CC BY-NC-SA 4.0 | [Modrinth](https://modrinth.com/mod/worldgen-editor) / [CurseForge](https://www.curseforge.com/minecraft/mc-mods/worldgen-editor), [source](https://github.com/waterflane/WorldGen-Editor) | Non-commercial, share-alike. See the derived files below |
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ Generated by `tools/drop_audit.py` (roadmap Phase 4, "unique drops"): every item
|
|||||||
| Dusk Stone (`cobblemon:dusk_stone`) | Honedge (westmarch, 2.5%) | Brush suspicious blocks in Rooted Gimmi Tower (cobblemon); Crafting (shapeless); Loot in Crumbling Arch (worldgen feature); Loot in Decaying Crypt Ruins (cobblemon), Deep Crypt (worldgen feature), Deserted Gimmi Tower (cobblemon), Frozen Gimmi Tower (cobblemon), Lush Gimmi Tower (cobblemon), Reclaimed Deserted Monument (cobblemon) and 5 more; Loot in Decaying Crypt Ruins (cobblemon), Rooted Gimmi Tower (cobblemon) … | Dusk Stone Block |
|
| Dusk Stone (`cobblemon:dusk_stone`) | Honedge (westmarch, 2.5%) | Brush suspicious blocks in Rooted Gimmi Tower (cobblemon); Crafting (shapeless); Loot in Crumbling Arch (worldgen feature); Loot in Decaying Crypt Ruins (cobblemon), Deep Crypt (worldgen feature), Deserted Gimmi Tower (cobblemon), Frozen Gimmi Tower (cobblemon), Lush Gimmi Tower (cobblemon), Reclaimed Deserted Monument (cobblemon) and 5 more; Loot in Decaying Crypt Ruins (cobblemon), Rooted Gimmi Tower (cobblemon) … | Dusk Stone Block |
|
||||||
| Echo Shard (`minecraft:echo_shard`) | – | Loot in Ancient City | Beast Ball, Recovery Compass |
|
| Echo Shard (`minecraft:echo_shard`) | – | Loot in Ancient City | Beast Ball, Recovery Compass |
|
||||||
| Egg (`minecraft:egg`) | Chansey (jungle, 50%); Exeggcute (jungle, ×0-3); Exeggutor (jungle, ×0-3) | Loot in Trial Chambers | Cake, Open-Faced Sandwich, Oran Lucky Egg, Pumpkin Pie |
|
| Egg (`minecraft:egg`) | Chansey (jungle, 50%); Exeggcute (jungle, ×0-3); Exeggutor (jungle, ×0-3) | Loot in Trial Chambers | Cake, Open-Faced Sandwich, Oran Lucky Egg, Pumpkin Pie |
|
||||||
|
| Electirizer (`cobblemon:electirizer`) | Electabuzz (frostpeak, 5%); Electivire (frostpeak, 10%) | Break Electirizer; Crafting | Power Block |
|
||||||
| Electric Seed (`cobblemon:electric_seed`) | Togedemaru (frostpeak, 5%) | Break Electric Seed | Miracle Seed |
|
| Electric Seed (`cobblemon:electric_seed`) | Togedemaru (frostpeak, 5%) | Break Electric Seed | Miracle Seed |
|
||||||
| Emerald (`minecraft:emerald`) | Nickit (westmarch, 2.5%); Sableye (button_isle, 5%); Thievul (westmarch, 5%) | Blast furnace; Break Emerald Ore; Brush suspicious blocks in Decaying Crypt Ruins (cobblemon); Brush suspicious blocks in Desert Pyramid; Brush suspicious blocks in Desert Well (worldgen feature) … | Block of Emerald |
|
| Emerald (`minecraft:emerald`) | Nickit (westmarch, 2.5%); Sableye (button_isle, 5%); Thievul (westmarch, 5%) | Blast furnace; Break Emerald Ore; Brush suspicious blocks in Decaying Crypt Ruins (cobblemon); Brush suspicious blocks in Desert Pyramid; Brush suspicious blocks in Desert Well (worldgen feature) … | Block of Emerald |
|
||||||
| Ender Pearl (`minecraft:ender_pearl`) | Abra (highlands, ×0-1); Alakazam (highlands, ×0-3); Beldum (dripstone, ×0-1); Elgyem (southwold, ×0-1); Grumpig (frostpeak, ×0-2); Kadabra (highlands, ×0-2); Kirlia (westmarch, ×0-2); Metang (dripstone, ×0-2); Rabsca (sunscorch, ×1); Ralts (westmarch, ×0-1); Sigilyph (southwold, ×0-1); Unown (jungle, ×0-1) | Loot in Trial Chambers | Eye of Ender, Flame Orb, Life Orb, Light Ball, Light Clay, Link Cable, Smoke Ball, Toxic Orb |
|
| Ender Pearl (`minecraft:ender_pearl`) | Abra (highlands, ×0-1); Alakazam (highlands, ×0-3); Beldum (dripstone, ×0-1); Elgyem (southwold, ×0-1); Grumpig (frostpeak, ×0-2); Kadabra (highlands, ×0-2); Kirlia (westmarch, ×0-2); Metang (dripstone, ×0-2); Rabsca (sunscorch, ×1); Ralts (westmarch, ×0-1); Sigilyph (southwold, ×0-1); Unown (jungle, ×0-1) | Loot in Trial Chambers | Eye of Ender, Flame Orb, Life Orb, Light Ball, Light Clay, Link Cable, Smoke Ball, Toxic Orb |
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import com.mojang.logging.LogUtils
|
|||||||
import dev.cobblesurvival.dev.BiomePreview
|
import dev.cobblesurvival.dev.BiomePreview
|
||||||
import dev.cobblesurvival.dev.OreVeins
|
import dev.cobblesurvival.dev.OreVeins
|
||||||
import dev.cobblesurvival.permadeath.Permadeath
|
import dev.cobblesurvival.permadeath.Permadeath
|
||||||
|
import dev.cobblesurvival.progress.Gates
|
||||||
|
import dev.cobblesurvival.progress.Progress
|
||||||
import dev.cobblesurvival.selftest.SelfTest
|
import dev.cobblesurvival.selftest.SelfTest
|
||||||
import dev.cobblesurvival.world.OnlyPokemon
|
import dev.cobblesurvival.world.OnlyPokemon
|
||||||
import dev.cobblesurvival.zone.Guardians
|
import dev.cobblesurvival.zone.Guardians
|
||||||
@@ -60,6 +62,7 @@ class CobbleSurvival {
|
|||||||
ZoneCondition.register()
|
ZoneCondition.register()
|
||||||
NeoForge.EVENT_BUS.addListener(Zones::onAddReloadListeners)
|
NeoForge.EVENT_BUS.addListener(Zones::onAddReloadListeners)
|
||||||
Guardians.register()
|
Guardians.register()
|
||||||
|
Gates.register()
|
||||||
ZoneHazards.register()
|
ZoneHazards.register()
|
||||||
LevelPressure.register()
|
LevelPressure.register()
|
||||||
SelfTest.register()
|
SelfTest.register()
|
||||||
@@ -94,6 +97,7 @@ class CobbleSurvival {
|
|||||||
})
|
})
|
||||||
.then(BiomePreview.command())
|
.then(BiomePreview.command())
|
||||||
.then(OreVeins.command())
|
.then(OreVeins.command())
|
||||||
|
.then(Progress.command())
|
||||||
.then(Commands.literal("zone").executes { context ->
|
.then(Commands.literal("zone").executes { context ->
|
||||||
val source = context.source
|
val source = context.source
|
||||||
val zone = Zones.at(source.level, BlockPos.containing(source.position))
|
val zone = Zones.at(source.level, BlockPos.containing(source.position))
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package dev.cobblesurvival.progress
|
||||||
|
|
||||||
|
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||||
|
import dev.cobblesurvival.CobbleSurvival
|
||||||
|
import net.minecraft.world.level.Level
|
||||||
|
import net.neoforged.fml.ModList
|
||||||
|
import java.lang.reflect.InvocationHandler
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
import java.lang.reflect.Proxy
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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's owner
|
||||||
|
* has unlocked that skill. Cobblebase looks the executor up on every tick, so the proxies take effect at once. One
|
||||||
|
* executor can serve several skills (all the finders), so the check is per skill, not per executor.
|
||||||
|
*/
|
||||||
|
object CobblebaseGate {
|
||||||
|
private const val CORE = "notlown.cobblebase.core"
|
||||||
|
|
||||||
|
val installed: Boolean get() = ModList.get().isLoaded("cobblebase")
|
||||||
|
|
||||||
|
internal val executorType by lazy { Class.forName("$CORE.SkillExecutor") }
|
||||||
|
internal val skillEntry by lazy { Class.forName("$CORE.SkillEntry") }
|
||||||
|
private val executors by lazy { Class.forName("$CORE.ExecutorRegistry").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") }
|
||||||
|
private val skillId by lazy { skillDef.getMethod("getId") }
|
||||||
|
|
||||||
|
/** Skill id → its definition (Cobblebase's SkillDef). */
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun skills(): Map<String, Any> {
|
||||||
|
val (type, instance) = skillRegistry
|
||||||
|
val all = type.getMethod("getAll").invoke(instance) as Map<String, Any>
|
||||||
|
return all.values.associateBy { skillId.invoke(it) as String }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun skillName(id: String): String? = skills()[id]?.let { skillDef.getMethod("getName").invoke(it) as String }
|
||||||
|
|
||||||
|
fun executor(name: String): Any? {
|
||||||
|
val (type, instance) = executors
|
||||||
|
return type.getMethod("get", String::class.java).invoke(instance, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The executor names the skills use (several skills can share one). */
|
||||||
|
fun executorNames(): Set<String> = skills().values.map { skillDef.getMethod("getExecutor").invoke(it) as String }.toSortedSet()
|
||||||
|
|
||||||
|
fun install() {
|
||||||
|
if (!installed) return
|
||||||
|
val (type, instance) = executors
|
||||||
|
val register = type.getMethod("register", String::class.java, executorType)
|
||||||
|
var wrapped = 0
|
||||||
|
for (name in executorNames()) {
|
||||||
|
val original = executor(name) ?: continue
|
||||||
|
if (isGated(original)) continue
|
||||||
|
register.invoke(instance, name, wrap(original))
|
||||||
|
wrapped++
|
||||||
|
}
|
||||||
|
CobbleSurvival.LOGGER.info("Progression gates in front of {} Cobblebase skill executors", wrapped)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isGated(executor: Any): Boolean =
|
||||||
|
Proxy.isProxyClass(executor.javaClass) && Proxy.getInvocationHandler(executor) is Gate
|
||||||
|
|
||||||
|
/** [original] behind the gates: `tick(level, pos, pokemonEntity, skillDef, skillEntry)` only runs if allowed. */
|
||||||
|
fun wrap(original: Any): Any = Proxy.newProxyInstance(executorType.classLoader, arrayOf(executorType), Gate(original))
|
||||||
|
|
||||||
|
private class Gate(private val original: Any) : InvocationHandler {
|
||||||
|
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
|
||||||
|
if (method.name == "tick" && args != null && args.size == 5) {
|
||||||
|
val level = args[0] as Level
|
||||||
|
val owner = (args[2] as PokemonEntity).pokemon.getOwnerUUID()
|
||||||
|
val skill = skillId.invoke(args[3]) as String
|
||||||
|
val server = level.server ?: return null
|
||||||
|
if (!Gates.skillOpen(Progress.of(server), owner, skill)) return null
|
||||||
|
}
|
||||||
|
return try {
|
||||||
|
method.invoke(original, *(args ?: emptyArray()))
|
||||||
|
} catch (e: InvocationTargetException) {
|
||||||
|
throw e.targetException
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package dev.cobblesurvival.progress
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import dev.cobblesurvival.CobbleSurvival
|
||||||
|
import dev.cobblesurvival.zone.Zones
|
||||||
|
import net.minecraft.ChatFormatting
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries
|
||||||
|
import net.minecraft.network.chat.Component
|
||||||
|
import net.minecraft.resources.ResourceLocation
|
||||||
|
import net.minecraft.server.level.ServerPlayer
|
||||||
|
import net.minecraft.server.packs.resources.ResourceManager
|
||||||
|
import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener
|
||||||
|
import net.minecraft.util.profiling.ProfilerFiller
|
||||||
|
import net.neoforged.neoforge.common.NeoForge
|
||||||
|
import net.neoforged.neoforge.event.AddReloadListenerEvent
|
||||||
|
import net.neoforged.neoforge.event.level.BlockEvent
|
||||||
|
import net.neoforged.neoforge.event.server.ServerStartedEvent
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Progression gates (Adrian, 2026-10-30: Cobblebase's skills and the power source stay in, "gated behind
|
||||||
|
* progression"): a Cobblebase skill works for a pastured Pokémon, and a gated block can be placed, only once its owner
|
||||||
|
* has beaten a given zone's Alpha guardian ([Progress]).
|
||||||
|
*
|
||||||
|
* The table is data, `data/cobblesurvival/progression_gates/<name>.json`:
|
||||||
|
* `{"cobblebase_skills": {"<skill id>": "<zone>" or null}, "blocks": {"<block id>": "<zone>"}}`; null means from the
|
||||||
|
* start, later files override earlier ones. A Cobblebase skill missing from the table stays off, so an update can't
|
||||||
|
* slip new skills past the gates; the self-test `gates_cover_every_cobblebase_skill` names any that are missing.
|
||||||
|
*/
|
||||||
|
object Gates : SimpleJsonResourceReloadListener(Gson(), "progression_gates") {
|
||||||
|
var skills: Map<String, String?> = emptyMap()
|
||||||
|
private set
|
||||||
|
var blocks: Map<ResourceLocation, String> = emptyMap()
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun register() {
|
||||||
|
NeoForge.EVENT_BUS.addListener<AddReloadListenerEvent> { it.addListener(this) }
|
||||||
|
NeoForge.EVENT_BUS.addListener(::onPlace)
|
||||||
|
NeoForge.EVENT_BUS.addListener<ServerStartedEvent> { CobblebaseGate.install() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apply(files: Map<ResourceLocation, JsonElement>, resources: ResourceManager, profiler: ProfilerFiller) {
|
||||||
|
val skillGates = LinkedHashMap<String, String?>()
|
||||||
|
val blockGates = LinkedHashMap<ResourceLocation, String>()
|
||||||
|
for ((_, element) in files.toSortedMap()) {
|
||||||
|
val json = element.asJsonObject
|
||||||
|
json.getAsJsonObject("cobblebase_skills")?.entrySet()?.forEach { (skill, zone) ->
|
||||||
|
skillGates[skill] = if (zone.isJsonNull) null else zone.asString
|
||||||
|
}
|
||||||
|
json.getAsJsonObject("blocks")?.entrySet()?.forEach { (block, zone) ->
|
||||||
|
blockGates[ResourceLocation.parse(block)] = zone.asString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
skills = skillGates
|
||||||
|
blocks = blockGates
|
||||||
|
CobbleSurvival.LOGGER.info("Loaded progression gates: {} Cobblebase skills, {} blocks", skills.size, blocks.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun skillOpen(progress: Progress, owner: UUID?, skill: String): Boolean {
|
||||||
|
if (!skills.containsKey(skill)) return false
|
||||||
|
val zone = skills[skill] ?: return true
|
||||||
|
return owner != null && zone in progress.beaten(owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onPlace(event: BlockEvent.EntityPlaceEvent) {
|
||||||
|
val player = event.entity as? ServerPlayer ?: return
|
||||||
|
if (player.isCreative) return
|
||||||
|
val zone = blocks[BuiltInRegistries.BLOCK.getKey(event.placedBlock.block)] ?: return
|
||||||
|
if (zone in Progress.of(player.server).beaten(player.uuid)) return
|
||||||
|
event.isCanceled = true
|
||||||
|
player.displayClientMessage(
|
||||||
|
Component.translatableWithFallback(
|
||||||
|
"message.cobblesurvival.gate.block", "Beat the guardian of %s to use this.", zoneName(zone),
|
||||||
|
).withStyle(ChatFormatting.RED), true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tells [player] what beating [zone]'s guardian unlocked for them. */
|
||||||
|
fun announceUnlocks(player: ServerPlayer, zone: String) {
|
||||||
|
val unlocked = skills.filterValues { it == zone }.keys.map { CobblebaseGate.skillName(it) ?: it } +
|
||||||
|
blocks.filterValues { it == zone }.keys.map { BuiltInRegistries.BLOCK.get(it).name.string }
|
||||||
|
if (unlocked.isEmpty()) return
|
||||||
|
player.sendSystemMessage(
|
||||||
|
Component.translatableWithFallback(
|
||||||
|
"message.cobblesurvival.gate.unlocked", "Unlocked at your base: %s", unlocked.joinToString(),
|
||||||
|
).withStyle(ChatFormatting.GREEN)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun zoneName(zone: String) = Zones.byId[zone]?.name ?: zone
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package dev.cobblesurvival.progress
|
||||||
|
|
||||||
|
import com.mojang.brigadier.arguments.StringArgumentType
|
||||||
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder
|
||||||
|
import dev.cobblesurvival.zone.Guardians
|
||||||
|
import net.minecraft.commands.CommandSourceStack
|
||||||
|
import net.minecraft.commands.Commands
|
||||||
|
import net.minecraft.commands.SharedSuggestionProvider
|
||||||
|
import net.minecraft.commands.arguments.EntityArgument
|
||||||
|
import net.minecraft.core.HolderLookup
|
||||||
|
import net.minecraft.nbt.CompoundTag
|
||||||
|
import net.minecraft.nbt.ListTag
|
||||||
|
import net.minecraft.nbt.StringTag
|
||||||
|
import net.minecraft.nbt.Tag
|
||||||
|
import net.minecraft.network.chat.Component
|
||||||
|
import net.minecraft.server.MinecraftServer
|
||||||
|
import net.minecraft.world.level.saveddata.SavedData
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A player's progression: the zones whose Alpha guardian they have helped beat. [Guardians] records a win for every
|
||||||
|
* player in the battle; [Gates] unlocks base skills and blocks from it. Saved with the overworld.
|
||||||
|
*
|
||||||
|
* Operators: `/cobblesurvival progress <player>` lists it, `... grant|revoke <player> <zone>` changes it.
|
||||||
|
*/
|
||||||
|
class Progress : SavedData() {
|
||||||
|
private val beaten = HashMap<UUID, MutableSet<String>>()
|
||||||
|
|
||||||
|
fun beaten(player: UUID): Set<String> = beaten[player].orEmpty()
|
||||||
|
|
||||||
|
/** Records that [player] beat [zone]'s guardian; false if they had already. */
|
||||||
|
fun grant(player: UUID, zone: String): Boolean =
|
||||||
|
beaten.getOrPut(player) { HashSet() }.add(zone).also { if (it) setDirty() }
|
||||||
|
|
||||||
|
fun revoke(player: UUID, zone: String): Boolean = (beaten[player]?.remove(zone) == true).also { if (it) setDirty() }
|
||||||
|
|
||||||
|
override fun save(tag: CompoundTag, registries: HolderLookup.Provider): CompoundTag {
|
||||||
|
for ((player, zones) in beaten) {
|
||||||
|
tag.put(player.toString(), ListTag().apply { zones.sorted().forEach { add(StringTag.valueOf(it)) } })
|
||||||
|
}
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val FACTORY = Factory(::Progress, ::load)
|
||||||
|
|
||||||
|
fun of(server: MinecraftServer): Progress = server.overworld().dataStorage.computeIfAbsent(FACTORY, "cobblesurvival_progress")
|
||||||
|
|
||||||
|
private fun load(tag: CompoundTag, registries: HolderLookup.Provider): Progress {
|
||||||
|
val progress = Progress()
|
||||||
|
for (key in tag.allKeys) {
|
||||||
|
val zones = tag.getList(key, Tag.TAG_STRING.toInt()).map { it.asString }
|
||||||
|
progress.beaten[UUID.fromString(key)] = zones.toHashSet()
|
||||||
|
}
|
||||||
|
return progress
|
||||||
|
}
|
||||||
|
|
||||||
|
fun command(): LiteralArgumentBuilder<CommandSourceStack> {
|
||||||
|
val zone = Commands.argument("zone", StringArgumentType.word())
|
||||||
|
.suggests { _, builder -> SharedSuggestionProvider.suggest(Guardians.definitions.keys, builder) }
|
||||||
|
fun change(grant: Boolean) = Commands.literal(if (grant) "grant" else "revoke")
|
||||||
|
.then(Commands.argument("player", EntityArgument.player()).then(zone.executes { context ->
|
||||||
|
val player = EntityArgument.getPlayer(context, "player")
|
||||||
|
val id = StringArgumentType.getString(context, "zone")
|
||||||
|
val progress = of(context.source.server)
|
||||||
|
val changed = if (grant) progress.grant(player.uuid, id) else progress.revoke(player.uuid, id)
|
||||||
|
context.source.sendSuccess({ Component.literal(
|
||||||
|
"${player.gameProfile.name}: $id ${if (grant) "granted" else "revoked"}${if (changed) "" else " (no change)"}") }, true)
|
||||||
|
1
|
||||||
|
}))
|
||||||
|
return Commands.literal("progress")
|
||||||
|
.then(Commands.argument("player", EntityArgument.player()).executes { context ->
|
||||||
|
val player = EntityArgument.getPlayer(context, "player")
|
||||||
|
val zones = of(context.source.server).beaten(player.uuid).sorted()
|
||||||
|
context.source.sendSuccess({ Component.literal(
|
||||||
|
"${player.gameProfile.name} beat: ${zones.joinToString().ifEmpty { "no guardian yet" }}") }, false)
|
||||||
|
1
|
||||||
|
})
|
||||||
|
.then(change(grant = true))
|
||||||
|
.then(change(grant = false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import com.cobblemon.mod.common.battles.BattleBuilder
|
|||||||
import com.cobblemon.mod.common.battles.MoveActionResponse
|
import com.cobblemon.mod.common.battles.MoveActionResponse
|
||||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||||
import com.cobblemon.mod.common.util.isInBattle
|
import com.cobblemon.mod.common.util.isInBattle
|
||||||
|
import dev.cobblesurvival.progress.Progress
|
||||||
import dev.cobblesurvival.zone.Guardians
|
import dev.cobblesurvival.zone.Guardians
|
||||||
import dev.cobblesurvival.zone.Zones
|
import dev.cobblesurvival.zone.Zones
|
||||||
import net.minecraft.core.registries.BuiltInRegistries
|
import net.minecraft.core.registries.BuiltInRegistries
|
||||||
@@ -14,7 +15,7 @@ import kotlin.math.abs
|
|||||||
/**
|
/**
|
||||||
* Roadmap Phase 3, Alpha guardians (Adrian: fixed spots, back 5 hours after being beaten). On Button Isle's spot:
|
* Roadmap Phase 3, Alpha guardians (Adrian: fixed spots, back 5 hours after being beaten). On Button Isle's spot:
|
||||||
* a player arriving makes the guardian appear, big and uncatchable at its level; beating it in a battle gives the
|
* a player arriving makes the guardian appear, big and uncatchable at its level; beating it in a battle gives the
|
||||||
* player its drops; it stays away, and returns once its time is up. Runs in a batch of its own, since the guardian
|
* player its drops and the zone as progress (which unlocks base skills); it stays away, and returns once its time is up. Runs in a batch of its own, since the guardian
|
||||||
* state is shared.
|
* state is shared.
|
||||||
*/
|
*/
|
||||||
object GuardianTests {
|
object GuardianTests {
|
||||||
@@ -61,6 +62,7 @@ object GuardianTests {
|
|||||||
val have = player.inventory.countItem(BuiltInRegistries.ITEM.get(item))
|
val have = player.inventory.countItem(BuiltInRegistries.ITEM.get(item))
|
||||||
s.check(have >= count) { "the player got $have of $item from the guardian, not $count" }
|
s.check(have >= count) { "the player got $have of $item from the guardian, not $count" }
|
||||||
}
|
}
|
||||||
|
s.check(ZONE in Progress.of(level.server).beaten(player.uuid)) { "beating the guardian didn't count as progress" }
|
||||||
}
|
}
|
||||||
.thenIdle(100)
|
.thenIdle(100)
|
||||||
.thenExecute {
|
.thenExecute {
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package dev.cobblesurvival.selftest
|
||||||
|
|
||||||
|
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||||
|
import dev.cobblesurvival.progress.CobblebaseGate
|
||||||
|
import dev.cobblesurvival.progress.Gates
|
||||||
|
import dev.cobblesurvival.progress.Progress
|
||||||
|
import dev.cobblesurvival.zone.Guardians
|
||||||
|
import net.minecraft.core.BlockPos
|
||||||
|
import net.minecraft.core.Direction
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries
|
||||||
|
import net.minecraft.gametest.framework.GameTestAssertException
|
||||||
|
import net.minecraft.resources.ResourceLocation
|
||||||
|
import net.minecraft.world.InteractionHand
|
||||||
|
import net.minecraft.world.item.ItemStack
|
||||||
|
import net.minecraft.world.item.Items
|
||||||
|
import net.minecraft.world.level.Level
|
||||||
|
import net.minecraft.world.level.block.Blocks
|
||||||
|
import net.minecraft.world.phys.BlockHitResult
|
||||||
|
import net.minecraft.world.phys.Vec3
|
||||||
|
import java.lang.reflect.Proxy
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Progression gates (Adrian, 2026-10-30): Cobblebase's base skills and Poke Power's Power Block open up as a player
|
||||||
|
* beats the zones' Alpha guardians (`data/cobblesurvival/progression_gates/`).
|
||||||
|
*/
|
||||||
|
object ProgressionTests {
|
||||||
|
private val POWER_BLOCK = ResourceLocation.parse("poke_power:power_block")
|
||||||
|
|
||||||
|
fun all() = listOf(
|
||||||
|
SelfTestCase("gates_cover_every_cobblebase_skill", 20, ::gatesCoverEverySkill),
|
||||||
|
SelfTestCase("cobblebase_skills_wait_for_guardians", 20, ::skillsWaitForGuardians),
|
||||||
|
SelfTestCase("power_block_waits_for_guardian", 20, ::powerBlockWaits),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun gatesCoverEverySkill(s: Scenario) {
|
||||||
|
s.check(CobblebaseGate.installed) { "Cobblebase isn't installed" }
|
||||||
|
val skills = CobblebaseGate.skills().keys
|
||||||
|
val ungated = skills - Gates.skills.keys
|
||||||
|
s.check(ungated.isEmpty()) { "Cobblebase skills without a gate (they stay off): ${ungated.sorted()}" }
|
||||||
|
val stale = Gates.skills.keys - skills
|
||||||
|
s.check(stale.isEmpty()) { "gates for skills Cobblebase doesn't have: ${stale.sorted()}" }
|
||||||
|
val zones = (Gates.skills.values.filterNotNull() + Gates.blocks.values).toSet()
|
||||||
|
val noGuardian = zones - Guardians.definitions.keys
|
||||||
|
s.check(noGuardian.isEmpty()) { "gates name zones without a guardian: ${noGuardian.sorted()}" }
|
||||||
|
val noBlock = Gates.blocks.keys.filterNot { BuiltInRegistries.BLOCK.containsKey(it) }
|
||||||
|
s.check(noBlock.isEmpty()) { "gates for blocks that don't exist: $noBlock" }
|
||||||
|
val open = CobblebaseGate.executorNames().filterNot { name -> CobblebaseGate.executor(name)?.let(CobblebaseGate::isGated) == true }
|
||||||
|
s.check(open.isEmpty()) { "Cobblebase executors not behind the gates: $open" }
|
||||||
|
s.helper.succeed()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A probe behind the same gate as Cobblebase's executors, called the way Cobblebase calls them. */
|
||||||
|
private fun skillsWaitForGuardians(s: Scenario) {
|
||||||
|
val level = s.level
|
||||||
|
val player = s.player()
|
||||||
|
val pokemon = s.give(player, s.pokemon("pikachu", 10))
|
||||||
|
s.check(pokemon.getOwnerUUID() == player.uuid) { "the test Pokémon has no owner" }
|
||||||
|
val entity = PokemonEntity(level, pokemon)
|
||||||
|
val (openSkill, _) = Gates.skills.entries.firstOrNull { it.value == null } ?: throw GameTestAssertException("no skill is open from the start")
|
||||||
|
val (lockedSkill, zone) = Gates.skills.entries.firstOrNull { it.value != null } ?: throw GameTestAssertException("no skill is gated")
|
||||||
|
val progress = Progress.of(level.server)
|
||||||
|
s.afterTest { progress.revoke(player.uuid, zone!!) }
|
||||||
|
|
||||||
|
var calls = 0
|
||||||
|
val type = CobblebaseGate.executorType
|
||||||
|
val probe = Proxy.newProxyInstance(type.classLoader, arrayOf(type)) { _, method, _ -> if (method.name == "tick") calls++; null }
|
||||||
|
val gated = 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 run(skill: String) {
|
||||||
|
val def = CobblebaseGate.skills()[skill] ?: throw GameTestAssertException("Cobblebase has no skill $skill")
|
||||||
|
tick.invoke(gated, level, entity.blockPosition(), entity, def, entry.newInstance(skill, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
run(openSkill)
|
||||||
|
s.check(calls == 1) { "$openSkill is open from the start but didn't run" }
|
||||||
|
run(lockedSkill)
|
||||||
|
s.check(calls == 1) { "$lockedSkill ran before the guardian of $zone was beaten" }
|
||||||
|
progress.grant(player.uuid, zone!!)
|
||||||
|
run(lockedSkill)
|
||||||
|
s.check(calls == 2) { "$lockedSkill didn't run after the guardian of $zone was beaten" }
|
||||||
|
s.helper.succeed()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun powerBlockWaits(s: Scenario) {
|
||||||
|
val level = s.level
|
||||||
|
val zone = Gates.blocks[POWER_BLOCK] ?: throw GameTestAssertException("the Power Block has no gate")
|
||||||
|
val item = BuiltInRegistries.ITEM.get(POWER_BLOCK)
|
||||||
|
s.check(item != Items.AIR) { "Poke Power isn't installed" }
|
||||||
|
val player = s.player()
|
||||||
|
val progress = Progress.of(level.server)
|
||||||
|
s.afterTest { progress.revoke(player.uuid, zone) }
|
||||||
|
val floor = s.helper.absolutePos(BlockPos(7, 2, 7))
|
||||||
|
level.setBlockAndUpdate(floor, Blocks.STONE.defaultBlockState())
|
||||||
|
val target = floor.above()
|
||||||
|
fun place() {
|
||||||
|
player.setItemInHand(InteractionHand.MAIN_HAND, ItemStack(item))
|
||||||
|
val hit = BlockHitResult(Vec3.atCenterOf(floor).add(0.0, 0.5, 0.0), Direction.UP, floor, false)
|
||||||
|
player.gameMode.useItemOn(player, level, player.mainHandItem, InteractionHand.MAIN_HAND, hit)
|
||||||
|
}
|
||||||
|
fun placed() = BuiltInRegistries.BLOCK.getKey(level.getBlockState(target).block) == POWER_BLOCK
|
||||||
|
|
||||||
|
place()
|
||||||
|
s.check(!placed()) { "a player who hasn't beaten the guardian of $zone placed a Power Block" }
|
||||||
|
progress.grant(player.uuid, zone)
|
||||||
|
place()
|
||||||
|
s.check(placed()) { "the Power Block couldn't be placed after beating the guardian of $zone: ${level.getBlockState(target)}" }
|
||||||
|
s.helper.succeed()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,7 +51,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() + DropTests.all()
|
VeinTests.all() + DropTests.all() + ProgressionTests.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()
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
|||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
import dev.cobblesurvival.CobbleSurvival
|
import dev.cobblesurvival.CobbleSurvival
|
||||||
|
import dev.cobblesurvival.progress.Gates
|
||||||
|
import dev.cobblesurvival.progress.Progress
|
||||||
import net.minecraft.ChatFormatting
|
import net.minecraft.ChatFormatting
|
||||||
import net.minecraft.core.BlockPos
|
import net.minecraft.core.BlockPos
|
||||||
import net.minecraft.core.HolderLookup
|
import net.minecraft.core.HolderLookup
|
||||||
@@ -38,6 +40,7 @@ import kotlin.math.abs
|
|||||||
* `respawn_hours` of game time later (Adrian: fixed spots, 5 hours). It only appears while a player is near, and
|
* `respawn_hours` of game time later (Adrian: fixed spots, 5 hours). It only appears while a player is near, and
|
||||||
* wanders no further than [LEASH] blocks from its spot. Definitions: `data/cobblesurvival/guardians/<zone>.json`,
|
* wanders no further than [LEASH] blocks from its spot. Definitions: `data/cobblesurvival/guardians/<zone>.json`,
|
||||||
* written by tools/zone_spawns.py; state (which entity, when beaten, where it stands) lives in the world save.
|
* written by tools/zone_spawns.py; state (which entity, when beaten, where it stands) lives in the world save.
|
||||||
|
* Every player in the winning battle gets the drops and the zone in their [Progress], which unlocks base skills.
|
||||||
*/
|
*/
|
||||||
object Guardians : SimpleJsonResourceReloadListener(Gson(), "guardians") {
|
object Guardians : SimpleJsonResourceReloadListener(Gson(), "guardians") {
|
||||||
/** Key in the guardian Pokémon's persistent data; its value is the zone id. */
|
/** Key in the guardian Pokémon's persistent data; its value is the zone id. */
|
||||||
@@ -178,6 +181,7 @@ object Guardians : SimpleJsonResourceReloadListener(Gson(), "guardians") {
|
|||||||
"The guardian of %s is beaten! It returns in %s hours.", zoneName, hours,
|
"The guardian of %s is beaten! It returns in %s hours.", zoneName, hours,
|
||||||
).withStyle(ChatFormatting.GOLD)
|
).withStyle(ChatFormatting.GOLD)
|
||||||
)
|
)
|
||||||
|
if (Progress.of(player.server).grant(player.uuid, zone)) Gates.announceUnlocks(player, zone)
|
||||||
}
|
}
|
||||||
CobbleSurvival.LOGGER.info("Guardian of {} beaten by {}", zone, players.joinToString { it.gameProfile.name })
|
CobbleSurvival.LOGGER.info("Guardian of {} beaten by {}", zone, players.joinToString { it.gameProfile.name })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,7 @@
|
|||||||
"message.cobblesurvival.permadeath.lost": "%s fainted and is gone for good.",
|
"message.cobblesurvival.permadeath.lost": "%s fainted and is gone for good.",
|
||||||
"message.cobblesurvival.permadeath.saved": "%s's Revival Charm saved it and crumbled to dust.",
|
"message.cobblesurvival.permadeath.saved": "%s's Revival Charm saved it and crumbled to dust.",
|
||||||
"message.cobblesurvival.guardian.beaten": "The guardian of %s is beaten! It returns in %s hours.",
|
"message.cobblesurvival.guardian.beaten": "The guardian of %s is beaten! It returns in %s hours.",
|
||||||
"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.block": "Beat the guardian of %s to use this.",
|
||||||
|
"message.cobblesurvival.gate.unlocked": "Unlocked at your base: %s"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"general": {
|
||||||
|
"jobSearchRadius": 10,
|
||||||
|
"enableSafetyTeleport": true,
|
||||||
|
"safetyTeleportDistance": 30,
|
||||||
|
"enableUnstickTeleport": true,
|
||||||
|
"keepEntitiesAlive": false,
|
||||||
|
"enforceVersionCheck": false,
|
||||||
|
"enableConsoleLogging": false,
|
||||||
|
"mainButtonCorner": "BOTTOM_RIGHT"
|
||||||
|
},
|
||||||
|
"cry": {
|
||||||
|
"cryEnabled": true,
|
||||||
|
"cryVolume": 30
|
||||||
|
},
|
||||||
|
"skills": {
|
||||||
|
"harvestingEnabled": true,
|
||||||
|
"fishingEnabled": true,
|
||||||
|
"guardEnabled": true,
|
||||||
|
"healingEnabled": true,
|
||||||
|
"recruitingEnabled": true,
|
||||||
|
"generationEnabled": true,
|
||||||
|
"utilityEnabled": true,
|
||||||
|
"gathererEnabled": true,
|
||||||
|
"gathererPickupPlayerDrops": true,
|
||||||
|
"gathererCooldownSeconds": 10
|
||||||
|
},
|
||||||
|
"passiveXp": {
|
||||||
|
"enabled": true,
|
||||||
|
"xpPercent": 5,
|
||||||
|
"intervalSeconds": 60
|
||||||
|
},
|
||||||
|
"mentor": {
|
||||||
|
"mentorEnabled": true,
|
||||||
|
"mentorMaxBoost": 1.0
|
||||||
|
},
|
||||||
|
"recruiterRates": {
|
||||||
|
"commonRate": 93.8,
|
||||||
|
"uncommonRate": 5.0,
|
||||||
|
"rareRate": 1.0,
|
||||||
|
"ultraRareRate": 0.2,
|
||||||
|
"spawnCooldownSeconds": 300
|
||||||
|
},
|
||||||
|
"irrigator": {
|
||||||
|
"irrigatorEnabled": true,
|
||||||
|
"irrigatorCooldownSeconds": 3,
|
||||||
|
"irrigatorRadius": 1
|
||||||
|
},
|
||||||
|
"finder": {
|
||||||
|
"finderCooldownSeconds": 240
|
||||||
|
},
|
||||||
|
"buffs": {
|
||||||
|
"speedBoostEnabled": true,
|
||||||
|
"strengthBoostEnabled": true,
|
||||||
|
"resistanceBoostEnabled": true,
|
||||||
|
"nightVisionEnabled": true,
|
||||||
|
"waterBreathingEnabled": true,
|
||||||
|
"jumpBoostEnabled": true,
|
||||||
|
"hasteBoostEnabled": true,
|
||||||
|
"saturationBoostEnabled": true,
|
||||||
|
"auraEnabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#Blocks Config
|
||||||
|
[blocks]
|
||||||
|
|
||||||
|
#Power Block Config
|
||||||
|
[blocks.powerBlock]
|
||||||
|
#The amount of power gained per poke level
|
||||||
|
# Default: 50
|
||||||
|
# Range: > 1
|
||||||
|
powerPerPokeLevel = 50
|
||||||
|
#The amount of power that a power block can store
|
||||||
|
# Default: 120000
|
||||||
|
# Range: 1 ~ 9223372036854775807
|
||||||
|
capacity = 120000
|
||||||
|
#The maximum amount of power that can be extracted from a power block per tick
|
||||||
|
# Default: 16000
|
||||||
|
# Range: 1 ~ 9223372036854775807
|
||||||
|
maxExtract = 16000
|
||||||
|
|
||||||
|
#Cable Config
|
||||||
|
[blocks.cable]
|
||||||
|
#The amount of power that a cable can store
|
||||||
|
# Default: 32000
|
||||||
|
# Range: 1 ~ 9223372036854775807
|
||||||
|
capacity = 32000
|
||||||
|
#The maximum amount of power that can be extracted from a cable per tick
|
||||||
|
# Default: 16000
|
||||||
|
# Range: 1 ~ 9223372036854775807
|
||||||
|
maxExtract = 16000
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
hash-format = "sha256"
|
hash-format = "sha256"
|
||||||
|
|
||||||
|
[[files]]
|
||||||
|
file = "config/cobblebase.json"
|
||||||
|
hash = "5cf75685938ffd7771c204ee1603214319f33dd2a40df99cedc8d92c76304854"
|
||||||
|
|
||||||
[[files]]
|
[[files]]
|
||||||
file = "config/cobblemon/main.json"
|
file = "config/cobblemon/main.json"
|
||||||
hash = "95777507e9ca8aecb024db1cf25688592914b6e7c75b48d52d41893d5bfc821a"
|
hash = "95777507e9ca8aecb024db1cf25688592914b6e7c75b48d52d41893d5bfc821a"
|
||||||
@@ -28,6 +32,10 @@ hash = "ca348d022ad97e74ec51a896060e8fcfc079cf0d34ef116a967e72f60bdb3381"
|
|||||||
file = "config/matthiesen_core/metrics.properties"
|
file = "config/matthiesen_core/metrics.properties"
|
||||||
hash = "e3833b9a5d62612cc9f8886b8bbc4a6fbb0f5c531aede06b1a580ba522f576b4"
|
hash = "e3833b9a5d62612cc9f8886b8bbc4a6fbb0f5c531aede06b1a580ba522f576b4"
|
||||||
|
|
||||||
|
[[files]]
|
||||||
|
file = "config/poke_power/server.toml"
|
||||||
|
hash = "5fa46ca93c0f7e243ed1af1093e9663e1cccfb4c0ab0ba64911b5a8b7e60acee"
|
||||||
|
|
||||||
[[files]]
|
[[files]]
|
||||||
file = "config/regenerating_ore_veins/areas.json"
|
file = "config/regenerating_ore_veins/areas.json"
|
||||||
hash = "79a3e379b5d9a142930acc1b599f3bf773c2dd6ee578239181f9720676f876ad"
|
hash = "79a3e379b5d9a142930acc1b599f3bf773c2dd6ee578239181f9720676f876ad"
|
||||||
@@ -99,6 +107,11 @@ file = "mods/balm.pw.toml"
|
|||||||
hash = "603cdc47142b5aa5729cbbf265a473574c9e2b55561de4318e9842d3d4b3b995"
|
hash = "603cdc47142b5aa5729cbbf265a473574c9e2b55561de4318e9842d3d4b3b995"
|
||||||
metafile = true
|
metafile = true
|
||||||
|
|
||||||
|
[[files]]
|
||||||
|
file = "mods/cobblebase.pw.toml"
|
||||||
|
hash = "586a0c3388e78fea91123f6c88907cc2cc2c5f2b87a15d7c050787d2c8c78cee"
|
||||||
|
metafile = true
|
||||||
|
|
||||||
[[files]]
|
[[files]]
|
||||||
file = "mods/cobblehardcoremon.pw.toml"
|
file = "mods/cobblehardcoremon.pw.toml"
|
||||||
hash = "1ec1dbf7caf20e38c7fe0ffa8ac8697f8dbfe431a31683119f43b81d27c6b41a"
|
hash = "1ec1dbf7caf20e38c7fe0ffa8ac8697f8dbfe431a31683119f43b81d27c6b41a"
|
||||||
@@ -109,6 +122,11 @@ file = "mods/cobblemon-fight-or-flight-reborn.pw.toml"
|
|||||||
hash = "227eea45819938ae2e0911d825e9ccf0e0236b1a36e46627204d8aa53becc9e7"
|
hash = "227eea45819938ae2e0911d825e9ccf0e0236b1a36e46627204d8aa53becc9e7"
|
||||||
metafile = true
|
metafile = true
|
||||||
|
|
||||||
|
[[files]]
|
||||||
|
file = "mods/cobblemon-poke-power.pw.toml"
|
||||||
|
hash = "29ccd4c39e5af1a2fb63a40bf67ecd03bcced42e9e1edf296b26fd1847740a54"
|
||||||
|
metafile = true
|
||||||
|
|
||||||
[[files]]
|
[[files]]
|
||||||
file = "mods/cobblemon.pw.toml"
|
file = "mods/cobblemon.pw.toml"
|
||||||
hash = "e6bed1ffe881690add7bc456fd8a71c136b01ea4f39683648ae7b0e404329d83"
|
hash = "e6bed1ffe881690add7bc456fd8a71c136b01ea4f39683648ae7b0e404329d83"
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
name = "Cobblebase"
|
||||||
|
filename = "cobblebase-neoforge-2.0.1+1.8.0.jar"
|
||||||
|
side = "both"
|
||||||
|
|
||||||
|
[download]
|
||||||
|
url = "https://cdn.modrinth.com/data/kPojQdms/versions/pFYNQ6W2/cobblebase-neoforge-2.0.1%2B1.8.0.jar"
|
||||||
|
hash-format = "sha512"
|
||||||
|
hash = "e7ea95a4b6df6f71ad0c3b6c45038d69267799cfe8a1b3a3b3501077755e2a2e5d16bc7d3d28fdca698aa06f0e33255ee156773f356cbaf73a3e428f86b3806b"
|
||||||
|
|
||||||
|
[update]
|
||||||
|
[update.modrinth]
|
||||||
|
mod-id = "kPojQdms"
|
||||||
|
version = "pFYNQ6W2"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
name = "Cobblemon Poke Power"
|
||||||
|
filename = "poke-power-neoforge-1.3.1.jar"
|
||||||
|
side = "both"
|
||||||
|
|
||||||
|
[download]
|
||||||
|
url = "https://cdn.modrinth.com/data/lIn7oRFm/versions/vGpaTxmN/poke-power-neoforge-1.3.1.jar"
|
||||||
|
hash-format = "sha512"
|
||||||
|
hash = "7a61fcb36918fd95914fd1d7614ba1a22b3e59451229ea8f8aeb1853ab79a246962cc6e2f208b0c97c24e46ccb148172649cd27ed997242743a79968631da466"
|
||||||
|
|
||||||
|
[update]
|
||||||
|
[update.modrinth]
|
||||||
|
mod-id = "lIn7oRFm"
|
||||||
|
version = "vGpaTxmN"
|
||||||
+1
-1
@@ -6,7 +6,7 @@ pack-format = "packwiz:1.1.0"
|
|||||||
[index]
|
[index]
|
||||||
file = "index.toml"
|
file = "index.toml"
|
||||||
hash-format = "sha256"
|
hash-format = "sha256"
|
||||||
hash = "d3263f05660d4365aad10cef3f235c204ddc497118675165714f95d378de3d1f"
|
hash = "f13be586ab9aaa36fb1db267f406efd31c227e77f20ed6e971306c164e15757f"
|
||||||
|
|
||||||
[versions]
|
[versions]
|
||||||
minecraft = "1.21.1"
|
minecraft = "1.21.1"
|
||||||
|
|||||||
Reference in new issue
Block a user