Files
cobblemon-survival-pack/mod/src/main/kotlin/dev/cobblesurvival/CobbleSurvival.kt
T
claudeandClaude Opus 5.5 120c664cb0 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>
2026-11-03 10:00:57 +01:00

148 lines
6.0 KiB
Kotlin

package dev.cobblesurvival
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.mojang.logging.LogUtils
import dev.cobblesurvival.catching.Catching
import dev.cobblesurvival.dev.BiomePreview
import dev.cobblesurvival.dev.OreVeins
import dev.cobblesurvival.ore.BaseLoot
import dev.cobblesurvival.ore.OreNodes
import dev.cobblesurvival.permadeath.Permadeath
import dev.cobblesurvival.boss.BossCommands
import dev.cobblesurvival.boss.BossFights
import dev.cobblesurvival.boss.BossToughness
import dev.cobblesurvival.boss.EndBoss
import dev.cobblesurvival.machine.MachineShowcase
import dev.cobblesurvival.machine.Machines
import dev.cobblesurvival.party.ShoulderPassives
import dev.cobblesurvival.progress.Gates
import dev.cobblesurvival.selftest.SelfTest
import dev.cobblesurvival.world.OnlyPokemon
import dev.cobblesurvival.world.SharedLoot
import dev.cobblesurvival.world.SpecialPokemon
import dev.cobblesurvival.zone.Guardians
import dev.cobblesurvival.zone.InZoneCondition
import dev.cobblesurvival.zone.LevelPressure
import dev.cobblesurvival.zone.Wanderers
import dev.cobblesurvival.zone.ZoneCondition
import dev.cobblesurvival.zone.ZoneHazards
import dev.cobblesurvival.zone.Zones
import net.minecraft.commands.Commands
import net.minecraft.core.BlockPos
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.repository.Pack
import net.minecraft.server.packs.repository.PackSource
import net.minecraft.world.level.Level
import net.neoforged.fml.ModList
import net.neoforged.fml.common.Mod
import net.neoforged.neoforge.common.NeoForge
import net.neoforged.neoforge.event.AddPackFindersEvent
import net.neoforged.neoforge.event.RegisterCommandsEvent
import net.neoforged.neoforge.event.level.BlockEvent
import net.neoforged.neoforge.event.server.ServerStartedEvent
import org.slf4j.Logger
import thedarkcolour.kotlinforforge.neoforge.forge.MOD_BUS
@Mod(CobbleSurvival.MOD_ID)
class CobbleSurvival {
companion object {
const val MOD_ID = "cobblesurvival"
val LOGGER: Logger = LogUtils.getLogger()
val version: String
get() = ModList.get().getModContainerById(MOD_ID)
.map { it.modInfo.version.toString() }
.orElse("unknown")
/** One line the server smoke test reads over RCON to confirm the mod is live. */
fun statusLine() =
"$MOD_ID $version ok; cobblemon ${Cobblemon.VERSION}; species ${PokemonSpecies.count()}"
}
init {
LOGGER.info("Cobblemon Survival companion {} loaded (Cobblemon {})", version, Cobblemon.VERSION)
MOD_BUS.addListener(::onAddPackFinders)
InZoneCondition.register(MOD_BUS)
Machines.register(MOD_BUS)
NeoForge.EVENT_BUS.addListener(::onRegisterCommands)
NeoForge.EVENT_BUS.addListener(::onServerStarted)
NeoForge.EVENT_BUS.addListener(::onPortalSpawn)
NeoForge.EVENT_BUS.addListener(OnlyPokemon::onChunkLoad)
NeoForge.EVENT_BUS.addListener(OnlyPokemon::onEntityJoin)
NeoForge.EVENT_BUS.addListener(OnlyPokemon::onServerTick)
Permadeath.register()
ZoneCondition.register()
NeoForge.EVENT_BUS.addListener(Zones::onAddReloadListeners)
Guardians.register()
BossFights.register()
BossToughness.register()
EndBoss.register()
Gates.register()
ShoulderPassives.register()
OreNodes.register()
BaseLoot.register()
Catching.register()
Wanderers.register()
SharedLoot.register()
SpecialPokemon.register()
ZoneHazards.register()
LevelPressure.register()
SelfTest.register()
}
/** The island world type has no Nether, so a lit obsidian frame must not turn into a dead portal. */
private fun onPortalSpawn(event: BlockEvent.PortalSpawnEvent) {
val level = event.level as? ServerLevel ?: return
if (level.server.getLevel(Level.NETHER) == null) {
event.isCanceled = true
}
}
/** Ships datapacks/core from the repo as a built-in data pack that is always on, in every world. */
private fun onAddPackFinders(event: AddPackFindersEvent) {
event.addPackFinders(
ResourceLocation.fromNamespaceAndPath(MOD_ID, "datapacks/core"),
PackType.SERVER_DATA,
Component.literal("Cobblemon Survival core data"),
PackSource.BUILT_IN,
true,
Pack.Position.TOP,
)
}
private fun onRegisterCommands(event: RegisterCommandsEvent) {
val root = Commands.literal(MOD_ID)
.requires { it.hasPermission(2) }
.then(Commands.literal("status").executes { context ->
context.source.sendSuccess({ Component.literal(statusLine()) }, false)
1
})
.then(BiomePreview.command())
.then(OreVeins.command())
.then(MachineShowcase.command())
.then(Commands.literal("zone").executes { context ->
val source = context.source
val zone = Zones.at(source.level, BlockPos.containing(source.position))
val text = zone?.let { "${it.name} (${it.id}), Lv ${it.minLevel}-${it.maxLevel}" } ?: "no zone"
source.sendSuccess({ Component.literal(text) }, false)
1
})
.then(Commands.literal("guardians").executes { context ->
val lines = Guardians.status(context.source.server.overworld())
context.source.sendSuccess({ Component.literal(lines.joinToString("\n").ifEmpty { "no guardians loaded" }) }, false)
1
}.then(BossCommands.guardianReset()))
.then(BossCommands.practice())
if (SelfTest.enabled) root.then(SelfTest.command())
event.dispatcher.register(root)
}
private fun onServerStarted(event: ServerStartedEvent) {
LOGGER.info("Cobblemon Survival companion ready: {}", statusLine())
}
}