Big, tough bosses; working machines show it; JEI drop pages on the roadmap

Bosses (Adrian, 2026-11-02: Eternatus too small and too easy; guardians way
bigger and tougher; only defensive stats up; no attack takes more than 25 %):
- Guardians scale to about 8 blocks tall (1.5x-12x), Eternatus to 15 blocks
  (2.5x, was 0.2x). Cave guardians look for a spot with room above them.
  Bosses already in a world grow on the next check outside battle.
- Perfect IVs and 170 EVs each in HP, Defense and Sp. Def, nothing offensive.
- The Guardian's Ward, a Showdown-only held item (held_items/guardiansward.js)
  handed to every boss by a HeldItemProvider: Defense and Sp. Def x2 while
  defending, and one attack (all hits together) takes at most 25 % of max HP.
- Area attacks that start at the boss reach half its width further.

Machines (Adrian, 2026-11-02): a working core is active: it glows, its front
animates (flickering fire on the furnace core, pulsing emblems elsewhere), and
particles show in the chamber and at the core (client side). The Pokemon
inside flinches about every 15 s (red flash, Cobblemon's hit sound, no damage).

Roadmap Phase 8: every item that can drop gets a JEI page naming its sources.

Self-tests: practice_bosses_spawn checks size and training,
boss_takes_a_quarter_per_attack runs a real battle (each Lv 100 Bullet Seed
takes exactly a quarter), heat_core_cooks_with_hoppers checks the active state
and the flinch. Self-tests and the island check pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
claudeandClaude Opus 5.5 committed 2026-11-03 10:00:57 +01:00
1 parent 4f0bc03919
commit 120c664cb0
58 files changed
+681 -86

No files matched your search

@@ -3,10 +3,10 @@
"end": {
"species": "eternatus",
"level": 80,
"scale": 0.2,
"respawn_hours": 5,
"xp_first_win": 12000,
"xp": 500
"xp": 500,
"scale": 2.5
},
"arena": 40,
"patterns": [
@@ -2,7 +2,7 @@
"_comment": "Generated by tools/zone_spawns.py from world/zones.json; do not edit by hand.",
"zone": "basalt",
"pokemon": "moltres level=75 uncatchable=yes",
"scale": 1.5,
"scale": 2.0,
"drops": [
{
"item": "cobblemon:exp_candy_xl",
@@ -2,7 +2,7 @@
"_comment": "Generated by tools/zone_spawns.py from world/zones.json; do not edit by hand.",
"zone": "button_isle",
"pokemon": "morpeko level=15 uncatchable=yes",
"scale": 1.5,
"scale": 12,
"drops": [
{
"item": "cobblemon:poke_ball",
@@ -2,7 +2,7 @@
"_comment": "Generated by tools/zone_spawns.py from world/zones.json; do not edit by hand.",
"zone": "deep_dark",
"pokemon": "regigigas level=65 uncatchable=yes",
"scale": 1.0,
"scale": 1.5,
"drops": [
{
"item": "cobblemon:icy_rock",
@@ -2,7 +2,7 @@
"_comment": "Generated by tools/zone_spawns.py from world/zones.json; do not edit by hand.",
"zone": "deep_sea",
"pokemon": "kingdra level=55 uncatchable=yes",
"scale": 1.5,
"scale": 2.96,
"drops": [
{
"item": "cobblemon:mystic_water",
@@ -2,7 +2,7 @@
"_comment": "Generated by tools/zone_spawns.py from world/zones.json; do not edit by hand.",
"zone": "frostpeak",
"pokemon": "abomasnow level=60 uncatchable=yes",
"scale": 1.5,
"scale": 3.2,
"drops": [
{
"item": "cobblemon:icy_rock",
@@ -2,7 +2,7 @@
"_comment": "Generated by tools/zone_spawns.py from world/zones.json; do not edit by hand.",
"zone": "highlands",
"pokemon": "mudsdale level=40 uncatchable=yes",
"scale": 1.5,
"scale": 3.08,
"drops": [
{
"item": "cobblemon:icy_rock",
@@ -2,7 +2,7 @@
"_comment": "Generated by tools/zone_spawns.py from world/zones.json; do not edit by hand.",
"zone": "jungle",
"pokemon": "tangrowth level=35 uncatchable=yes",
"scale": 1.5,
"scale": 3.33,
"drops": [
{
"item": "cobblesurvival:mire_waders",
@@ -2,7 +2,7 @@
"_comment": "Generated by tools/zone_spawns.py from world/zones.json; do not edit by hand.",
"zone": "mire",
"pokemon": "swampert level=43 uncatchable=yes",
"scale": 1.5,
"scale": 5.0,
"drops": [
{
"item": "cobblemon:icy_rock",
@@ -2,7 +2,7 @@
"_comment": "Generated by tools/zone_spawns.py from world/zones.json; do not edit by hand.",
"zone": "southwold",
"pokemon": "pinsir level=23 uncatchable=yes",
"scale": 1.5,
"scale": 6.13,
"drops": [
{
"item": "cobblemon:great_ball",
@@ -2,7 +2,7 @@
"_comment": "Generated by tools/zone_spawns.py from world/zones.json; do not edit by hand.",
"zone": "sunscorch",
"pokemon": "flygon level=50 uncatchable=yes",
"scale": 1.5,
"scale": 2.78,
"drops": [
{
"item": "cobblemon:heat_rock",
@@ -2,7 +2,7 @@
"_comment": "Generated by tools/zone_spawns.py from world/zones.json; do not edit by hand.",
"zone": "westmarch",
"pokemon": "scyther level=30 uncatchable=yes",
"scale": 1.5,
"scale": 4.73,
"drops": [
{
"item": "cobblesurvival:mire_waders",
@@ -0,0 +1,49 @@
/*
* Cobblemon Survival Pack: the Guardian's Ward, a Showdown held item (Cobblemon loads data/<ns>/held_items/<id>.js).
* No Minecraft item exists for it: the companion mod gives it to every boss in battle (boss/BossToughness.kt), so it
* can't be stolen, knocked off or suppressed (the work is done by a lasting condition it starts). Adrian, 2026-11-02:
* bosses are tougher but not stronger, and no single attack takes more than a quarter of their HP.
*
* Showdown names the item after "name" (guardiansward), which must match this file's name. Cobblemon joins the file
* into one line before Showdown reads it: block comments only.
* Tunables (docs/balance.md, "Boss toughness"): DEF_FACTOR, SPD_FACTOR and HP_SHARE below.
*/
{
name: "Guardian's Ward",
spritenum: 0,
ignoreKlutz: true,
onTakeItem: false,
onStart(pokemon) {
pokemon.addVolatile('guardiansward');
},
condition: {
noCopy: true,
/* Defense and Sp. Def only while the boss defends: Body Press (its own Defense as attack) stays as it is. */
onModifyDefPriority: 1,
onModifyDef(def, pokemon) {
const DEF_FACTOR = 2;
if (this.activePokemon !== pokemon) return this.chainModify(DEF_FACTOR);
},
onModifySpDPriority: 1,
onModifySpD(spd, pokemon) {
const SPD_FACTOR = 2;
if (this.activePokemon !== pokemon) return this.chainModify(SPD_FACTOR);
},
/* One attack (every hit of a multi-hit move together) takes at most HP_SHARE of the boss's max HP. */
onDamagePriority: -100,
onDamage(damage, target, source, effect) {
const HP_SHARE = 0.25;
if (!effect || effect.effectType !== 'Move') return;
if (this.effectState.move !== effect) {
this.effectState.move = effect;
this.effectState.taken = 0;
}
const left = Math.max(0, Math.floor(target.maxhp * HP_SHARE) - this.effectState.taken);
const dealt = Math.min(damage, left);
this.effectState.taken += dealt;
return dealt;
},
},
num: -2001,
gen: 9,
}