Compare commits

...
Author SHA1 Message Date
Tyzillion 7c89d6f274 weather setup 2024-04-15 20:37:22 -04:00
Tyzillion 8801423ac1 Merge remote-tracking branch 'origin/battle_start_conditions' into battle_start_conditions 2024-04-13 21:48:05 -04:00
Tyzillion c1452c52a7 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into battle_start_conditions
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/battles/model/PokemonBattle.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/battles/interpreter/instructions/TurnInstruction.kt
2024-04-13 21:47:40 -04:00
Lily 1896cffa66 Merge remote-tracking branch 'origin/battle_start_conditions' into battle_start_conditions 2024-04-13 17:56:56 -04:00
Lily 9e8d5c71da Added support for starting battles with field conditions 2024-04-13 17:56:07 -04:00
Lily 9e486b9287 Added support for starting battles with field conditions 2024-04-13 17:53:59 -04:00
28 changed files with 523 additions and 8 deletions
@@ -0,0 +1,75 @@
package com.cobblemon.mod.common.api.battles.effects
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.util.math.orMax
import com.cobblemon.mod.common.util.math.orMin
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import net.minecraft.registry.Registry
import net.minecraft.registry.RegistryKeys
import net.minecraft.util.Identifier
import net.minecraft.world.biome.Biome
/**
* Represents a field condition that can occur at battle start.
*
* @param identifier The [Identifier] of this condition.
* @param biomes: A [MutableSet] of biomes that this condition will succeed in.
* @param timeRange A [TimeRange] that this condition will succeed in.
* @param isRaining Whether this condition requires rain to succeed. When null, will not perform any check.
* @param isThundering Whether this condition requires thunder to succeed. When null, will not perform any check.
* @param result The showdown ID of the field condition to apply.
*
* @author whatsy
* @since April 9th, 2024
*/
data class BattleStartCondition(
var identifier: Identifier,
val biomes: MutableSet<RegistryLikeCondition<Biome>>,
val timeRange: TimeRange? = null,
val isRaining: Boolean?,
val isThundering: Boolean?,
val minX: Float? = null,
val minY: Float? = null,
val minZ: Float? = null,
val maxX: Float? = null,
val maxY: Float? = null,
val maxZ: Float? = null,
val result: String
) {
/***
* Checks if this [BattleStartCondition] can succeed in the context of the provided [PokemonBattle].
*
* @author whatsy
* @since April 9th, 2024
*/
fun matches(battle: PokemonBattle): Boolean {
val player = battle.players.first()
val world = player.world ?: return false
if (!world.gameRules.getBoolean(CobblemonGameRules.WEATHER_AFFECTS_BATTLES)) return false
val pos = player.blockPos ?: return false
val biomeRegistry: Registry<Biome> by lazy { world.registryManager.get(RegistryKeys.BIOME) }
val biome = world.getBiome(pos).value()
if (biomes.isNotEmpty() && biomes.none { condition -> condition.fits(biome, biomeRegistry) }) {
return false
} else if (timeRange != null && !timeRange.contains((world.timeOfDay % 24000).toInt())) {
return false
} else if (world.isRaining != isRaining && isRaining != null) {
return false
} else if (world.isThundering != isThundering && isThundering != null) {
return false
} else if (pos.x < minX.orMin() || pos.x > maxX.orMax()) {
return false
} else if (pos.y < minY.orMin() || pos.y > maxY.orMax()) {
return false
} else if (pos.z < minZ.orMin() || pos.z > maxZ.orMax()) {
return false
}
return true
}
}
@@ -0,0 +1,61 @@
package com.cobblemon.mod.common.api.battles.effects
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.util.adapters.BiomeLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.IntRangesAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resource.ResourceType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.minecraft.world.biome.Biome
object BattleStartConditions : JsonDataRegistry<BattleStartCondition> {
override val id = cobblemonResource("start_conditions")
override val type = ResourceType.SERVER_DATA
override val observable = SimpleObservable<BattleStartConditions>()
override val gson = GsonBuilder()
.disableHtmlEscaping()
.setPrettyPrinting()
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
.registerTypeAdapter(TimeRange::class.java, IntRangesAdapter(TimeRange.timeRanges) { TimeRange(*it) })
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.create()
override val typeToken = TypeToken.get(BattleStartCondition::class.java)
override val resourcePath = "start_conditions"
private val conditions = hashMapOf<Identifier, BattleStartCondition>()
override fun reload(data: Map<Identifier, BattleStartCondition>) {
this.conditions.clear()
data.forEach { id, condition ->
try {
condition.identifier = id
this.conditions[id] = condition
} catch (e: Exception) {
Cobblemon.LOGGER.error("Skipped loading battle start condition: {}", condition.identifier)
}
Cobblemon.LOGGER.info("Loaded {} battle start conditions", this.conditions.size)
this.observable.emit(this)
}
}
override fun sync(player: ServerPlayerEntity) {
// no need for the client to know about these since it's all handled server-side
}
/**
* Gets all loaded [BattleStartCondition]s.
*/
fun all() = this.conditions.values.toList()
}
@@ -37,6 +37,7 @@ import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.battles.interpreter.ContextManager
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.entity.generic.GenericBedrockEntity
import com.cobblemon.mod.common.battles.ForfeitActionResponse
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
@@ -45,11 +46,15 @@ import com.cobblemon.mod.common.pokemon.evolution.progress.DefeatEvolutionProgre
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.requirements.DefeatRequirement
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.getPlayer
import net.minecraft.entity.Entity
import java.io.File
import java.util.UUID
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.Text
import net.minecraft.util.math.Vec3d
import net.minecraft.world.World
import java.util.concurrent.ConcurrentLinkedDeque
/**
@@ -65,7 +70,7 @@ open class PokemonBattle(
val side2: BattleSide
) {
/** Whether logging will be silenced for this battle. */
var mute = true
var mute = false
init {
side1.battle = this
@@ -119,6 +124,47 @@ open class PokemonBattle(
val minorBattleActions = hashMapOf<UUID, BattleMessage>()
val contextManager = ContextManager()
var bedrockEntity: GenericBedrockEntity? = null
val world: World
get() = this.players.first().world
val pokemonPositions: Set<Vec3d>
get() {
val vectors = mutableSetOf<Vec3d>()
activePokemon.forEach {
if (it.position != null) vectors.add(it.position!!.second)
}
return vectors
}
fun getOrCreateBedrockEntity(): GenericBedrockEntity {
return if (bedrockEntity != null) {
bedrockEntity!!
} else {
bedrockEntity = GenericBedrockEntity(world = world)
bedrockEntity!!.apply {
category = cobblemonResource("field_particles")
syncAge = false
if (pokemonPositions.isNotEmpty()) setPosition(getPosForBedrockEntity(pokemonPositions))
}
world.spawnEntity(bedrockEntity)
bedrockEntity!!
}
}
fun getPosForBedrockEntity(vectors: Set<Vec3d>): Vec3d {
var sumX = 0.0
var sumZ = 0.0
for (vector in vectors) {
sumX += vector.x
sumZ += vector.z
}
val count = vectors.size
val y = vectors.maxByOrNull { it.y }?.y ?: 0.0
return Vec3d(sumX / count, y + 5.0, sumZ / count)
}
/** Whether or not there is one side with at least one player, and the other only has wild Pokémon. */
val isPvW: Boolean
get() {
@@ -217,6 +263,7 @@ open class PokemonBattle(
fun end() {
ended = true
bedrockEntity?.remove(Entity.RemovalReason.DISCARDED)
this.actors.forEach { actor ->
val faintedPokemons = actor.pokemonList.filter { it.health <= 0 }
actor.getSide().getOppositeSide().actors.forEach { opponent ->
@@ -396,6 +443,8 @@ open class PokemonBattle(
return
}
bedrockEntity?.setPosition(getPosForBedrockEntity(pokemonPositions))
if (started) {
ticks++
if (isPvW && !ended && dispatches.isEmpty()) checkFlee()
@@ -39,7 +39,8 @@ class TimeRange : IntRanges {
"dusk" to TimeRange(11834..13701),
"twilight" to TimeRange(11834..13701, 22300..23999, 0..166),
"morning" to TimeRange(0..4999),
"afternoon" to TimeRange(7000..12039)
"afternoon" to TimeRange(7000..12039),
"midday" to TimeRange(1000..11000)
)
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.battles.effects.BattleStartConditions
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.BattleStartedPostEvent
@@ -146,10 +147,16 @@ object BattleRegistry {
}
private fun startShowdown(battle: PokemonBattle) {
// Check for battle start condition
val effect = BattleStartConditions.all().firstOrNull { it.matches(battle) }
// Build request message
val messages = mutableListOf<String>()
messages.add(">start { \"format\": ${battle.format.toFormatJSON()} }")
if (effect != null) {
messages.add(">start { \"format\": ${battle.format.toFormatJSON()}, \"defaultWeather\": \"${effect.result}\" }")
} else {
messages.add(">start { \"format\": ${battle.format.toFormatJSON()} }")
}
/*
* Showdown IDs are like p1, p2, p3, etc. Showdown uses these keys to identify who is doing what to whom.
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.effects.BattleStartConditions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.scheduling.afterOnServer
@@ -32,9 +33,6 @@ import com.cobblemon.mod.common.util.battleLang
*/
class TurnInstruction(val message: BattleMessage) : InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
// TODO maybe tell the client that the turn number has changed
val turnNumber = message.argumentAt(0)?.toInt() ?: return
if (!battle.started) {
battle.started = true
battle.actors.filterIsInstance<PlayerBattleActor>().forEach { actor ->
@@ -58,6 +56,20 @@ class TurnInstruction(val message: BattleMessage) : InterpreterInstruction {
}
}
// TODO maybe tell the client that the turn number has changed
val turnNumber = message.argumentAt(0)?.toInt() ?: return
// Try to apply battle start conditions
if (turnNumber == 1) {
val effect = BattleStartConditions.all().firstOrNull { it.matches(battle) }
if (effect != null) {
battle.writeShowdownAction(">eval battle.field.setWeather('${effect.result}', 'debug')")
afterOnServer(0, 2.5F) {
battle.getOrCreateBedrockEntity()
}
}
}
battle.dispatch {
battle.sendToActors(BattleMakeChoicePacket())
battle.broadcastChatMessage(battleLang("turn", turnNumber).aqua())
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.CobblemonMechanics
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.battles.effects.BattleStartConditions
import com.cobblemon.mod.common.api.berry.Berries
import com.cobblemon.mod.common.api.data.DataProvider
import com.cobblemon.mod.common.api.data.DataRegistry
@@ -72,6 +73,7 @@ object CobblemonDataProvider : DataProvider {
this.register(Dialogues)
this.register(NaturalMaterials)
this.register(Fossils)
this.register(BattleStartConditions)
CobblemonSpawnPools.load()
this.register(Berries)
@@ -24,7 +24,6 @@ import net.minecraft.entity.Entity
import net.minecraft.entity.EntityDimensions
import net.minecraft.entity.EntityPose
import net.minecraft.entity.data.DataTracker
import net.minecraft.entity.data.TrackedData
import net.minecraft.entity.data.TrackedDataHandlerRegistry
import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtList
@@ -20,5 +20,7 @@ object CobblemonGameRules {
val DO_POKEMON_LOOT: GameRules.Key<GameRules.BooleanRule> = Cobblemon.implementation.registerGameRule("doPokemonLoot", GameRules.Category.DROPS, BooleanRuleInvoker.`cobblemon$create`(true))
@JvmField
val SHINY_STARTERS: GameRules.Key<GameRules.BooleanRule> = Cobblemon.implementation.registerGameRule("doShinyStarters", GameRules.Category.MISC, BooleanRuleInvoker.`cobblemon$create`(false))
@JvmField
val WEATHER_AFFECTS_BATTLES: GameRules.Key<GameRules.BooleanRule> = Cobblemon.implementation.registerGameRule("weatherAffectsBattles", GameRules.Category.MISC, BooleanRuleInvoker.`cobblemon$create`(true))
}
@@ -0,0 +1,77 @@
{
"format_version": "1.8.0",
"animations": {
"animation.weather.sunnyday_day": {
"loop": true,
"particle_effects": {
"0.0": {
"effect": "cobblemon:weather/sunnyday_day"
}
}
},
"animation.weather.sunnyday_night": {
"loop": true,
"particle_effects": {
"0.0": {
"effect": "cobblemon:weather/sunnyday_night"
}
}
},
"animation.weather.raindance": {
"loop": true,
"particle_effects": {
"0.0": {
"effect": "cobblemon:weather/raindance"
}
}
},
"animation.weather.sandstorm": {
"loop": true,
"particle_effects": {
"0.0": {
"effect": "cobblemon:weather/sandstorm"
}
}
},
"animation.weather.snow": {
"loop": true,
"particle_effects": {
"0.0": {
"effect": "cobblemon:weather/snow"
}
}
},
"animation.weather.hail": {
"loop": true,
"particle_effects": {
"0.0": {
"effect": "cobblemon:weather/hail"
}
}
},
"animation.weather.primordialsea": {
"loop": true,
"particle_effects": {
"0.0": {
"effect": "cobblemon:weather/primordialsea"
}
}
},
"animation.weather.desolateland": {
"loop": true,
"particle_effects": {
"0.0": {
"effect": "cobblemon:weather/desolateland"
}
}
},
"animation.weather.deltastream": {
"loop": true,
"particle_effects": {
"0.0": {
"effect": "cobblemon:weather/deltastream"
}
}
}
}
}
@@ -0,0 +1,21 @@
{
"format_version": "1.12.0",
"minecraft:geometry": [
{
"description": {
"identifier": "geometry.weather",
"texture_width": 16,
"texture_height": 16,
"visible_bounds_width": 1,
"visible_bounds_height": 1,
"visible_bounds_offset": [0, 0, 0]
},
"bones": [
{
"name": "weather",
"pivot": [0, 0, 0]
}
]
}
]
}
@@ -0,0 +1,12 @@
{
"poses": {
"evolve": {
"poseName": "weather",
"transformTicks": 0,
"poseTypes": ["NONE"],
"animations": [
"bedrock(weather, deltastream)"
]
}
}
}
@@ -0,0 +1,12 @@
{
"poses": {
"evolve": {
"poseName": "weather",
"transformTicks": 0,
"poseTypes": ["NONE"],
"animations": [
"bedrock(weather, desolateland)"
]
}
}
}
@@ -0,0 +1,12 @@
{
"poses": {
"evolve": {
"poseName": "weather",
"transformTicks": 0,
"poseTypes": ["NONE"],
"animations": [
"bedrock(weather, hail)"
]
}
}
}
@@ -0,0 +1,12 @@
{
"poses": {
"evolve": {
"poseName": "weather",
"transformTicks": 0,
"poseTypes": ["NONE"],
"animations": [
"bedrock(weather, primordialsea)"
]
}
}
}
@@ -0,0 +1,12 @@
{
"poses": {
"evolve": {
"poseName": "weather",
"transformTicks": 0,
"poseTypes": ["NONE"],
"animations": [
"bedrock(weather, raindance)"
]
}
}
}
@@ -0,0 +1,12 @@
{
"poses": {
"evolve": {
"poseName": "weather",
"transformTicks": 0,
"poseTypes": ["NONE"],
"animations": [
"bedrock(weather, sandstorm)"
]
}
}
}
@@ -0,0 +1,12 @@
{
"poses": {
"evolve": {
"poseName": "weather",
"transformTicks": 0,
"poseTypes": ["NONE"],
"animations": [
"bedrock(weather, snow)"
]
}
}
}
@@ -0,0 +1,12 @@
{
"poses": {
"evolve": {
"poseName": "weather",
"transformTicks": 0,
"poseTypes": ["NONE"],
"animations": [
"bedrock(weather, sunnyday_day)"
]
}
}
}
@@ -0,0 +1,12 @@
{
"poses": {
"evolve": {
"poseName": "weather",
"transformTicks": 0,
"poseTypes": ["NONE"],
"animations": [
"bedrock(weather, sunnyday_night)"
]
}
}
}
@@ -0,0 +1,69 @@
{
"name": "cobblemon:weather",
"order": 0,
"variations": [
{
"aspects": ["weather-sunnyday_day"],
"poser": "cobblemon:weather_sunnyday_day",
"model": "cobblemon:weather.geo",
"texture": "cobblemon:textures/white.png",
"layers": []
},
{
"aspects": ["weather-sunnyday_night"],
"poser": "cobblemon:weather_sunnyday_night",
"model": "cobblemon:weather.geo",
"texture": "cobblemon:textures/white.png",
"layers": []
},
{
"aspects": ["weather-raindance"],
"poser": "cobblemon:weather_raindance",
"model": "cobblemon:weather.geo",
"texture": "cobblemon:textures/white.png",
"layers": []
},
{
"aspects": ["weather-hail"],
"poser": "cobblemon:weather_hail",
"model": "cobblemon:weather.geo",
"texture": "cobblemon:textures/white.png",
"layers": []
},
{
"aspects": ["weather-snow"],
"poser": "cobblemon:weather_snow",
"model": "cobblemon:weather.geo",
"texture": "cobblemon:textures/white.png",
"layers": []
},
{
"aspects": ["weather-sandstorm"],
"poser": "cobblemon:weather_sandstorm",
"model": "cobblemon:weather.geo",
"texture": "cobblemon:textures/white.png",
"layers": []
},
{
"aspects": ["weather-primordialsea"],
"poser": "cobblemon:weather_primordialsea",
"model": "cobblemon:weather.geo",
"texture": "cobblemon:textures/white.png",
"layers": []
},
{
"aspects": ["weather-desolateland"],
"poser": "cobblemon:weather_desolateland",
"model": "cobblemon:weather.geo",
"texture": "cobblemon:textures/white.png",
"layers": []
},
{
"aspects": ["weather-deltastream"],
"poser": "cobblemon:weather_deltastream",
"model": "cobblemon:weather.geo",
"texture": "cobblemon:textures/white.png",
"layers": []
}
]
}
@@ -1538,6 +1538,7 @@
"gamerule.doPokemonSpawning": "Spawn Pokémon",
"gamerule.doShinyStarters": "Starters will be shiny",
"gamerule.doPokemonLoot": "Drop Pokémon Loot",
"gamerule.weatherAffectsBattles": "In-game weather affects Pokémon battles",
"cobblemon.command.checkspawns.invalid-bucket": "Invalid Spawn Bucket",
"cobblemon.command.checkspawns.spawns": "Spawns",
"cobblemon.command.checkspawns.nothing": "Nothing can spawn here right now.",
@@ -66,6 +66,9 @@
"cobblemon:capturestar",
"cobblemon:pop_particle",
"cobblemon:white",
"cobblemon:invisible"
"cobblemon:invisible",
"cobblemon:snow",
"cobblemon:rain",
"cobblemon:rain_splash"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

@@ -0,0 +1,18 @@
{
"type": "choice",
"keys": ["weather"],
"default": "false",
"choices": [
"sunnyday_day",
"sunnyday_night",
"raindance",
"snow",
"hail",
"sandstorm",
"deltasteam",
"primordialsea",
"desolateland"
],
"isAspect": true,
"aspectFormat": "weather-{{choice}}"
}
@@ -0,0 +1,10 @@
{
"id": "cobblemon:desert_sun",
"timeRange": "midday",
"biomes": [
"#cobblemon:is_sandy"
],
"isRaining": false,
"isThundering": false,
"result": "sunnyday"
}