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,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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user