Cobblemon Survival Pack: phases 0-2 and Phase 3 zones and hazards
Free NeoForge 1.21.1 modpack on Cobblemon 1.8.4 built with packwiz, plus the Kotlin companion mod (cobblesurvival) and its built-in data pack. - Phase 0: packwiz pack with Modrinth/CurseForge cross-links, build and export tooling, Prism instance sync, test server with smoke checks. - Phase 1: Poke Ball shaped island (WorldGen Editor preset, seed isle-26), Adrian's biome list, no natural ore, structures without ore or vanilla mobs, no Nether portals, world template builder and world report. - Phase 2: permadeath (CobbleHardcoreMon plus the companion mod's gap fixes), Revival Charm, expensive Healing Machine, Lootr refreshing loot, in-game self-tests on Minecraft's GameTest framework. - Phase 3 so far: 14 zones with their own spawn pools and level bands (tools/zone_layout.py, tools/zone_spawns.py, "zone" spawn condition), Cold Sweat heat and cold as gates with boss-drop insulation recipes, spawn survey and hazard self-tests in tools/quick_check.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
398301f0be
232 files changed
+72665
No files matched your search
@@ -0,0 +1,117 @@
|
||||
package dev.cobblesurvival.permadeath
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleEndedEvent
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleFaintedEvent
|
||||
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
|
||||
import com.cobblemon.mod.common.api.storage.pc.PCStore
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.isInBattle
|
||||
import dev.cobblesurvival.CobbleSurvival
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.core.registries.Registries
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.sounds.SoundEvents
|
||||
import net.minecraft.sounds.SoundSource
|
||||
import net.minecraft.tags.TagKey
|
||||
import net.minecraft.world.item.Item
|
||||
import net.neoforged.neoforge.common.NeoForge
|
||||
import net.neoforged.neoforge.event.tick.ServerTickEvent
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
/**
|
||||
* A fainted Pokémon is gone for good. CobbleHardcoreMon settles faints in battles; this closes the gaps around it:
|
||||
* - a Pokémon that faints outside a battle (falling, lava, poison, ...) is settled at the end of the same server
|
||||
* tick. CobbleHardcoreMon only looks once a second, and waits a full minute for a Pokémon whose charm already
|
||||
* saved it once, which left time to heal it or move it to the PC.
|
||||
* - a Pokémon that fainted in a battle and was revived before the battle ended (a Revive, Revival Blessing, ...)
|
||||
* is fainted again when the battle ends, just before CobbleHardcoreMon settles the battle.
|
||||
* - once a second, every online player's party and PC are swept for fainted Pokémon (one whose trainer was offline
|
||||
* or in a battle when it fainted).
|
||||
* Settling: a Pokémon holding an item from `#cobblesurvival:revival_charms` is healed and the charm is used up;
|
||||
* any other is removed from its party or PC.
|
||||
*/
|
||||
object Permadeath {
|
||||
val REVIVAL_CHARMS: TagKey<Item> =
|
||||
TagKey.create(Registries.ITEM, ResourceLocation.fromNamespaceAndPath(CobbleSurvival.MOD_ID, "revival_charms"))
|
||||
private const val SWEEP_TICKS = 20
|
||||
|
||||
private val fainted = ConcurrentLinkedQueue<Pokemon>()
|
||||
private val faintedInBattle = HashMap<UUID, MutableSet<Pokemon>>()
|
||||
|
||||
fun register() {
|
||||
CobblemonEvents.POKEMON_FAINTED.subscribe(Priority.NORMAL) { fainted += it.pokemon }
|
||||
CobblemonEvents.BATTLE_FAINTED.subscribe(Priority.NORMAL, ::onBattleFainted)
|
||||
// CobbleHardcoreMon settles the battle at NORMAL priority; it has to see the revived Pokémon fainted.
|
||||
CobblemonEvents.BATTLE_ENDED.subscribe(Priority.HIGHEST, ::onBattleEnded)
|
||||
NeoForge.EVENT_BUS.addListener(::onServerTick)
|
||||
}
|
||||
|
||||
private fun onBattleFainted(event: BattleFaintedEvent) {
|
||||
val killed = event.killed
|
||||
// In a battle on copies (exhibition rules) the real Pokémon is not hurt, so it does not count.
|
||||
if (killed.effectedPokemon !== killed.originalPokemon || !killed.originalPokemon.isPlayerOwned()) return
|
||||
faintedInBattle.getOrPut(event.battle.battleId) { HashSet() } += killed.originalPokemon
|
||||
}
|
||||
|
||||
private fun onBattleEnded(event: BattleEndedEvent) {
|
||||
val faintedHere = faintedInBattle.remove(event.battle.battleId) ?: return
|
||||
for (pokemon in faintedHere) {
|
||||
if (!pokemon.isFainted() && pokemon.storeCoordinates.get() != null) {
|
||||
pokemon.currentHealth = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onServerTick(event: ServerTickEvent.Post) {
|
||||
while (true) {
|
||||
val pokemon = fainted.poll() ?: break
|
||||
val owner = pokemon.getOwnerPlayer() ?: continue
|
||||
settle(owner, pokemon)
|
||||
}
|
||||
if (event.server.tickCount % SWEEP_TICKS == 0) {
|
||||
for (player in event.server.playerList.players) {
|
||||
val party = Cobblemon.storage.getParty(player)
|
||||
val pc = Cobblemon.storage.getPC(player)
|
||||
(party.filter { it.isFainted() } + pc.filter { it.isFainted() }).forEach { settle(player, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun settle(player: ServerPlayer, pokemon: Pokemon) {
|
||||
val store = pokemon.storeCoordinates.get()?.store
|
||||
if (!pokemon.isFainted() || (store !is PlayerPartyStore && store !is PCStore)) return
|
||||
// Faints during a battle are settled when it ends (above, and by CobbleHardcoreMon).
|
||||
if (player.isInBattle()) return
|
||||
try {
|
||||
val name = pokemon.getDisplayName(false)
|
||||
if (pokemon.heldItem().`is`(REVIVAL_CHARMS)) {
|
||||
pokemon.heal()
|
||||
pokemon.removeHeldItem()
|
||||
player.sendSystemMessage(
|
||||
Component.translatableWithFallback(
|
||||
"message.cobblesurvival.permadeath.saved",
|
||||
"%s's Revival Charm saved it and crumbled to dust.", name,
|
||||
).withStyle(ChatFormatting.GOLD)
|
||||
)
|
||||
player.playNotifySound(SoundEvents.RESPAWN_ANCHOR_DEPLETE.value(), SoundSource.PLAYERS, 1f, 1f)
|
||||
CobbleSurvival.LOGGER.info("Permadeath: a Revival Charm saved {} of {}", name.string, player.gameProfile.name)
|
||||
} else if (store?.remove(pokemon) == true) {
|
||||
player.sendSystemMessage(
|
||||
Component.translatableWithFallback(
|
||||
"message.cobblesurvival.permadeath.lost",
|
||||
"%s fainted and is gone for good.", name,
|
||||
).withStyle(ChatFormatting.RED)
|
||||
)
|
||||
CobbleSurvival.LOGGER.info("Permadeath: {} of {} fainted and was removed", name.string, player.gameProfile.name)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
CobbleSurvival.LOGGER.error("Permadeath: could not settle a fainted Pokémon of {}", player.gameProfile.name, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user