diff --git a/docs/addon-status.md b/docs/addon-status.md index 6ebf3b2..5048377 100644 --- a/docs/addon-status.md +++ b/docs/addon-status.md @@ -40,6 +40,7 @@ Checked 2026-10-28 against Cobblemon **1.8.4** (NeoForge, MC 1.21.1). Re-check b | Cobblemon Fight or Flight Reborn | wild Pokémon flee or fight back (Phase 2) | `fightorflight-neoforge-0.11.1.jar` (MR `OD7X9rYz`, CF `9156010`) | MR + CF | ✅ Declares `[1.8.2,)` | | Cobblemon TP-Pastures | pastured Pokémon teleported back, not recalled (Phase 2) | `cobblemon_tp_pastures-1.21.1-1.3.0.jar` (MR `so0hyqC2`) | MR + CF | ⚠️ Declares `[1.7.1,)`; loads on 1.8.4, behaviour still to be checked in-game | | Lootr | loot containers refill 1 hour after first opened, loot per player (Phase 2) | `lootr-neoforge-1.21.1-1.11.38.127.jar` (MR `SPOrIzX8`, CF `8968561`) | MR + CF | n/a (no Cobblemon dependency); converts Cobblemon's loot chests too | +| Cobblemon HMs | field moves (Phase 5) | `cobblemon-hms-neoforge-4.1.0.jar` (MR `uC02CqRy`) | MR (CF link not resolved yet) | ✅ Declares Cobblemon ≥ 1.8.0; loads and runs on 1.8.4 (self-tests, incl. Rock Smash on a regenerating vein). Added 2026-10-31 | | Better Advanced Tooltips | none: ships *inside* KubeJS as jar-in-jar | — | — | Modrinth marks it as a required KubeJS dependency, but KubeJS declares it optional and embeds it. Not referenced separately | JEI declares Minecraft `[1.21, 1.21.1)` in every 1.21.1 build, which a strict reading excludes; NeoForge 21.1.265 loads it anyway (server boot checked 2026-10-28). diff --git a/docs/balance.md b/docs/balance.md index 0e688c7..2bc549b 100644 --- a/docs/balance.md +++ b/docs/balance.md @@ -27,6 +27,7 @@ Every tunable lives in a config file or in this table. **Placeholder** means gue | Guardian strength | strongest Pokémon spawning in the zone, band top + 5 levels, 1.5× size, uncatchable | Decided (Adrian, 2026-10-30) | `tools/zone_spawns.py` (`GUARDIAN_*`), `world/zones.json` → `guardian.species` | | | Guardian drops | Button Isle 10 Poké Balls + 4 Potions; Southwold 6 Great Balls + 4 Exp. Candy S; Westmarch and Jungle Mire Waders + 4 Pecha Berries + 2 Exp. Candy M; Mire and Highlands 3 Icy Rocks + 4 Super Potions; Sunscorch 3 Heat Rocks + 2 Exp. Candy L; Frostpeak and Deep Dark 3 Icy Rocks + 2 Exp. Candy L; Deep Sea Mystic Water + 3 Exp. Candy M | Decided (Adrian, 2026-10-30) | `world/zones.json` → `guardian.drops` | 3 rocks make one full fur or hide set | | Mire poison | Poison I for 7 s, renewed every 5 s while in the Mire without Mire Waders | Decided (Adrian, 2026-10-30) | `datapacks/core/data/cobblesurvival/zone_hazards/mire.json` | Vanilla Poison stops at half a heart | +| Field moves (Cobblemon HMs) | mod defaults: all 39 abilities on, no level minimum, no fullness or friendship cost; Rock Smash breaks up to 32 connected ore blocks with no cooldown | Placeholder | `config/cobblemon_hms.json` (generated by the mod; the pack ships none yet) | Rock Smash mines any ore, whatever pickaxe the player has. HMs' `minLevel` is the player's XP level, not the Pokémon's | | 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 | | 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`) | | diff --git a/docs/checklists/phase-5.md b/docs/checklists/phase-5.md new file mode 100644 index 0000000..ef1eca7 --- /dev/null +++ b/docs/checklists/phase-5.md @@ -0,0 +1,23 @@ +# Phase 5 – checks for Adrian + +## Decided + +**2026-10-31:** the field moves come from Cobblemon HMs instead of our own system. Mining ore is the one hard requirement, the other moves are nice to have. Furnaces are lit by putting a Pokémon inside a Phase 6 machine, not by a field move. Details: `docs/design.md` → "Phase 5: field moves from Cobblemon HMs". + +## Built so far + +- **Cobblemon HMs** 4.1.0 is in the pack with its default config (39 abilities, all on). +- **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. + +## In game (Prism instance) + +- [ ] 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. +- [ ] Look at the other abilities your Pokémon offer on the wheel (Cut, Flash, Strength, Surf boosts, ...) and note any you want switched off or tuned. + +## Ask Adrian: Phase 5 decisions + +1. **Progression.** HMs' abilities all work from the start. Should some wait for guardians like Cobblebase's skills (Rock Smash after an early one, so tiers still matter at first)? HMs' own `minLevel` is the *player's* XP level, which the island doesn't use otherwise. +2. **Zone hazards.** Some abilities get around them: Flash (night vision, lights up the Deep Dark), Lava Plume (fire immunity and walking on lava in the Basalt Furnace), Magnet Rise (flying), Lucky Chant (more ore). Leave them on, switch them off, or gate them behind the zone's guardian? +3. **Party passives.** The roadmap's other half (e.g. Haste with a Rock type, warmth from a Fire type in Frostpeak; your idea of effects from shoulder Pokémon). Still wanted on top of HMs' auras? diff --git a/docs/design.md b/docs/design.md index 62c12a6..ccfc624 100644 --- a/docs/design.md +++ b/docs/design.md @@ -179,6 +179,16 @@ The roadmap lists four mods as prior art; on Cobblemon 1.8 / NeoForge 1.21.1 tod - [Cobblemon Environment Interactions](https://modrinth.com/project/cobblemon-environment-interactions) 3.1.0 (NeoForge, Apache-2.0, Cobblemon ≥ 1.7.3): type-based mining and utilities while riding or commanding one companion; not move-based. - Cobblemon Field Moves (Fabric, Cobblemon 1.6), Cobblemon Map Kit (Fabric), SceneBlocks (special blocks, custom license), CobbleStrike (real-time attacks): no fit. +## Phase 5: field moves from Cobblemon HMs (Adrian, 2026-10-31) + +Adrian: use Cobblemon HMs. Mining ore is the one hard requirement; the other field moves are nice to have, and Flamethrower in the roadmap was only an example. Furnaces are not lit by a field move: a Pokémon goes *inside* a Phase 6 multiblock instead. So the companion mod doesn't build the roadmap's own `field_moves` system for now; HMs replaces it. + +- **Mining** is HMs' Rock Smash: with a Pokémon that knows it sent out, the player's fist breaks the ore it hits and the ore connected to it, up to 32 blocks (power 32). HMs breaks each block as the player with a netherite pickaxe, through the normal break event, so Regenerating Ore Veins turns mined vein blocks into regenerators and the vein grows back. Automated: `rock_smash_mines_veins` (fires HMs' own Rock Smash handler at a vein). +- **Trainer Ditto.** HMs sends a visiting Trainer Ditto that sells TMs; underneath it is a wild Cobblemon Ditto. The island's wild-Pokémon rules (hunting weaker trainers, strays despawning) leave it alone, like guardians (`world/SpecialPokemon.kt`). +- **Config:** HMs' defaults for now (`config/cobblemon_hms.json` is written by the mod on first start; the pack ships none). Every ability can be switched off or tuned there. + +Open, for Adrian (`docs/checklists/phase-5.md`): whether HMs' abilities wait for progression like Cobblebase's skills, what to do about the ones that get around zone hazards, and whether party passives (shoulder Pokémon) are still wanted. + ## 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: diff --git a/docs/licenses.md b/docs/licenses.md index fa476e8..7f2ab6e 100644 --- a/docs/licenses.md +++ b/docs/licenses.md @@ -42,6 +42,7 @@ Read each license before adding a mod, and add a row here in the same change. | 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 | +| Cobblemon HMs | shiero | shiero's Mods License (in the jar): free to use, also in free modpacks; any monetized use (paid pack, paid server perks) needs an agreement with the author | [Modrinth](https://modrinth.com/project/cobblemon-hms) | Phase 5: field moves (both). Not resolved on CurseForge yet | | 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 | diff --git a/docs/roadmap.md b/docs/roadmap.md index c724594..e35d72c 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -132,6 +132,8 @@ Goal: ore, Pokémon drops and base production form the economy. Goal: moves a Pokémon knows act on the world, HM-style. Flamethrower sets blocks on fire. +**Decided 2026-10-31 (Adrian):** the field moves come from Cobblemon HMs instead of the spec below; mining ore is the one hard requirement, and furnaces are lit by a Pokémon inside a Phase 6 machine. See `docs/design.md`. + **Prior art to study, not depend on.** None is confirmed on Cobblemon 1.8: - [Cobblemon Field Survival](https://www.modpackindex.com/mod/173947/cobblemon-field-survival) (NeoForge 1.21.1, Cobblemon 1.7.3): mining, digging, water utilities, Fire types cooking food. diff --git a/mod/src/main/kotlin/dev/cobblesurvival/catching/Catching.kt b/mod/src/main/kotlin/dev/cobblesurvival/catching/Catching.kt index 8069578..8baae0e 100644 --- a/mod/src/main/kotlin/dev/cobblesurvival/catching/Catching.kt +++ b/mod/src/main/kotlin/dev/cobblesurvival/catching/Catching.kt @@ -9,6 +9,7 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity import com.google.gson.Gson import com.google.gson.JsonElement import dev.cobblesurvival.CobbleSurvival +import dev.cobblesurvival.world.SpecialPokemon import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import net.minecraft.server.packs.resources.ResourceManager @@ -23,7 +24,8 @@ import net.neoforged.neoforge.event.tick.ServerTickEvent * strongest party Pokémon: * - the catch rate falls with the level gap, down to a small share at 20 levels above ("very improbable"); * - a Pokémon that breaks out of a ball attacks the thrower instead of fleeing; - * - a wild Pokémon above a nearby player's strongest Pokémon is hostile to that player. + * - a wild Pokémon above a nearby player's strongest Pokémon is hostile to that player (not guardians and other + * special Pokémon, see [SpecialPokemon]). * * The attacking is Fight or Flight Reborn's: an angry Pokémon carries the aspect [ENRAGED], which the pack's Fight or * Flight config lists in `always_aggro_aspects` (so it fights rather than flees), and its target is set through Fight @@ -96,7 +98,7 @@ object Catching : SimpleJsonResourceReloadListener(Gson(), "catching") { val strongest = strongest(player) val near = player.boundingBox.inflate(rules.hostileRange) player.serverLevel().getEntitiesOfClass(PokemonEntity::class.java, near) { - it.pokemon.isWild() && !it.isBattling && it.pokemon.level > strongest + it.pokemon.isWild() && !it.isBattling && it.pokemon.level > strongest && !SpecialPokemon.isExempt(it.pokemon) }.forEach { enrage(it, player) } } } diff --git a/mod/src/main/kotlin/dev/cobblesurvival/selftest/FieldMoveTests.kt b/mod/src/main/kotlin/dev/cobblesurvival/selftest/FieldMoveTests.kt new file mode 100644 index 0000000..f29e315 --- /dev/null +++ b/mod/src/main/kotlin/dev/cobblesurvival/selftest/FieldMoveTests.kt @@ -0,0 +1,62 @@ +package dev.cobblesurvival.selftest + +import dev.cobblesurvival.dev.OreVeins +import net.minecraft.commands.arguments.EntityAnchorArgument +import net.minecraft.core.BlockPos +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.resources.ResourceLocation +import net.minecraft.server.level.ServerPlayer +import net.minecraft.util.RandomSource +import net.minecraft.world.entity.item.ItemEntity +import net.minecraft.world.item.Items +import net.minecraft.world.level.GameType +import net.minecraft.world.level.block.Blocks +import net.minecraft.world.phys.AABB +import net.minecraft.world.phys.Vec3 + +/** + * Phase 5 field moves come from Cobblemon HMs (Adrian, 2026-10-31); mining ore is the one hard requirement. Rock Smash + * must mine a regenerating vein the normal way: the ore drops and Regenerating Ore Veins turns the block into a + * regenerator, so the vein grows back. The test fires HMs' own Rock Smash handler (by reflection) at a one-block + * coal vein, away from spawn, since HMs respects spawn protection. + */ +object FieldMoveTests { + private const val VEIN = "selftest" + private const val OFFSET = 64 + private val REGENERATOR = ResourceLocation.parse("regenerating_ore_veins:regenerator_block") + + fun all() = listOf(SelfTestCase("rock_smash_mines_veins", 100, ::rockSmashMinesVeins)) + + private fun rockSmashMinesVeins(s: Scenario) { + val level = s.level + val pos = level.sharedSpawnPos.let { BlockPos(it.x + OFFSET, SelfTest.PLATFORM_Y, it.z) } + val player = s.player() + val start = player.position() + s.helper.startSequence() + .thenExecute { + s.moveTo(player, pos.x + 0.5, pos.y - 1.0, pos.z + 3.5) // forces the chunks around the vein + s.check(OreVeins.place(level, pos, VEIN, RandomSource.create(1L)) > 0) { "the test vein was not placed" } + s.check(level.getBlockState(pos).`is`(Blocks.COAL_ORE)) { "the test vein is ${level.getBlockState(pos)}" } + player.lookAt(EntityAnchorArgument.Anchor.EYES, Vec3.atCenterOf(pos)) + s.check(rockSmash(player)) { "Rock Smash refused to break the ore" } + player.teleportTo(start.x, start.y, start.z) + } + .thenWaitUntil { + val block = BuiltInRegistries.BLOCK.getKey(level.getBlockState(pos).block) + s.check(block == REGENERATOR) { "the mined vein block is $block, not a regenerator: it won't grow back" } + val drops = level.getEntitiesOfClass(ItemEntity::class.java, AABB(pos).inflate(4.0)) { it.item.`is`(Items.COAL) } + s.check(drops.isNotEmpty()) { "Rock Smash dropped no coal" } + } + .thenExecute { + level.getEntitiesOfClass(ItemEntity::class.java, AABB(pos).inflate(4.0)).forEach { it.discard() } + player.setGameMode(GameType.CREATIVE) // breaking a regenerator in creative removes the vein for good + player.gameMode.destroyBlock(pos) + } + .thenSucceed() + } + + private fun rockSmash(player: ServerPlayer): Boolean { + val handler = Class.forName("com.shier.cobblemonhms.ability.handlers.RockSmashHandler") + return handler.getMethod("execute", ServerPlayer::class.java).invoke(handler.getField("INSTANCE").get(null), player) as Boolean + } +} diff --git a/mod/src/main/kotlin/dev/cobblesurvival/selftest/SelfTest.kt b/mod/src/main/kotlin/dev/cobblesurvival/selftest/SelfTest.kt index 67636e3..4958d06 100644 --- a/mod/src/main/kotlin/dev/cobblesurvival/selftest/SelfTest.kt +++ b/mod/src/main/kotlin/dev/cobblesurvival/selftest/SelfTest.kt @@ -54,7 +54,7 @@ object SelfTest { private val cases: List get() = PermadeathTests.all() + SurvivalRulesTests.all() + ZoneTests.all() + HazardTests.all() + GuardianTests.all() + VeinTests.all() + OreNodeTests.all() + DropTests.all() + ProgressionTests.all() + - CatchingTests.all() + WorldRulesTests.all() + CatchingTests.all() + WorldRulesTests.all() + FieldMoveTests.all() private var running: List = emptyList() private var forced: List = emptyList() diff --git a/mod/src/main/kotlin/dev/cobblesurvival/world/SpecialPokemon.kt b/mod/src/main/kotlin/dev/cobblesurvival/world/SpecialPokemon.kt new file mode 100644 index 0000000..1349c92 --- /dev/null +++ b/mod/src/main/kotlin/dev/cobblesurvival/world/SpecialPokemon.kt @@ -0,0 +1,17 @@ +package dev.cobblesurvival.world + +import com.cobblemon.mod.common.pokemon.Pokemon +import dev.cobblesurvival.zone.Guardians + +/** + * Wild Pokémon that the island's rules for wild Pokémon (strays despawning, hunting weaker trainers) leave alone: + * guardians, anything uncatchable, and other mods' characters that are Pokémon underneath, such as Cobblemon HMs' + * visiting Trainer Ditto, which sells TMs. + */ +object SpecialPokemon { + /** Cobblemon HMs marks its Trainer Ditto with this in the Pokémon's persistent data. */ + private const val HMS_TRAINER = "dittohms_trainer" + + fun isExempt(pokemon: Pokemon): Boolean = + pokemon.isUncatchable() || pokemon.persistentData.contains(Guardians.TAG) || pokemon.persistentData.contains(HMS_TRAINER) +} diff --git a/mod/src/main/kotlin/dev/cobblesurvival/zone/Wanderers.kt b/mod/src/main/kotlin/dev/cobblesurvival/zone/Wanderers.kt index 559f6a3..ede08f8 100644 --- a/mod/src/main/kotlin/dev/cobblesurvival/zone/Wanderers.kt +++ b/mod/src/main/kotlin/dev/cobblesurvival/zone/Wanderers.kt @@ -1,6 +1,7 @@ package dev.cobblesurvival.zone import com.cobblemon.mod.common.entity.pokemon.PokemonEntity +import dev.cobblesurvival.world.SpecialPokemon import net.minecraft.server.level.ServerLevel import net.minecraft.world.entity.ai.memory.MemoryModuleType import net.neoforged.neoforge.common.NeoForge @@ -11,7 +12,8 @@ import java.util.UUID * Wild Pokémon keep to their zone's levels (Adrian, 2026-10-30: a Lv 61 Pokémon from the Basalt Furnace turned up in the * Lush Caves, Lv 12–28). Spawns follow the zones, but wild Pokémon wander and cave zones touch, so every few seconds a * wild Pokémon near a player that stands in a zone whose band it tops by more than [Zones.wanderGrace] levels - * despawns, unless it is in a battle, angry at someone (it is chasing them) or a guardian. Grace: world/zones.json → + * despawns, unless it is in a battle, angry at someone (it is chasing them), a guardian or another special Pokémon + * ([SpecialPokemon]). Grace: world/zones.json → * `wanderers`. */ object Wanderers { @@ -38,7 +40,7 @@ object Wanderers { } fun strayed(level: ServerLevel, wild: PokemonEntity, grace: Int): Boolean { - if (wild.isBattling || wild.pokemon.persistentData.contains(Guardians.TAG)) return false + if (wild.isBattling || SpecialPokemon.isExempt(wild.pokemon)) return false if (wild.brain.hasMemoryValue(MemoryModuleType.ATTACK_TARGET)) return false val zone = Zones.at(level, wild.blockPosition()) ?: return false return wild.pokemon.level > zone.maxLevel + grace diff --git a/pack/index.toml b/pack/index.toml index 51fa616..f358c5f 100644 --- a/pack/index.toml +++ b/pack/index.toml @@ -126,6 +126,11 @@ file = "mods/cobblemon-fight-or-flight-reborn.pw.toml" hash = "227eea45819938ae2e0911d825e9ccf0e0236b1a36e46627204d8aa53becc9e7" metafile = true +[[files]] +file = "mods/cobblemon-hms.pw.toml" +hash = "36f7da26797651777c39409f5e8b2aeb2fe8b7d59a0a62a791a9c6b2c2d318e8" +metafile = true + [[files]] file = "mods/cobblemon-poke-power.pw.toml" hash = "29ccd4c39e5af1a2fb63a40bf67ecd03bcced42e9e1edf296b26fd1847740a54" diff --git a/pack/mods/cobblemon-hms.pw.toml b/pack/mods/cobblemon-hms.pw.toml new file mode 100644 index 0000000..ca7f4d1 --- /dev/null +++ b/pack/mods/cobblemon-hms.pw.toml @@ -0,0 +1,13 @@ +name = "Cobblemon HMs" +filename = "cobblemon-hms-neoforge-4.1.0.jar" +side = "both" + +[download] +url = "https://cdn.modrinth.com/data/JNfSyMuQ/versions/uC02CqRy/cobblemon-hms-neoforge-4.1.0.jar" +hash-format = "sha512" +hash = "009f1a6deb011ffa42f29bb784a7de5ddd0233b38da0885e6f8aa70ce5a07c961f853ad5807dd6706f756e191751faf2f096f1df9331c652ca8254302979d437" + +[update] +[update.modrinth] +mod-id = "JNfSyMuQ" +version = "uC02CqRy" diff --git a/pack/pack.toml b/pack/pack.toml index 80ecc06..cd1d798 100644 --- a/pack/pack.toml +++ b/pack/pack.toml @@ -6,7 +6,7 @@ pack-format = "packwiz:1.1.0" [index] file = "index.toml" hash-format = "sha256" -hash = "36704b01cdb1201d353d8987ebaa4ecb8b1bee8a4ea5352dfc9c14e9472f764a" +hash = "d8359ad9c1f98c8c72503f0ce16773d7721a1a4095cfdef15ca3e985736ae6ff" [versions] minecraft = "1.21.1"