Compare commits

..
1 Commits
Author SHA1 Message Date
Paul dcfed61069 Push for non-snapshot 1.1.0 2022-11-28 10:03:14 +10:00
2292 changed files with 10140 additions and 17625 deletions

No files matched your search

+1 -5
View File
@@ -28,8 +28,4 @@ forge*changelog.txt
/logs/
.vscode/launch.json
.vscode
common/src/main/resources/assets/cobblemon/showdown
# Generated files
*/generated/resources/*
/fabric/src/generated/resources/cobblemon-common.accesswidener
common/src/main/resources/assets/cobblemon/showdown
+1 -1
View File
@@ -10,7 +10,7 @@ stages:
build-job:
stage: build
script:
- export GRADLE_OPTS="-Xmx4G -Xms4G"
- export GRADLE_OPTS="-Xmx3G -Xms3G"
- chmod +x ./gradlew
- ./gradlew build
cache:
+5 -60
View File
@@ -1,63 +1,9 @@
# Changelog
## [1.2.0 - The Customization Update (January 1st, 2023)](#1-2-0)
### Additions
- Added models for Natu and Xatu, Murkrow and Honchkrow, Wailmer and Wailord.
- Added new PC interface and it is beautiful.
- Reworked the battle system so that battles load faster, cause fewer bugs, and can run on shared server hosts. This is a very big change that also reduced the size of the mod by 50mb!
- Added full resource pack / data pack customization of models, textures, animations, spawning, and spawn file presets to make custom Pokémon species and variations very easy to create. You can find the guide for creating custom Pokémon on [our website](https://cobblemon.com/guides/custompokemon.html)!
- Added water surface spawning for Pokémon like Lapras.
- Added emissive texture support to Pokémon render layers.
- Added compatibility for Mod Menu ([CurseForge](https://www.curseforge.com/minecraft/mc-mods/modmenu), [Modrinth](https://modrinth.com/mod/modmenu)).
- Added blank ambient Pokémon cries so cries can be added via resource packs.
- Added new sounds for GUIs and item uses.
- Added `nature` and `pokeball` options to commands such as `/spawnpokemon` and `/givepokemon`.
### Changes
- Reinforced party and PC saving to make data corruption from crashes less bad.
- Added a config option for whether the starter config will be exported, making it more maintainable as we add starter Pokémon.
- Battles now start with the options menu open for convenience.
- Doubled the default charge rate of healers. You'd need to reset your config under `./config/cobblemon/main.json` to see this change!
- Changed the default Apricorn seed chance config value from 6% to 10%.
- The mod now correctly reports our dependency on Architectury API so people don't get super confused when things don't work.
- Pokémon now look at their opponents during battle.
- Updated Sableye's animations to be super freaky.
- Changed the healer advancements to make the healing machine's mechanics more obvious.
### Fixes
- Fixed an incompatibility with [Porting Lib](https://github.com/Fabricators-of-Create/Porting-Lib) used by Fabric ports of mods such as Create or Twilight Forest.
- Fixed HP and max HP values in the battle GUI not being correct.
- Fixed some animations on snake-type Pokémon being super slow.
- Fixed a typo in the English name for Calcium. Calcuim.
- Fixed Pokémon gradually becoming rarer around you if you move long distances.
- Fixed a shoulder mount crash on Fabric.
- Fixed a rare issue where chunks would take a really long time to generate.
- Fixed a singleplayer bug where battles wouldn't work after leaving then rejoining a world.
- Fixed stat calculations for everything except HP. HP was fine though :).
- Fixed a randomized Pokémon spawning in mountains that corrupted the data of whatever party or PC it got into. Yikes.
- Fixed a rare crash involving literally random number generation. A random crash involving random numbers.
- Fixed all regular Mewtwo having the stats and types of Mega Mewtwo X - same deal with many other Pokémon.
- Fixed the framing of many Pokémon in GUIs.
- Fixed texture glitches with Rattata and Nidoqueen (again!).
- Fixed dropped item forms of all Pokéballs and the Healing Machine, and slightly adjusted all other display settings
- Fixed issues with forms not showing the correct evolution in the evolution menu.
- Fixed some alternate forms not having the alternate stats and types.
- Fixed moves that only work in double battles not being selectable at all (such as Helping Hand and Aromatic Mist).
- Fixed abilities not remaining legal in some forms.
- Fixed Poké Ball capture effects not triggering after a successful capture, such as the Heal Ball's healing effect.
- Fixed multiple-hit moves sending gibberish into the battle chat.
- Fixed Pyukumuku not being appropriately scaled.
- Fixed shiny and other variations of Pokémon not showing in the battle GUI.
- Fixed Eevee being poorly positioned and un-animated on shoulders.
- Fixed a Pokémon's hitbox not updating when it evolves while sent out.
- Fixed a Pokémon's PP going from zero to above the maximum when entering another battle.
## [1.1.1 (November 27th, 2022)](#1-1-1)
### Fixes
- Fixed a critical issue with servers where Pokémon data didn't properly synchronize and so you couldn't see any.
## [1.1.0 - The Swim and Sleep Update (November 27th, 2022)](#1-1-0)
## 1.1.0 - The Swim and Sleep Update
#### "Ideally not at the same time."
### Additions
- Added a new GUI for viewing party information, rearranging moves, and evolving Pokémon. It looks too good.
- Starter Pokémon will sleep on top of you if sent out when you get on a bed.
@@ -111,7 +57,6 @@
- Statuses like sleep and frozen no longer last forever if it was carried over from a previous battle.
### Localization
- Added species, ability, and move translations for `ko_ko`, `jp_jp`, `fr_fr`, `es_es`, `it_it`, and `zh_cn`.
## [1.0.0 (November 12th, 2022)](#1-0-0)
- Added species, ability, and move translations for ko_ko, jp_jp, fr_fr, es_es, it_it, and zh_cn.
## 1.0.0 (2022-11-12)
- Initial release.
@@ -19,7 +19,6 @@ tasks {
archiveClassifier.set("dev-shadow")
archiveBaseName.set("Cobblemon-${project.name}")
configurations = listOf(bundle)
mergeServiceFiles()
}
remapJar {
+7 -1
View File
@@ -21,7 +21,8 @@ dependencies {
modApi(libs.mclib)
// For Showdown
modCompileOnly(libs.graal)
modCompileOnly(libs.javet) // Linux or Windows
modCompileOnly(libs.javetMac) // Mac OS (x86_64 Only)
//shadowCommon group: 'commons-io', name: 'commons-io', version: '2.6'
@@ -32,6 +33,11 @@ dependencies {
testImplementation(libs.mockk)
compileOnly("net.luckperms:api:${rootProject.property("luckperms_version")}")
// For Showdown
// modCompileOnly 'com.caoccao.javet:javet:1.0.6' // Linux or Windows
// modCompileOnly 'com.caoccao.javet:javet-macos:1.0.6' // Mac OS (x86_64 Only)
// modCompileOnly group: 'commons-io', name: 'commons-io', version: '2.6'
}
publishing {
@@ -35,10 +35,9 @@ import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffectRegistry
import com.cobblemon.mod.common.api.pokemon.experience.ExperienceCalculator
import com.cobblemon.mod.common.api.pokemon.experience.ExperienceGroups
import com.cobblemon.mod.common.api.pokemon.experience.StandardExperienceCalculator
import com.cobblemon.mod.common.api.pokemon.feature.ChoiceSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.FlagSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.GlobalSpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.EnumSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.feature.FlagSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeature
import com.cobblemon.mod.common.api.pokemon.stats.EvCalculator
import com.cobblemon.mod.common.api.pokemon.stats.Generation8EvCalculator
import com.cobblemon.mod.common.api.pokemon.stats.StatProvider
@@ -48,6 +47,7 @@ import com.cobblemon.mod.common.api.reactive.Observable.Companion.map
import com.cobblemon.mod.common.api.reactive.Observable.Companion.takeFirst
import com.cobblemon.mod.common.api.scheduling.ScheduledTaskTracker
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.CobblemonSpawningProspector
import com.cobblemon.mod.common.api.spawning.context.AreaContextResolver
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningProspector
@@ -67,6 +67,7 @@ import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.battles.ShowdownThread
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.battles.runner.ShowdownConnection
import com.cobblemon.mod.common.config.CobblemonConfig
import com.cobblemon.mod.common.config.constraint.IntConstraint
import com.cobblemon.mod.common.config.starter.StarterConfig
@@ -78,12 +79,21 @@ import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacke
import com.cobblemon.mod.common.net.serverhandling.ServerPacketRegistrar
import com.cobblemon.mod.common.permission.LaxPermissionValidator
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.aspects.FishStripesAspect
import com.cobblemon.mod.common.pokemon.aspects.GENDER_ASPECT
import com.cobblemon.mod.common.pokemon.aspects.SHINY_ASPECT
import com.cobblemon.mod.common.pokemon.aspects.SeasonAspect
import com.cobblemon.mod.common.pokemon.aspects.SnakePatternAspect
import com.cobblemon.mod.common.pokemon.evolution.variants.BlockClickEvolution
import com.cobblemon.mod.common.pokemon.feature.BattleCriticalHitsFeature
import com.cobblemon.mod.common.pokemon.feature.DamageTakenFeature
import com.cobblemon.mod.common.pokemon.feature.TagSeasonResolver
import com.cobblemon.mod.common.pokemon.feature.DamageTakenFeature
import com.cobblemon.mod.common.pokemon.feature.FISH_STRIPES
import com.cobblemon.mod.common.pokemon.feature.FishStripesFeature
import com.cobblemon.mod.common.pokemon.feature.SEASON
import com.cobblemon.mod.common.pokemon.feature.SNAKE_PATTERN
import com.cobblemon.mod.common.pokemon.feature.SeasonFeature
import com.cobblemon.mod.common.pokemon.feature.SnakePatternFeature
import com.cobblemon.mod.common.pokemon.properties.HiddenAbilityPropertyType
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.pokemon.properties.UntradeableProperty
@@ -91,6 +101,7 @@ import com.cobblemon.mod.common.pokemon.properties.tags.PokemonFlagProperty
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
import com.cobblemon.mod.common.registry.CompletableRegistry
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.getServer
import com.cobblemon.mod.common.util.ifDedicatedServer
@@ -121,11 +132,12 @@ import org.apache.logging.log4j.LogManager
object Cobblemon {
const val MODID = "cobblemon"
const val VERSION = "1.2.0"
const val VERSION = "1.1.0"
const val CONFIG_PATH = "config/$MODID/main.json"
val LOGGER = LogManager.getLogger()
lateinit var implementation: CobblemonImplementation
lateinit var showdown: ShowdownConnection
var captureCalculator: CaptureCalculator = CobblemonGen348CaptureCalculator
var experienceCalculator: ExperienceCalculator = StandardExperienceCalculator
var evYieldCalculator: EvCalculator = Generation8EvCalculator
@@ -150,6 +162,7 @@ object Cobblemon {
DropEntry.register("item", ItemDropEntry::class.java, isDefault = true)
ExperienceGroups.registerDefaults()
PokemonSpecies.observable.subscribe { CobblemonSpawnPools.load() }
this.loadConfig()
this.implementation = implementation
@@ -212,12 +225,21 @@ object Cobblemon {
SHINY_ASPECT.register()
GENDER_ASPECT.register()
SnakePatternAspect.register()
FishStripesAspect.register()
SeasonAspect.register()
SpeciesFeatures.types["choice"] = ChoiceSpeciesFeatureProvider::class.java
SpeciesFeatures.types["flag"] = FlagSpeciesFeatureProvider::class.java
GlobalSpeciesFeatures.register(DamageTakenFeature.ID) { DamageTakenFeature() }
GlobalSpeciesFeatures.register(BattleCriticalHitsFeature.ID) { BattleCriticalHitsFeature() }
config.flagSpeciesFeatures.forEach(FlagSpeciesFeature::registerWithPropertyAndAspect)
config.globalFlagSpeciesFeatures.forEach(FlagSpeciesFeature::registerWithPropertyAndAspect)
FlagSpeciesFeature.registerWithPropertyAndAspect(DataKeys.ALOLAN)
FlagSpeciesFeature.registerWithPropertyAndAspect(DataKeys.GALARIAN)
FlagSpeciesFeature.registerWithPropertyAndAspect(DataKeys.HISUIAN)
FlagSpeciesFeature.registerWithPropertyAndAspect(DataKeys.VALENCIAN)
SpeciesFeature.registerGlobalFeature(DamageTakenFeature.ID) { DamageTakenFeature() }
SpeciesFeature.registerGlobalFeature(BattleCriticalHitsFeature.ID) { BattleCriticalHitsFeature() }
EnumSpeciesFeature.registerWithProperty(SNAKE_PATTERN, SnakePatternFeature::class.java)
EnumSpeciesFeature.registerWithProperty(SEASON, SeasonFeature::class.java)
EnumSpeciesFeature.registerWithProperty(FISH_STRIPES, FishStripesFeature::class.java)
CustomPokemonProperty.register(UntradeableProperty)
CustomPokemonProperty.register(UncatchableProperty)
@@ -258,6 +280,7 @@ object Cobblemon {
SERVER_STOPPED.subscribe {
storage.unregisterAll()
playerData.saveAll()
showdown.close()
}
SERVER_STARTED.subscribe {
bestSpawner.onServerStarted()
@@ -347,23 +370,19 @@ object Cobblemon {
}
fun loadStarterConfig(): StarterConfig {
if (config.exportStarterConfig) {
val file = File("config/cobblemon/starters.json")
file.parentFile.mkdirs()
if (!file.exists()) {
val config = StarterConfig()
val pw = PrintWriter(file)
StarterConfig.GSON.toJson(config, pw)
pw.close()
return config
}
val reader = FileReader(file)
val config = StarterConfig.GSON.fromJson(reader, StarterConfig::class.java)
reader.close()
val file = File("config/cobblemon/starters.json")
file.parentFile.mkdirs()
if (!file.exists()) {
val config = StarterConfig()
val pw = PrintWriter(file)
StarterConfig.GSON.toJson(config, pw)
pw.close()
return config
} else {
return StarterConfig()
}
val reader = FileReader(file)
val config = StarterConfig.GSON.fromJson(reader, StarterConfig::class.java)
reader.close()
return config
}
fun saveConfig() {
@@ -32,8 +32,15 @@ object CobblemonEntities : CompletableRegistry<EntityType<*>>(Registry.ENTITY_TY
EntityAttributeRegistry.register(
{ POKEMON.get() },
{
LivingEntity.createLivingAttributes()
DefaultAttributeContainer
.builder()
.add(EntityAttributes.GENERIC_FOLLOW_RANGE)
.add(EntityAttributes.GENERIC_MAX_HEALTH)
.add(EntityAttributes.GENERIC_MOVEMENT_SPEED)
.add(EntityAttributes.HORSE_JUMP_STRENGTH)
.add(EntityAttributes.GENERIC_KNOCKBACK_RESISTANCE)
.add(EntityAttributes.GENERIC_ARMOR)
.add(EntityAttributes.GENERIC_ARMOR_TOUGHNESS)
.also { CobblemonEvents.ENTITY_ATTRIBUTE.post(EntityAttributeEvent(POKEMON.get(), it)) }
}
)
@@ -52,7 +52,6 @@ import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.Add
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.ClearEvolutionsPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.RemoveEvolutionPacket
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
import com.cobblemon.mod.common.net.messages.client.sound.UnvalidatedPlaySoundS2CPacket
import com.cobblemon.mod.common.net.messages.client.starter.OpenStarterUIPacket
import com.cobblemon.mod.common.net.messages.client.starter.SetClientPlayerDataPacket
import com.cobblemon.mod.common.net.messages.client.storage.RemoveClientPokemonPacket
@@ -208,9 +207,6 @@ object CobblemonNetwork {
buildClientMessage<SpeciesRegistrySyncPacket>()
buildClientMessage<PropertiesCompletionRegistrySyncPacket>()
// Hax
buildClientMessage<UnvalidatedPlaySoundS2CPacket>()
/**
* Server Packets
*/
@@ -16,14 +16,6 @@ import net.minecraft.util.registry.Registry
object CobblemonSounds : CompletableRegistry<SoundEvent>(Registry.SOUND_EVENT_KEY) {
private fun queue(name: String) = queue(name) { SoundEvent(cobblemonResource(name)) }
val GUI_CLICK = queue("gui.click")
val PC_ON = queue("pc.on")
val PC_OFF = queue("pc.off")
val PC_GRAB = queue("pc.grab")
val PC_DROP = queue("pc.drop")
val PC_RELEASE = queue("pc.release")
val POKE_BALL_CAPTURE_STARTED = queue("poke_ball.capture_started")
val POKE_BALL_CAPTURE_SUCCEEDED = queue("poke_ball.capture_succeeded")
val POKE_BALL_SHAKE = queue("poke_ball.shake")
@@ -31,6 +23,5 @@ object CobblemonSounds : CompletableRegistry<SoundEvent>(Registry.SOUND_EVENT_KE
val POKE_BALL_HIT = queue("poke_ball.hit")
val POKE_BALL_SEND_OUT = queue("poke_ball.send_out")
val POKE_BALL_RECALL = queue("poke_ball.recall")
val ITEM_USE = queue("item.use")
}
@@ -22,6 +22,7 @@ import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
import kotlin.io.path.Path
import net.minecraft.resource.ResourceType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.MutableText
@@ -82,6 +83,6 @@ object Abilities : JsonDataRegistry<AbilityTemplate> {
fun all() = allAbilities.toList()
fun first() = allAbilities.first()
fun get(name: String) = abilityMap[name.lowercase()]
fun getOrException(name: String) = get(name) ?: throw IllegalArgumentException("Unable to find ability of name: $name")
fun getOrException(name: String) = get(name)!!
fun count() = allAbilities.size
}
@@ -21,35 +21,27 @@ import net.minecraft.text.MutableText
* @author Qu
* @since January 9th, 2022
*/
open class Ability internal constructor(var template: AbilityTemplate, var forced: Boolean) {
open class Ability internal constructor(var template: AbilityTemplate) {
val name: String
get() = template.name
val displayName: String
val displayName: MutableText
get() = template.displayName
val description: String
val description: MutableText
get() = template.description
open fun saveToNBT(nbt: NbtCompound): NbtCompound {
nbt.putString(DataKeys.POKEMON_ABILITY_NAME, name)
nbt.putBoolean(DataKeys.POKEMON_ABILITY_FORCED, forced)
return nbt
}
open fun saveToJSON(json: JsonObject): JsonObject {
json.addProperty(DataKeys.POKEMON_ABILITY_NAME, name)
json.addProperty(DataKeys.POKEMON_ABILITY_FORCED, forced)
return json
}
open fun loadFromNBT(nbt: NbtCompound): Ability {
forced = nbt.getBoolean(DataKeys.POKEMON_ABILITY_FORCED)
return this
}
open fun loadFromJSON(json: JsonObject): Ability {
forced = json.get(DataKeys.POKEMON_ABILITY_FORCED)?.asBoolean ?: false
return this
}
open fun loadFromNBT(nbt: NbtCompound) = this
open fun loadFromJSON(json: JsonObject) = this
}
@@ -8,8 +8,10 @@
package com.cobblemon.mod.common.api.abilities
import com.cobblemon.mod.common.util.lang
import com.google.gson.JsonObject
import net.minecraft.nbt.NbtCompound
import net.minecraft.text.MutableText
/**
* This represents the base of an Ability.
@@ -19,14 +21,14 @@ import net.minecraft.nbt.NbtCompound
*/
class AbilityTemplate(
val name: String = "",
var builder: (AbilityTemplate, forced: Boolean) -> Ability = { template, forced -> Ability(template, forced) },
val displayName: String = "cobblemon.ability.$name",
val description: String = "cobblemon.ability.$name.desc"
var builder: (AbilityTemplate) -> Ability = { Ability(it) },
val displayName: MutableText = lang("ability.$name"),
val description: MutableText = lang("ability.$name.desc")
) {
/**
* Returns the Ability or if applicable the extension connected to this template
*/
fun create(forced: Boolean = false) = builder(this, forced)
fun create() = builder(this)
/**
@@ -34,12 +36,12 @@ class AbilityTemplate(
*
* Ability extensions need to write and read their needed data from here.
*/
fun create(nbt: NbtCompound) = create().loadFromNBT(nbt)
fun create(nbt: NbtCompound) = builder(this).loadFromNBT(nbt)
/**
* Returns the Ability and loads the given JSON object into it.
*
* Ability extensions need to write and read their needed data from here.
*/
fun create(json: JsonObject) = create().loadFromJSON(json)
fun create(json: JsonObject) = builder(this).loadFromJSON(json)
}
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.battles.model
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.Cobblemon.showdown
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
@@ -25,11 +26,13 @@ import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.battles.dispatch.BattleDispatch
import com.cobblemon.mod.common.battles.dispatch.DispatchResult
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.runner.GraalShowdown
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
import com.cobblemon.mod.common.pokemon.feature.BattleCriticalHitsFeature
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getPlayer
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import java.util.UUID
import java.util.concurrent.ConcurrentLinkedQueue
import net.minecraft.text.Text
@@ -47,7 +50,6 @@ open class PokemonBattle(
) {
/** Whether or not logging will be silenced for this battle. */
var mute = true
init {
side1.battle = this
side2.battle = this
@@ -146,8 +148,17 @@ open class PokemonBattle(
}
fun writeShowdownAction(vararg messages: String) {
log(messages.joinToString("\n"))
GraalShowdown.sendToShowdown(battleId, messages.toList().toTypedArray())
val jsonArray = JsonArray()
for (message in messages) {
jsonArray.add(message)
}
val request = JsonObject()
request.addProperty(DataKeys.REQUEST_TYPE, DataKeys.REQUEST_BATTLE_SEND_MESSAGE)
request.addProperty(DataKeys.REQUEST_BATTLE_ID, battleId.toString())
request.add(DataKeys.REQUEST_MESSAGES, jsonArray)
val json = BattleRegistry.gson.toJson(request)
log(json)
showdown.write(json)
}
fun turn(newTurnNumber: Int) {
@@ -25,7 +25,7 @@ interface DataProvider {
*
* @param registry The [DataRegistry] being registered.
*/
fun <T : DataRegistry> register(registry: T): T
fun register(registry: DataRegistry)
/**
* Attempts to find a [DataRegistry] with the given [Identifier].
@@ -9,11 +9,8 @@
package com.cobblemon.mod.common.api.net
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.util.getServer
import net.minecraft.network.PacketByteBuf
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.registry.RegistryKey
import net.minecraft.world.World
/*
* Simple packet interface to make a more traditional layout for netcode
@@ -33,17 +30,4 @@ interface NetworkPacket {
}
fun sendToAllPlayers() = CobblemonNetwork.sendToAllPlayers(this)
fun sendToServer() = CobblemonNetwork.sendToServer(this)
// A copy from PlayerManager#sendToAround to work with our packets
fun sendToPlayersAround(x: Double, y: Double, z: Double, distance: Double, worldKey: RegistryKey<World>, exclusionCondition: (ServerPlayerEntity) -> Boolean = { false }) {
val server = getServer() ?: return
server.playerManager.playerList.filter { player ->
if (exclusionCondition.invoke(player))
return@filter false
val xDiff = x - player.x
val yDiff = y - player.y
val zDiff = z - player.z
return@filter (xDiff * xDiff + yDiff * yDiff + zDiff) < distance * distance
}
.forEach { player -> CobblemonNetwork.sendToPlayer(player, this) }
}
}
@@ -26,127 +26,127 @@ object Natures {
private val allNatures = mutableListOf<Nature>()
val HARDY = registerNature(
Nature(cobblemonResource("hardy"), "cobblemon.nature.hardy",
Nature(cobblemonResource("hardy"), lang("nature.hardy"),
null, null, null, null)
)
val LONELY = registerNature(
Nature(cobblemonResource("lonely"), "cobblemon.nature.lonely",
Nature(cobblemonResource("lonely"), lang("nature.lonely"),
Stats.ATTACK, Stats.DEFENCE, Flavor.SPICY, Flavor.SOUR)
)
val BRAVE = registerNature(
Nature(cobblemonResource("brave"), "cobblemon.nature.brave",
Nature(cobblemonResource("brave"), lang("nature.brave"),
Stats.ATTACK, Stats.SPEED, Flavor.SPICY, Flavor.SWEET)
)
val ADAMANT = registerNature(
Nature(cobblemonResource("adamant"), "cobblemon.nature.adamant",
Nature(cobblemonResource("adamant"), lang("nature.adamant"),
Stats.ATTACK, Stats.SPECIAL_ATTACK, Flavor.SPICY, Flavor.DRY)
)
val NAUGHTY = registerNature(
Nature(cobblemonResource("naughty"), "cobblemon.nature.naughty",
Nature(cobblemonResource("naughty"), lang("nature.naughty"),
Stats.ATTACK, Stats.SPECIAL_DEFENCE, Flavor.SPICY, Flavor.BITTER)
)
val BOLD = registerNature(
Nature(cobblemonResource("bold"), "cobblemon.nature.bold",
Nature(cobblemonResource("bold"), lang("nature.bold"),
Stats.DEFENCE, Stats.ATTACK, Flavor.SOUR, Flavor.SPICY)
)
val DOCILE = registerNature(
Nature(cobblemonResource("docile"), "cobblemon.nature.docile",
Nature(cobblemonResource("docile"), lang("nature.docile"),
null, null, null, null)
)
val RELAXED = registerNature(
Nature(cobblemonResource("relaxed"), "cobblemon.nature.relaxed",
Nature(cobblemonResource("relaxed"), lang("nature.relaxed"),
Stats.DEFENCE, Stats.SPEED, Flavor.SOUR, Flavor.SWEET)
)
val IMPISH = registerNature(
Nature(cobblemonResource("impish"), "cobblemon.nature.impish",
Nature(cobblemonResource("impish"), lang("nature.impish"),
Stats.DEFENCE, Stats.SPECIAL_ATTACK, Flavor.SOUR, Flavor.DRY)
)
val LAX = registerNature(
Nature(cobblemonResource("lax"), "cobblemon.nature.lax",
Nature(cobblemonResource("lax"), lang("nature.lax"),
Stats.DEFENCE, Stats.SPECIAL_DEFENCE, Flavor.SOUR, Flavor.BITTER)
)
val TIMID = registerNature(
Nature(cobblemonResource("timid"), "cobblemon.nature.timid",
Nature(cobblemonResource("timid"), lang("nature.timid"),
Stats.SPEED, Stats.ATTACK, Flavor.SWEET, Flavor.SPICY)
)
val HASTY = registerNature(
Nature(cobblemonResource("hasty"), "cobblemon.nature.hasty",
Nature(cobblemonResource("hasty"), lang("nature.hasty"),
Stats.SPEED, Stats.DEFENCE, Flavor.SWEET, Flavor.SOUR)
)
val SERIOUS = registerNature(
Nature(cobblemonResource("serious"), "cobblemon.nature.serious",
Nature(cobblemonResource("serious"), lang("nature.serious"),
null, null, null, null)
)
val JOLLY = registerNature(
Nature(cobblemonResource("jolly"), "cobblemon.nature.jolly",
Nature(cobblemonResource("jolly"), lang("nature.jolly"),
Stats.SPEED, Stats.SPECIAL_ATTACK, Flavor.SWEET, Flavor.DRY)
)
val NAIVE = registerNature(
Nature(cobblemonResource("naive"), "cobblemon.nature.naive",
Nature(cobblemonResource("naive"), lang("nature.naive"),
Stats.SPEED, Stats.SPECIAL_DEFENCE, Flavor.SWEET, Flavor.BITTER)
)
val MODEST = registerNature(
Nature(cobblemonResource("modest"), "cobblemon.nature.modest",
Nature(cobblemonResource("modest"), lang("nature.modest"),
Stats.SPECIAL_ATTACK, Stats.ATTACK, null, null)
)
val MILD = registerNature(
Nature(cobblemonResource("mild"), "cobblemon.nature.mild",
Nature(cobblemonResource("mild"), lang("nature.mild"),
Stats.SPECIAL_ATTACK, Stats.DEFENCE, Flavor.DRY, Flavor.SOUR)
)
val QUIET = registerNature(
Nature(cobblemonResource("quiet"), "cobblemon.nature.quiet",
Nature(cobblemonResource("quiet"), lang("nature.quiet"),
Stats.SPECIAL_ATTACK, Stats.SPEED, Flavor.DRY, Flavor.SWEET)
)
val BASHFUL = registerNature(
Nature(cobblemonResource("bashful"), "cobblemon.nature.bashful",
Nature(cobblemonResource("bashful"), lang("nature.bashful"),
null, null, null, null)
)
val RASH = registerNature(
Nature(cobblemonResource("rash"), "cobblemon.nature.rash",
Nature(cobblemonResource("rash"), lang("nature.rash"),
Stats.SPECIAL_ATTACK, Stats.SPECIAL_DEFENCE, Flavor.DRY, Flavor.BITTER)
)
val CALM = registerNature(
Nature(cobblemonResource("calm"), "cobblemon.nature.calm",
Nature(cobblemonResource("calm"), lang("nature.calm"),
Stats.SPECIAL_DEFENCE, Stats.ATTACK, Flavor.BITTER, Flavor.SPICY)
)
val GENTLE = registerNature(
Nature(cobblemonResource("gentle"), "cobblemon.nature.gentle",
Nature(cobblemonResource("gentle"), lang("nature.gentle"),
Stats.SPECIAL_DEFENCE, Stats.DEFENCE, Flavor.BITTER, Flavor.SOUR)
)
val SASSY = registerNature(
Nature(cobblemonResource("sassy"), "cobblemon.nature.sassy",
Nature(cobblemonResource("sassy"), lang("nature.sassy"),
Stats.SPECIAL_DEFENCE, Stats.SPEED, Flavor.BITTER, Flavor.SWEET)
)
val CAREFUL = registerNature(
Nature(cobblemonResource("careful"), "cobblemon.nature.careful",
Nature(cobblemonResource("careful"), lang("nature.careful"),
Stats.SPECIAL_DEFENCE, Stats.SPECIAL_ATTACK, Flavor.BITTER, Flavor.DRY)
)
val QUIRKY = registerNature(
Nature(cobblemonResource("quirky"), "cobblemon.nature.quirky",
Nature(cobblemonResource("quirky"), lang("nature.quirky"),
null, null, null, null)
)
@@ -174,7 +174,4 @@ object Natures {
fun getRandomNature(): Nature {
return allNatures.random()
}
fun all(): Collection<Nature> = this.allNatures.toList()
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.pokemon
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -26,7 +25,6 @@ import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtElement
import net.minecraft.nbt.NbtList
import net.minecraft.nbt.NbtString
import net.minecraft.util.Identifier
import net.minecraft.util.InvalidIdentifierException
import net.minecraft.world.World
@@ -76,8 +74,6 @@ open class PokemonProperties {
props.species = parseSpeciesIdentifier(keyPairs)
props.form = parseForm(keyPairs)
props.friendship = parseIntProperty(keyPairs, listOf("friendship"))?.coerceIn(0, Cobblemon.config.maxPokemonFriendship)
props.pokeball = parseIdentifierOfRegistry(keyPairs, listOf("pokeball")) { identifier -> PokeBalls.getPokeBall(identifier)?.name?.toString() }
props.nature = parseIdentifierOfRegistry(keyPairs, listOf("nature")) { identifier -> Natures.getNature(identifier)?.name?.toString() }
props.updateAspects()
return props
}
@@ -96,17 +92,6 @@ open class PokemonProperties {
}
}
private fun parseIdentifierOfRegistry(keyPairs: MutableList<Pair<String, String?>>, validKeys: List<String>, valueFetcher: (Identifier) -> String?): String? {
val matched = getMatchedKeyPair(keyPairs, validKeys) ?: return null
val value = matched.second?.lowercase() ?: return null
return try {
val identifier = value.asIdentifierDefaultingNamespace()
valueFetcher(identifier)
} catch (_: InvalidIdentifierException) {
null
}
}
private fun parseSpeciesIdentifier(keyPairs: MutableList<Pair<String, String?>>): String? {
val matched = getMatchedKeyPair(keyPairs, listOf("species"))
if (matched != null) {
@@ -201,8 +186,6 @@ open class PokemonProperties {
var gender: Gender? = null
var level: Int? = null
var friendship: Int? = null
var pokeball: String? = null
var nature: String? = null
var aspects: Set<String> = emptySet()
var customProperties = mutableListOf<CustomPokemonProperty>()
@@ -235,8 +218,6 @@ open class PokemonProperties {
gender?.let { pokemon.gender = it }
level?.let { pokemon.level = it }
friendship?.let { pokemon.setFriendship(it) }
pokeball?.let { PokeBalls.getPokeBall(it.asIdentifierDefaultingNamespace())?.let { pokeball -> pokemon.caughtBall = pokeball } }
nature?.let { Natures.getNature(it.asIdentifierDefaultingNamespace())?.let { nature -> pokemon.nature = nature } }
customProperties.forEach { it.apply(pokemon) }
}
@@ -257,8 +238,6 @@ open class PokemonProperties {
shiny?.let { pokemonEntity.pokemon.shiny = it }
gender?.let { pokemonEntity.pokemon.gender = it }
friendship?.let { pokemonEntity.pokemon.setFriendship(it) }
pokeball?.let { PokeBalls.getPokeBall(it.asIdentifierDefaultingNamespace())?.let { pokeball -> pokemonEntity.pokemon.caughtBall = pokeball } }
nature?.let { Natures.getNature(it.asIdentifierDefaultingNamespace())?.let { nature -> pokemonEntity.pokemon.nature = nature } }
customProperties.forEach { it.apply(pokemonEntity) }
}
@@ -282,8 +261,6 @@ open class PokemonProperties {
}
form?.takeIf { !it.equals(pokemon.form.name, true) }?.let { return false }
friendship?.takeIf { it != pokemon.friendship }?.let { return false }
pokeball?.takeIf { it != pokemon.caughtBall.name.toString() }?.let { return false }
nature?.takeIf { it != pokemon.nature.name.toString() }?.let { return false }
return customProperties.none { !it.matches(pokemon) }
}
@@ -305,20 +282,11 @@ open class PokemonProperties {
}
form?.takeIf { !it.equals(pokemonEntity.pokemon.form.name, true) }?.let { return false }
friendship?.takeIf { it != pokemonEntity.pokemon.friendship }?.let { return false }
pokeball?.takeIf { it != pokemonEntity.pokemon.caughtBall.name.toString() }?.let { return false }
nature?.takeIf { it != pokemonEntity.pokemon.nature.name.toString() }?.let { return false }
return customProperties.none { !it.matches(pokemonEntity) }
}
fun create(): Pokemon {
val pokemon = Pokemon()
apply(pokemon)
// Force the setter to initialize it
pokemon.species = pokemon.species
if (pokemon.moveSet.none { it != null }) {
pokemon.initializeMoveset()
}
return pokemon
return Pokemon().also { apply(it) }.also { if (it.moveSet.none { it != null }) it.initializeMoveset() }
}
fun createEntity(world: World): PokemonEntity {
@@ -334,8 +302,6 @@ open class PokemonProperties {
species?.let { nbt.putString(DataKeys.POKEMON_SPECIES_TEXT, it) }
form?.let { nbt.putString(DataKeys.POKEMON_FORM_ID, it) }
friendship?.let { nbt.putInt(DataKeys.POKEMON_FRIENDSHIP, it) }
pokeball?.let { nbt.putString(DataKeys.POKEMON_CAUGHT_BALL, it) }
nature?.let { nbt.putString(DataKeys.POKEMON_NATURE, it) }
val custom = NbtList()
customProperties.map { NbtString.of(it.asString()) }.forEach { custom.add(it) }
nbt.put(DataKeys.POKEMON_PROPERTIES_CUSTOM, custom)
@@ -350,8 +316,6 @@ open class PokemonProperties {
species = if (tag.contains(DataKeys.POKEMON_SPECIES_TEXT)) tag.getString(DataKeys.POKEMON_SPECIES_TEXT) else null
form = if (tag.contains(DataKeys.POKEMON_FORM_ID)) tag.getString(DataKeys.POKEMON_FORM_ID) else null
friendship = if (tag.contains(DataKeys.POKEMON_FRIENDSHIP)) tag.getInt(DataKeys.POKEMON_FRIENDSHIP) else null
pokeball = if (tag.contains(DataKeys.POKEMON_CAUGHT_BALL)) tag.getString(DataKeys.POKEMON_CAUGHT_BALL) else null
nature = if (tag.contains(DataKeys.POKEMON_NATURE)) tag.getString(DataKeys.POKEMON_NATURE) else null
val custom = tag.getList(DataKeys.POKEMON_PROPERTIES_CUSTOM, NbtElement.STRING_TYPE.toInt())
// This is kinda gross
custom.forEach { customProperties.addAll(parse(it.asString()).customProperties) }
@@ -368,12 +332,9 @@ open class PokemonProperties {
species?.let { json.addProperty(DataKeys.POKEMON_SPECIES_TEXT, it) }
form?.let { json.addProperty(DataKeys.POKEMON_FORM_ID, it) }
friendship?.let { json.addProperty(DataKeys.POKEMON_FRIENDSHIP, it) }
pokeball?.let { json.addProperty(DataKeys.POKEMON_CAUGHT_BALL, it) }
nature?.let { json.addProperty(DataKeys.POKEMON_NATURE, it) }
val custom = JsonArray()
customProperties.map { it.asString() }.forEach { custom.add(it) }
json.add(DataKeys.POKEMON_PROPERTIES_CUSTOM, custom)
return json
}
@@ -385,8 +346,6 @@ open class PokemonProperties {
species = json.get(DataKeys.POKEMON_SPECIES_TEXT)?.asString
form = json.get(DataKeys.POKEMON_FORM_ID)?.asString
friendship = json.get(DataKeys.POKEMON_FRIENDSHIP)?.asInt
pokeball = json.get(DataKeys.POKEMON_CAUGHT_BALL)?.asString
nature = json.get(DataKeys.POKEMON_NATURE)?.asString
val custom = json.get(DataKeys.POKEMON_PROPERTIES_CUSTOM)?.asJsonArray
// This is still kinda gross
custom?.forEach { customProperties.addAll(parse(it.asString).customProperties) }
@@ -402,8 +361,6 @@ open class PokemonProperties {
shiny?.let { pieces.add("shiny=$it") }
gender?.let { pieces.add("gender=$it")}
friendship?.let { pieces.add("friendship=$it") }
pokeball?.let { pieces.add("pokeball=$it") }
nature?.let { pieces.add("nature=$it") }
customProperties.forEach { pieces.add(it.asString()) }
return pieces.joinToString(separator)
}
@@ -8,6 +8,8 @@
package com.cobblemon.mod.common.api.pokemon
import com.caoccao.javet.interop.V8Host
import com.caoccao.javet.interop.V8Runtime
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.abilities.AbilityPool
import com.cobblemon.mod.common.api.abilities.AbilityTemplate
@@ -30,11 +32,10 @@ import com.cobblemon.mod.common.api.pokemon.experience.ExperienceGroupAdapter
import com.cobblemon.mod.common.api.pokemon.moves.Learnset
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.spawning.condition.TimeRange
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.api.types.adapters.ElementalTypeAdapter
import com.cobblemon.mod.common.battles.runner.GraalShowdown
import com.cobblemon.mod.common.net.messages.client.data.SpeciesRegistrySyncPacket
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
@@ -50,11 +51,11 @@ import com.cobblemon.mod.common.util.adapters.DropEntryAdapter
import com.cobblemon.mod.common.util.adapters.EggGroupAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.IntRangeAdapter
import com.cobblemon.mod.common.util.adapters.IntRangesAdapter
import com.cobblemon.mod.common.util.adapters.ItemLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.LazySetAdapter
import com.cobblemon.mod.common.util.adapters.LearnsetAdapter
import com.cobblemon.mod.common.util.adapters.NbtCompoundAdapter
import com.cobblemon.mod.common.util.adapters.TimeRangeAdapter
import com.cobblemon.mod.common.util.adapters.pokemonPropertiesShortAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.common.collect.HashBasedTable
@@ -63,6 +64,7 @@ import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import java.io.File
import java.nio.file.Files
import kotlin.io.path.Path
import kotlin.reflect.KProperty
import net.minecraft.block.Block
import net.minecraft.entity.EntityDimensions
@@ -97,7 +99,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
.registerTypeAdapter(TimeRange::class.java, IntRangesAdapter(TimeRange.timeRanges) { TimeRange(*it) })
.registerTypeAdapter(TimeRange::class.java, TimeRangeAdapter)
.registerTypeAdapter(ItemDropMethod::class.java, ItemDropMethod.adapter)
.registerTypeAdapter(SleepDepth::class.java, SleepDepth.adapter)
.registerTypeAdapter(DropEntry::class.java, DropEntryAdapter)
@@ -216,40 +218,45 @@ object PokemonSpecies : JsonDataRegistry<Species> {
this.speciesByDex.remove(species.resourceIdentifier.namespace, species.nationalPokedexNumber)
}
}
// Showdown loads mods by reading existing files as such we cannot dynamically add to the Pokédex, instead, we will overwrite the existing file and force a mod reload.
val pokedexFile = File("showdown/node_modules/pokemon-showdown/.data-dist/mods/cobblemon/pokedex.js")
Files.createDirectories(pokedexFile.toPath().parent)
pokedexFile.bufferedWriter().use { writer ->
writer.write(
V8Host.getNodeInstance().createV8Runtime<V8Runtime>().use { runtime ->
// Showdown loads mods by reading existing files as such we cannot dynamically add to the Pokédex, instead, we will overwrite the existing file and force a mod reload.
val pokedexFile = File("node_modules/pokemon-showdown/.data-dist/mods/Cobblemon/pokedex.js")
Files.createDirectories(pokedexFile.toPath().parent)
pokedexFile.bufferedWriter().use { writer ->
writer.write(
"""
"use strict";
Object.defineProperty(exports, "__esModule", {value: true});
const Pokedex = {
$pokedexDataHolder
};
exports.Pokedex = Pokedex;
""".trimIndent()
)
}
val formatsDataFile = File("node_modules/pokemon-showdown/.data-dist/mods/Cobblemon/formats-data.js")
formatsDataFile.bufferedWriter().use { writer ->
writer.write(
"""
"use strict";
Object.defineProperty(exports, "__esModule", {value: true});
const FormatsData = {
$formatsDataHolder
};
exports.FormatsData = FormatsData;
""".trimIndent()
)
}
val executor = runtime.getExecutor(
"""
"use strict";
Object.defineProperty(exports, "__esModule", {value: true});
const Pokedex = {
$pokedexDataHolder
};
exports.Pokedex = Pokedex;
""".trimIndent()
const PokemonShowdown = require('pokemon-showdown');
PokemonShowdown.Dex.modsLoaded = false;
PokemonShowdown.Dex.includeMods();
""".trimIndent()
)
executor.resourceName = "./node_modules"
executor.executeVoid()
}
val formatsDataFile = File("showdown/node_modules/pokemon-showdown/.data-dist/mods/cobblemon/formats-data.js")
formatsDataFile.bufferedWriter().use { writer ->
writer.write(
"""
"use strict";
Object.defineProperty(exports, "__esModule", {value: true});
const FormatsData = {
$formatsDataHolder
};
exports.FormatsData = FormatsData;
""".trimIndent()
)
}
GraalShowdown.context.eval("js", """
PokemonShowdown.Dex.modsLoaded = false;
PokemonShowdown.Dex.includeMods();
""".trimIndent())
Cobblemon.LOGGER.info("Finished creating showdown data for species")
}
@@ -1,87 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.feature
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.substitute
/**
* A [SpeciesFeatureProvider] which is a string value selected from a fixed list of choices. Parameters exist
* to change default behaviour, aspects, and the available choices. Choices must be lowercase.
*
* @author Hiroku
* @since November 30th, 2022
*/
open class ChoiceSpeciesFeatureProvider(
override val keys: List<String>,
var default: String? = null,
var choices: List<String> = listOf(),
var isAspect: Boolean = true,
var aspectFormat: String = "{{choice}}"
) : SpeciesFeatureProvider<StringSpeciesFeature>, CustomPokemonPropertyType<StringSpeciesFeature>, AspectProvider {
override val needsKey = true
fun getAspect(feature: StringSpeciesFeature) = aspectFormat.substitute("choice", feature.value)
override fun examples() = choices
internal constructor(): this(emptyList())
fun get(pokemon: Pokemon) = pokemon.getFeature<StringSpeciesFeature>(keys.first())
override fun invoke(pokemon: Pokemon): StringSpeciesFeature? {
val existing = pokemon.getFeature<StringSpeciesFeature>(keys.first())
return if (existing != null && existing.value in choices) {
existing
} else {
val value = if (default in choices) {
default!!
} else if (default == "random") {
// If it's mandatory, but they provided no value and no default, give it a random value.
choices.randomOrNull() ?: throw IllegalStateException("The 'choices' list is empty for species feature provider: ${keys.joinToString()}")
} else {
return null
}
fromString(value)
}
}
override fun fromString(value: String?): StringSpeciesFeature? {
val lower = value?.lowercase()
if (lower == null || lower !in choices) {
return null
}
return StringSpeciesFeature(keys.first(), lower)
}
override fun provide(pokemon: Pokemon): Set<String> {
return if (isAspect) {
pokemon.getFeature<StringSpeciesFeature>(keys.first())?.let { setOf(getAspect(it)) } ?: emptySet()
} else {
emptySet()
}
}
override fun provide(properties: PokemonProperties): Set<String> {
return if (isAspect) {
val feature = properties.customProperties.filterIsInstance<StringSpeciesFeature>().find { it.name == keys.first() }
if (feature != null) {
setOf(getAspect(feature))
} else {
emptySet()
}
} else {
emptySet()
}
}
}
@@ -0,0 +1,66 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.feature
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.pokemon.Pokemon
import com.google.gson.JsonObject
import java.util.EnumSet
import net.minecraft.nbt.NbtCompound
/**
* A [SpeciesFeature] which has a single value from an enum. Extensions of this class
* just need to provide the set of all entries of that enum.
*
* @author Hiroku
* @since October 13th, 2022
*/
abstract class EnumSpeciesFeature<T : Enum<T>> : SpeciesFeature, CustomPokemonProperty {
companion object {
fun <E : Enum<E>, T : EnumSpeciesFeature<E>> registerWithProperty(name: String, clazz: Class<T>) {
SpeciesFeature.register(name, clazz)
CustomPokemonProperty.properties.add(EnumSpeciesFeatureCustomPropertyType<E>(name))
}
}
abstract override val name: String
abstract fun getValues(): EnumSet<T>
open var enumValue = getValues().random()
override fun saveToNBT(pokemonNBT: NbtCompound): NbtCompound {
pokemonNBT.putString(name, enumValue.name)
return pokemonNBT
}
override fun loadFromNBT(pokemonNBT: NbtCompound): SpeciesFeature {
val valueName = pokemonNBT.getString(name)?.takeIf { it.isNotBlank() } ?: return this
enumValue = getValues().first { it.name == valueName }
return this
}
override fun saveToJSON(pokemonJSON: JsonObject): JsonObject {
pokemonJSON.addProperty(name, enumValue.name)
return pokemonJSON
}
override fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature {
val valueName = pokemonJSON.get(name)?.asString ?: return this
enumValue = getValues().first { it.name == valueName }
return this
}
override fun asString() = "$name=${enumValue.name}"
override fun apply(pokemon: Pokemon) {
pokemon.getFeature<EnumSpeciesFeature<T>>(name)?.enumValue = enumValue
pokemon.updateAspects()
}
override fun matches(pokemon: Pokemon) = pokemon.getFeature<EnumSpeciesFeature<T>>(name)?.enumValue == enumValue
}
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.feature
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
/**
* An implementation of [CustomPokemonPropertyType] that interprets values for some [EnumSpeciesFeature]. The key of the
* property is the name of the enum species feature.
*
* @author Hiroku
* @since October 13th, 2022
*/
open class EnumSpeciesFeatureCustomPropertyType<T : Enum<T>>(val name: String) : CustomPokemonPropertyType<EnumSpeciesFeature<T>> {
override val keys = setOf(name)
override val needsKey = true
override fun fromString(value: String?): EnumSpeciesFeature<T>? {
value ?: return null
val feature = SpeciesFeature.get(name)?.invoke() as? EnumSpeciesFeature<T> ?: return null
val enumValue = feature.getValues().find { it.name.equals(value, ignoreCase = true) } ?: return null
feature.enumValue = enumValue
return feature
}
override fun examples(): Collection<String> {
val feature = SpeciesFeature.get(name)?.invoke() as? EnumSpeciesFeature<T> ?: return emptySet()
return feature.getValues().map { it.name.lowercase() }
}
}
@@ -8,19 +8,18 @@
package com.cobblemon.mod.common.api.pokemon.feature
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.pokemon.aspect.SingleConditionalAspectProvider
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.substitute
import com.google.gson.JsonObject
import kotlin.random.Random
import net.minecraft.nbt.NbtCompound
/**
* A simple [SpeciesFeature] that is a true/false flag value. It implements [CustomPokemonProperty] for use in
* [PokemonProperties]. [FlagSpeciesFeatureProvider]s must be registered within [SpeciesFeatures].
* A simple [SpeciesFeature] that is a true/false flag value. It implements [CustomPokemonProperty]
* to provide a convenient means of registering it with a [CustomPokemonPropertyType]. That can be done
* smoothly using [FlagSpeciesFeature.registerWithProperty].
*
* Implementations of this class don't need to implement anything.
*
@@ -28,11 +27,21 @@ import net.minecraft.nbt.NbtCompound
* @since May 13th, 2022
*/
open class FlagSpeciesFeature(override val name: String) : SpeciesFeature, CustomPokemonProperty {
constructor(name: String, enabled: Boolean): this(name) {
this.enabled = enabled
companion object {
fun <T : FlagSpeciesFeature> registerWithProperty(name: String, clazz: Class<T>) {
SpeciesFeature.register(name, clazz)
CustomPokemonProperty.properties.add(FlagSpeciesFeatureCustomPropertyType(name))
}
fun registerWithPropertyAndAspect(name: String) {
SpeciesFeature.register(name) { FlagSpeciesFeature(name) }
CustomPokemonProperty.properties.add(FlagSpeciesFeatureCustomPropertyType(name))
AspectProvider.register(SingleConditionalAspectProvider.getForFeature(name))
}
}
var enabled = false
open var enabled = false
override fun saveToNBT(pokemonNBT: NbtCompound): NbtCompound {
pokemonNBT.putBoolean(name, enabled)
return pokemonNBT
@@ -57,75 +66,9 @@ open class FlagSpeciesFeature(override val name: String) : SpeciesFeature, Custo
override fun asString() = "$name=$enabled"
override fun apply(pokemon: Pokemon) {
val featureProvider = SpeciesFeatures.getFeature(name) ?: return
if (featureProvider in SpeciesFeatures.getFeaturesFor(pokemon.species)) {
val existingFeature = pokemon.getFeature<FlagSpeciesFeature>(name)
if (existingFeature != null) {
existingFeature.enabled = enabled
} else {
pokemon.features.add(FlagSpeciesFeature(name, enabled))
}
pokemon.updateAspects()
}
pokemon.getFeature<FlagSpeciesFeature>(name)?.enabled = enabled
pokemon.updateAspects()
}
override fun matches(pokemon: Pokemon) = pokemon.getFeature<FlagSpeciesFeature>(name)?.enabled == enabled
}
class FlagSpeciesFeatureProvider : SpeciesFeatureProvider<FlagSpeciesFeature>, CustomPokemonPropertyType<FlagSpeciesFeature>, AspectProvider {
override val keys: List<String>
override val needsKey get() = true
val default: String? = null
val isAspect = true
override fun examples() = setOf("true", "false")
internal constructor() {
this.keys = emptyList()
}
constructor(keys: List<String>) {
this.keys = keys
}
constructor(vararg keys: String) : this(keys.toList())
override fun invoke(pokemon: Pokemon): FlagSpeciesFeature? {
return pokemon.getFeature(keys.first())
?: when (default) {
"random" -> FlagSpeciesFeature(keys.first(), Random.Default.nextBoolean())
in setOf("true", "false") -> FlagSpeciesFeature(keys.first(), default.toBoolean())
else -> null
}
}
override fun fromString(value: String?): FlagSpeciesFeature? {
val isWeirdValue = value != null && value !in examples()
if (isWeirdValue) {
return null
}
return if (value == null) {
FlagSpeciesFeature(keys.first(), true)
} else {
FlagSpeciesFeature(keys.first(), value.toBoolean())
}
}
override fun provide(pokemon: Pokemon): Set<String> {
return if (isAspect && pokemon.getFeature<FlagSpeciesFeature>(keys.first())?.enabled == true) {
setOf(keys.first())
} else {
emptySet()
}
}
override fun provide(properties: PokemonProperties): Set<String> {
return if (isAspect && properties.customProperties.filterIsInstance<FlagSpeciesFeature>().find { it.name == keys.first() }?.enabled == true) {
setOf(keys.first())
} else {
emptySet()
}
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.feature
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
/**
* An implementation of [CustomPokemonPropertyType] that is a simple true/false value for a specific
* [FlagSpeciesFeature]. The key of the property is the name of the flag.
*
* @author Hiroku
* @since May 13th, 2022
*/
open class FlagSpeciesFeatureCustomPropertyType(val name: String) : CustomPokemonPropertyType<FlagSpeciesFeature> {
override val keys = setOf(name)
override val needsKey = true
override fun fromString(value: String?): FlagSpeciesFeature? {
var enabled = true
if (value != null) {
try {
enabled = value.toBoolean()
} catch (_: Exception) {}
}
val feature = SpeciesFeature.get(name)?.invoke() as? FlagSpeciesFeature ?: return null
feature.enabled = enabled
return feature
}
override fun examples() = setOf("true", "false")
}
@@ -1,85 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.feature
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.util.adapters.SpeciesFeatureProviderAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
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
/**
* A registry very similar to [SpeciesFeatures] but the [SpeciesFeatureProvider] contained within it
* will attempt to be applied to all Pokémon, even those that have not elected to have the feature.
*
* @author Hiroku
* @since November 30th, 2022
*/
object GlobalSpeciesFeatures : JsonDataRegistry<SpeciesFeatureProvider<*>> {
override val id = cobblemonResource("global_species_features")
override val type = ResourceType.SERVER_DATA
override val observable = SimpleObservable<SpeciesFeatures>()
private val codeFeatures = mutableMapOf<String, SpeciesFeatureProvider<*>>()
private val resourceFeatures = mutableMapOf<String, SpeciesFeatureProvider<*>>()
override val gson: Gson = GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(SpeciesFeatureProvider::class.java, SpeciesFeatureProviderAdapter)
.create()
override val typeToken: TypeToken<SpeciesFeatureProvider<*>> = TypeToken.get(SpeciesFeatureProvider::class.java)
override val resourcePath: String = "global_species_features"
override fun sync(player: ServerPlayerEntity) {}
override fun reload(data: Map<Identifier, SpeciesFeatureProvider<*>>) {
resourceFeatures.keys.toList().forEach(this::unregister)
data.forEach(this::registerFromAssets)
}
fun getCodeFeature(name: String) = resourceFeatures[name]
fun getResourceFeature(name: String) = codeFeatures[name]
fun getFeature(name: String) = getCodeFeature(name) ?: getResourceFeature(name)
fun getFeatures() = (resourceFeatures.keys + codeFeatures.keys).mapNotNull(this::getFeature)
private fun register(name: String, provider: SpeciesFeatureProvider<*>, isCoded: Boolean) {
val mapping = if (isCoded) codeFeatures else resourceFeatures
if (provider is AspectProvider) {
AspectProvider.register(provider)
}
if (provider is CustomPokemonPropertyType<*>) {
CustomPokemonProperty.register(provider)
}
mapping[name] = provider
}
fun register(name: String, provider: SpeciesFeatureProvider<*>) = register(name, provider, isCoded = true)
private fun registerFromAssets(identifier: Identifier, provider: SpeciesFeatureProvider<*>) = register(identifier.path, provider, isCoded = false)
fun unregister(name: String) {
var coded = true
val value = getResourceFeature(name)?.also { coded = false } ?: getCodeFeature(name) ?: return
if (value is AspectProvider) {
AspectProvider.unregister(value)
}
if (value is CustomPokemonPropertyType<*>) {
CustomPokemonProperty.unregister(value)
}
val mapping = if (coded) codeFeatures else resourceFeatures
mapping.remove(name)
}
}
@@ -16,9 +16,8 @@ import net.minecraft.nbt.NbtCompound
/**
* A piece of state that can be added to some species of Pokémon. Registering an implementing class
* using [SpeciesFeatures.register] adds it as a usable value in the [SpeciesFeatures] list. All Pokémon
* are given the opportunity to be assigned features depending on whether it's a global feature or they
* have opted into it in their species JSON.
* using [SpeciesFeature.register] adds it as a usable value in the [Species.features] list. All Pokémon
* are given a default-constructed instance of all SpeciesFeatures mentioned in its species definition.
*
* The role of this is to allow species-specific data to be attached to individual Pokémon, such as an alolan
* flag or a Vivillon pattern variety. This is powerful when combined with [AspectProvider]s.
@@ -28,6 +27,36 @@ import net.minecraft.nbt.NbtCompound
*/
interface SpeciesFeature {
val name: String
companion object {
private val speciesFeatures = HashBiMap.create<String, () -> SpeciesFeature>()
private val globalSpeciesFeatures = HashBiMap.create<String, () -> SpeciesFeature>()
fun <T : SpeciesFeature> register(name: String, clazz: Class<T>, global: Boolean = false) {
try {
clazz.getDeclaredConstructor().newInstance()
} catch (e: Exception) {
e.printStackTrace()
throw IllegalArgumentException("The given species feature class for $name could not be constructed with a default constructor call. You need a default constructor to use species features.", e)
}
val instanceJob = { clazz.getDeclaredConstructor().newInstance() }
speciesFeatures[name] = instanceJob
if (global) {
globalSpeciesFeatures[name] = instanceJob
}
}
fun <T : SpeciesFeature> register(name: String, instantiator: () -> T) {
speciesFeatures[name] = instantiator
}
fun <T : SpeciesFeature> registerGlobalFeature(name: String, instantiator: () -> T) {
this.register(name, instantiator)
globalSpeciesFeatures[name] = instantiator
}
fun unregister(name: String) {
speciesFeatures.remove(name)
}
fun get(name: String) = speciesFeatures[name]
fun globalFeatures() = this.globalSpeciesFeatures.toMap()
}
fun saveToNBT(pokemonNBT: NbtCompound): NbtCompound
fun loadFromNBT(pokemonNBT: NbtCompound): SpeciesFeature
fun saveToJSON(pokemonJSON: JsonObject): JsonObject
@@ -1,25 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.feature
import com.cobblemon.mod.common.pokemon.Species
/**
* An assignment of some number of [Species] to some number of [SpeciesFeatureProvider]s. In most cases
* this will be a single Pokémon and a single feature, but it can be used to attach many to many.
*
* These assignments are registered in [SpeciesFeatureAssignments].
*
* @author Hiroku
* @since December 1st, 2022
*/
class SpeciesFeatureAssignment {
val pokemon: List<String> = emptyList()
val features: List<String> = emptyList()
}
@@ -1,53 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.feature
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
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
/**
* A registry of assignments combining [SpeciesFeatures] and [PokemonSpecies]. This is a way around the issue
* of when multiple data packs want to add their own [SpeciesFeature]s to the same species. The correct way,
* with this registry, is to add a new JSON that joins together a list of species with a list of species feature
* keys.
*
* @author Hiroku
* @since December 1st, 2022
*/
object SpeciesFeatureAssignments : JsonDataRegistry<SpeciesFeatureAssignment> {
override val id: Identifier = cobblemonResource("species_feature_assignments")
override val type: ResourceType = ResourceType.SERVER_DATA
override val observable = SimpleObservable<SpeciesFeatureAssignments>()
override val gson: Gson = GsonBuilder().setPrettyPrinting().create()
override val typeToken = TypeToken.get(SpeciesFeatureAssignment::class.java)
override val resourcePath = "species_feature_assignments"
private val assignments = mutableMapOf<String, MutableSet<String>>()
override fun sync(player: ServerPlayerEntity) {}
override fun reload(data: Map<Identifier, SpeciesFeatureAssignment>) {
data.values.forEach {
it.pokemon.forEach { pokemon ->
assignments.getOrPut(pokemon) { mutableSetOf() }.addAll(it.features)
}
}
}
fun getFeatures(species: Species) = assignments[species.name] ?: emptySet()
}
@@ -1,30 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.feature
import com.cobblemon.mod.common.pokemon.Pokemon
/**
* A provider of [SpeciesFeature]s. These must be registered via [SpeciesFeatures] either programmatically or
* using the datapack structure. Note that for datapacks to be able to use an implementation of this interface,
* it must be registered in [SpeciesFeatures.types] and then the JSON must include a 'type' field that references
* the key used for that mapping.
*
* @author Hiroku
* @since November 29th, 2022
*/
fun interface SpeciesFeatureProvider<T : SpeciesFeature> {
/**
* Attempts to create a species feature for the given [Pokemon]. It is appropriate to check for an existing feature
* that is compatible with this type and transfer / retain that feature instead of constructing a new one. For example
* a feature for an Ekan's snake pattern should be retained when evolving into Arbok, and species feature invocation
* occurs whenever a species changes.
*/
operator fun invoke(pokemon: Pokemon): T?
}
@@ -1,101 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.feature
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.properties.CustomPokemonPropertyType
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.adapters.SpeciesFeatureProviderAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
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
/**
* A registry for [SpeciesFeatureProvider]s. This is the backbone of species-specific data such as
* variation information and counters used for some evolution types.
*
* Providers are loaded from JSON but can also be added programmatically. Species feature providers
* that are registered via code will replace same-name features that were drawn from assets.
*
* If a species feature provider implements [CustomPokemonPropertyType] then it will be automatically
* registered appropriately, as it will with aspects if it implements [AspectProvider].
*
* @author Hiroku
* @since November 30th, 2022
*/
object SpeciesFeatures : JsonDataRegistry<SpeciesFeatureProvider<*>> {
override val id = cobblemonResource("species_features")
override val type = ResourceType.SERVER_DATA
override val observable = SimpleObservable<SpeciesFeatures>()
val types = mutableMapOf<String, Class<out SpeciesFeatureProvider<*>>>()
private val codeFeatures = mutableMapOf<String, SpeciesFeatureProvider<*>>()
private val resourceFeatures = mutableMapOf<String, SpeciesFeatureProvider<*>>()
override val gson: Gson = GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(SpeciesFeatureProvider::class.java, SpeciesFeatureProviderAdapter)
.create()
override val typeToken: TypeToken<SpeciesFeatureProvider<*>> = TypeToken.get(SpeciesFeatureProvider::class.java)
override val resourcePath: String = "species_features"
override fun sync(player: ServerPlayerEntity) {}
override fun reload(data: Map<Identifier, SpeciesFeatureProvider<*>>) {
resourceFeatures.keys.toList().forEach(this::unregister)
data.forEach(this::registerFromAssets)
}
fun getCodeFeature(name: String) = resourceFeatures[name]
fun getResourceFeature(name: String) = codeFeatures[name]
fun getFeature(name: String) = getCodeFeature(name) ?: getResourceFeature(name)
fun getFeatures() = (resourceFeatures.keys + codeFeatures.keys).mapNotNull(this::getFeature)
fun getFeaturesFor(species: Species): List<SpeciesFeatureProvider<*>> {
val mentionedFeatures = species.features.mapNotNull(this::getFeature)
val globalFeatures = GlobalSpeciesFeatures.getFeatures()
val assignedFeatures = SpeciesFeatureAssignments.getFeatures(species).mapNotNull(this::getFeature)
return (mentionedFeatures + globalFeatures + assignedFeatures).distinct()
}
private fun register(name: String, provider: SpeciesFeatureProvider<*>, isCoded: Boolean) {
val mapping = if (isCoded) codeFeatures else resourceFeatures
if (provider is AspectProvider) {
AspectProvider.register(provider)
}
if (provider is CustomPokemonPropertyType<*>) {
CustomPokemonProperty.register(provider)
}
mapping[name] = provider
}
fun register(name: String, provider: SpeciesFeatureProvider<*>) = register(name, provider, isCoded = true)
private fun registerFromAssets(identifier: Identifier, provider: SpeciesFeatureProvider<*>) = register(identifier.path, provider, isCoded = false)
fun unregister(name: String) {
var coded = true
val value = getResourceFeature(name)?.also { coded = false } ?: getCodeFeature(name) ?: return
if (value is AspectProvider) {
AspectProvider.unregister(value)
}
if (value is CustomPokemonPropertyType<*>) {
CustomPokemonProperty.unregister(value)
}
val mapping = if (coded) codeFeatures else resourceFeatures
mapping.remove(name)
}
}
@@ -1,62 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokemon.feature
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.pokemon.Pokemon
import com.google.gson.JsonObject
import net.minecraft.nbt.NbtCompound
/**
* A species feature value that is a string value.
*
* @author Hiroku
* @since December 30th, 2022
*/
class StringSpeciesFeature(
override val name: String,
var value: String
) : SpeciesFeature, CustomPokemonProperty {
override fun saveToNBT(pokemonNBT: NbtCompound): NbtCompound {
pokemonNBT.putString(name, value)
return pokemonNBT
}
override fun loadFromNBT(pokemonNBT: NbtCompound): SpeciesFeature {
value = pokemonNBT.getString(name)?.takeIf { it.isNotBlank() }?.lowercase() ?: return this
return this
}
override fun saveToJSON(pokemonJSON: JsonObject): JsonObject {
pokemonJSON.addProperty(name, value)
return pokemonJSON
}
override fun loadFromJSON(pokemonJSON: JsonObject): SpeciesFeature {
value = pokemonJSON.get(name)?.asString?.lowercase() ?: return this
return this
}
override fun asString() = "$name=$value"
override fun apply(pokemon: Pokemon) {
val featureProvider = SpeciesFeatures.getFeature(name) ?: return
if (featureProvider in SpeciesFeatures.getFeaturesFor(pokemon.species)) {
val existingFeature = pokemon.getFeature<StringSpeciesFeature>(name)
if (existingFeature != null) {
existingFeature.value = value
} else {
pokemon.features.add(StringSpeciesFeature(name, value))
}
pokemon.updateAspects()
}
}
override fun matches(pokemon: Pokemon) = pokemon.getFeature<StringSpeciesFeature>(name)?.value == value
}
@@ -37,12 +37,6 @@ object CobblemonPokemonLabels {
*/
const val PSEUDO_LEGENDARY = "pseudo_legendary"
/**
* Represents a baby Pokémon, this is not just a first stage Pokémon species, it is also unable to breed.
* For more information see this [Bulbapedia](https://bulbapedia.bulbagarden.net/wiki/Baby_Pok%C3%A9mon) page.
*/
const val BABY = "baby"
/**
* Represents a Pokémon that has multiple forms depending on the region they're from.
* In Cobblemon/Minecraft terms there are no regions, but we follow the official concept.
@@ -99,6 +93,12 @@ object CobblemonPokemonLabels {
*/
const val REGIONAL_OF_PALDEA = "paldea_regional"
/**
* Represents a Pokémon "baby", this is not just a first stage Pokémon species, it is also unable to breed.
* For more information see this [Bulbapedia](https://bulbapedia.bulbagarden.net/wiki/Baby_Pok%C3%A9mon) page.
*/
const val BABY = "baby"
/**
* Represents a mega evolution.
* For more information see the [Bulbapedia](https://bulbapedia.bulbagarden.net/wiki/Mega_Evolution) page.
@@ -158,12 +158,13 @@ object CobblemonPokemonLabels {
const val GENERATION_8 = "gen8"
/**
* Official Pokémon changed by a data pack, there is no guarantee authors will adhere to this principle
* Official Pokémon changed by a datapack, there is no guarantee authors will adhere to this principle
*/
const val CUSTOMIZED_OFFICIAL = "customized_official"
/**
* Unofficial Pokémon created by a data pack, there is no guarantee authors will adhere to this principle
* Unofficial Pokémon created by a datapack, there is no guarantee authors will adhere to this principle
*/
const val CUSTOM = "custom"
}
const val FAKEMON = "fakemon"
}
@@ -48,10 +48,6 @@ interface CustomPokemonProperty {
this.triggerSyncAttempt()
}
fun unregister(property: CustomPokemonPropertyType<*>) {
properties.remove(property)
}
// We do this every time a new property is registered if the server is running in order to synchronize all players with the new property for tab completion purposes
private fun triggerSyncAttempt() {
val server = getServer() ?: return
@@ -15,20 +15,17 @@ import com.cobblemon.mod.common.api.spawning.condition.BasicSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.GroundedSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SubmergedSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SurfaceSpawningCondition
import com.cobblemon.mod.common.api.spawning.context.AreaContextResolver
import com.cobblemon.mod.common.api.spawning.context.GroundedSpawningContext
import com.cobblemon.mod.common.api.spawning.context.LavafloorSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SeafloorSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.context.SubmergedSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SurfaceSpawningContext
import com.cobblemon.mod.common.api.spawning.context.calculators.GroundedSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.LavafloorSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.SeafloorSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.SubmergedSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.SurfaceSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
@@ -36,6 +33,7 @@ import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.preset.BasicSpawnDetailPreset
import com.cobblemon.mod.common.api.spawning.preset.BestSpawnerConfig
import com.cobblemon.mod.common.api.spawning.preset.PokemonSpawnDetailPreset
import com.cobblemon.mod.common.api.spawning.preset.SpawnDetailPreset
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningProspector
import com.cobblemon.mod.common.api.spawning.selection.SpawningSelector
import com.cobblemon.mod.common.api.spawning.spawner.AreaSpawner
@@ -81,6 +79,7 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
*/
object BestSpawner {
var config = BestSpawnerConfig()
var presets = mutableMapOf<String, SpawnDetailPreset>()
val spawnerManagers = mutableListOf<SpawnerManager>(CobblemonWorldSpawnerManager)
var defaultPokemonDespawner: Despawner<PokemonEntity> = CobblemonAgingDespawner(getAgeTicks = { it.ticksLived })
@@ -90,20 +89,17 @@ object BestSpawner {
SpawningCondition.register(AreaSpawningCondition.NAME, AreaSpawningCondition::class.java)
SpawningCondition.register(SubmergedSpawningCondition.NAME, SubmergedSpawningCondition::class.java)
SpawningCondition.register(GroundedSpawningCondition.NAME, GroundedSpawningCondition::class.java)
SpawningCondition.register(SurfaceSpawningCondition.NAME, SurfaceSpawningCondition::class.java)
LOGGER.info("Loaded ${SpawningCondition.conditionTypes.size} spawning condition types.")
SpawningContextCalculator.register(GroundedSpawningContextCalculator)
SpawningContextCalculator.register(SeafloorSpawningContextCalculator)
SpawningContextCalculator.register(LavafloorSpawningContextCalculator)
SpawningContextCalculator.register(SubmergedSpawningContextCalculator)
SpawningContextCalculator.register(SurfaceSpawningContextCalculator)
SpawningContext.register(name = "grounded", clazz = GroundedSpawningContext::class.java, defaultCondition = GroundedSpawningCondition.NAME)
SpawningContext.register(name = "seafloor", clazz = SeafloorSpawningContext::class.java, defaultCondition = GroundedSpawningCondition.NAME)
SpawningContext.register(name = "lavafloor", clazz = LavafloorSpawningContext::class.java, defaultCondition = GroundedSpawningCondition.NAME)
SpawningContext.register(name = "submerged", clazz = SubmergedSpawningContext::class.java, defaultCondition = SubmergedSpawningCondition.NAME)
SpawningContext.register(name = "surface", clazz = SurfaceSpawningContext::class.java, defaultCondition = SurfaceSpawningCondition.NAME)
LOGGER.info("Loaded ${SpawningContext.contexts.size} spawning context types.")
@@ -112,8 +108,10 @@ object BestSpawner {
config = BestSpawnerConfig.load()
SpawnDetailPresets.registerPresetType(BasicSpawnDetailPreset.NAME, BasicSpawnDetailPreset::class.java)
SpawnDetailPresets.registerPresetType(PokemonSpawnDetailPreset.NAME, PokemonSpawnDetailPreset::class.java)
SpawnDetailPreset.registerPresetType(BasicSpawnDetailPreset.NAME, BasicSpawnDetailPreset::class.java)
SpawnDetailPreset.registerPresetType(PokemonSpawnDetailPreset.NAME, PokemonSpawnDetailPreset::class.java)
presets = SpawnDetailPreset.load()
LOGGER.info("Loaded ${presets.size} spawn detail presets.")
}
fun onServerStarted() {
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.api.spawning.condition.BucketPrecalculation
import com.cobblemon.mod.common.api.spawning.condition.ContextPrecalculation
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.data.CobblemonDataProvider
/**
* A collection of all of Cobblemon's general-purpose [SpawnPool]s. These
@@ -26,6 +25,6 @@ object CobblemonSpawnPools {
lateinit var WORLD_SPAWN_POOL: SpawnPool
fun load() {
WORLD_SPAWN_POOL = CobblemonDataProvider.register(SpawnPool("world").addPrecalculators(ContextPrecalculation, BucketPrecalculation))
WORLD_SPAWN_POOL = SpawnLoader.load("world").addPrecalculators(ContextPrecalculation, BucketPrecalculation)
}
}
@@ -1,25 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning
/**
* A simple utility for combining multiple integer ranges.
*
* @author Hiroku
* @since December 16th, 2022
*/
open class IntRanges() {
constructor(vararg ranges: IntRange) : this() {
this.ranges = ranges.toMutableList()
}
var ranges = mutableListOf<IntRange>()
operator fun contains(value: Int) = ranges.any { value in it }
}
@@ -1,42 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.api.spawning.MoonPhaseRange.Companion.moonPhaseRanges
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
/**
* A range of moon phases for a world mainly to be used in [SpawningCondition]s. A moon phase range
* is defined as any number of phase integer ranges (0-7).
*
* There are a series of in-built moon phase ranges that you can replace if they don't suit
* your definitions.
*
* If you want a moon phase range to be referenced by name in [SpawningCondition]s, you need
* to register it, by name, in [moonPhaseRanges].
*
* @author Hiroku
* @since December 16th, 2022
*/
class MoonPhaseRange : IntRanges {
companion object {
val moonPhaseRanges = mutableMapOf(
"crescent" to MoonPhaseRange(3..3, 5..5),
"gibbous" to MoonPhaseRange(1..1, 7..7),
"full" to MoonPhaseRange(0..0),
"new" to MoonPhaseRange(4..4),
"quarter" to MoonPhaseRange(2..2, 6..6),
"waxing" to MoonPhaseRange(5..5, 7..7),
"waning" to MoonPhaseRange(1..1, 3..3)
)
}
constructor() : super()
constructor(vararg ranges: IntRange) : super(*ranges)
}
@@ -1,85 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning
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.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.context.RegisteredSpawningContext
import com.cobblemon.mod.common.api.spawning.preset.SpawnDetailPreset
import com.cobblemon.mod.common.util.adapters.BiomeLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.BlockLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.FluidLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.IntRangesAdapter
import com.cobblemon.mod.common.util.adapters.RegisteredSpawningContextAdapter
import com.cobblemon.mod.common.util.adapters.SpawnBucketAdapter
import com.cobblemon.mod.common.util.adapters.SpawnDetailPresetAdapter
import com.cobblemon.mod.common.util.adapters.SpawningConditionAdapter
import com.cobblemon.mod.common.util.adapters.pokemonPropertiesShortAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.block.Block
import net.minecraft.fluid.Fluid
import net.minecraft.resource.ResourceType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.minecraft.world.biome.Biome
/**
* Data registry for [SpawnDetailPreset]s. These help the maintainability of spawn files by allowing common presets
* to be defined separately to the spawns that obey it. You can register custom presets either programmatically or
* by adding preset JSONs to the spawn_detail_presets data folder.
*
* @author Hiroku
* @since December 9th, 2022
*/
object SpawnDetailPresets : JsonDataRegistry<SpawnDetailPreset> {
val GSON = GsonBuilder()
.setPrettyPrinting()
.setLenient()
.disableHtmlEscaping()
.registerTypeAdapter(SpawnBucket::class.java, SpawnBucketAdapter)
.registerTypeAdapter(RegisteredSpawningContext::class.java, RegisteredSpawningContextAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
.registerTypeAdapter(SpawnDetailPreset::class.java, SpawnDetailPresetAdapter)
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
.registerTypeAdapter(SpawningCondition::class.java, SpawningConditionAdapter)
.registerTypeAdapter(TimeRange::class.java, IntRangesAdapter(TimeRange.timeRanges) { TimeRange(*it) })
.registerTypeAdapter(MoonPhaseRange::class.java, IntRangesAdapter(MoonPhaseRange.moonPhaseRanges) { MoonPhaseRange(*it) })
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.create()
val presetTypes = mutableMapOf<String, Class<out SpawnDetailPreset>>()
fun <T : SpawnDetailPreset> registerPresetType(name: String, detailClass: Class<T>) {
presetTypes[name] = detailClass
}
override val gson: Gson = GSON
override val typeToken = TypeToken.get(SpawnDetailPreset::class.java)
override val resourcePath = "spawn_detail_presets"
override val id = cobblemonResource(resourcePath)
override val type = ResourceType.SERVER_DATA
override val observable = SimpleObservable<SpawnDetailPresets>()
var presets = mutableMapOf<Identifier, SpawnDetailPreset>()
override fun sync(player: ServerPlayerEntity) {}
override fun reload(data: Map<Identifier, SpawnDetailPreset>) {
this.presets = data.toMutableMap()
Cobblemon.LOGGER.info("Loaded ${presets.size} spawn detail presets.")
}
}
@@ -8,30 +8,46 @@
package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.drop.DropEntry
import com.cobblemon.mod.common.api.drop.ItemDropMethod
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.TimeRange
import com.cobblemon.mod.common.api.spawning.context.RegisteredSpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.util.AssetLoading
import com.cobblemon.mod.common.util.AssetLoading.toPath
import com.cobblemon.mod.common.util.adapters.BiomeLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.BlockLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.DropEntryAdapter
import com.cobblemon.mod.common.util.adapters.FluidLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.IntRangeAdapter
import com.cobblemon.mod.common.util.adapters.IntRangesAdapter
import com.cobblemon.mod.common.util.adapters.NbtCompoundAdapter
import com.cobblemon.mod.common.util.adapters.RegisteredSpawningContextAdapter
import com.cobblemon.mod.common.util.adapters.SpawnBucketAdapter
import com.cobblemon.mod.common.util.adapters.SpawnDetailAdapter
import com.cobblemon.mod.common.util.adapters.SpawningConditionAdapter
import com.cobblemon.mod.common.util.adapters.TimeRangeAdapter
import com.cobblemon.mod.common.util.adapters.pokemonPropertiesShortAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.fromJson
import com.cobblemon.mod.common.util.isHigherVersion
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.reflect.TypeToken
import java.io.File
import java.io.FileOutputStream
import java.io.FileReader
import java.io.InputStream
import java.io.InputStreamReader
import java.nio.file.Path
import kotlin.io.path.pathString
import net.minecraft.block.Block
import net.minecraft.fluid.Fluid
import net.minecraft.nbt.NbtCompound
import net.minecraft.util.Identifier
import net.minecraft.world.biome.Biome
@@ -50,14 +66,12 @@ object SpawnLoader {
.setLenient()
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
.registerTypeAdapter(RegisteredSpawningContext::class.java, RegisteredSpawningContextAdapter)
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
.registerTypeAdapter(SpawnDetail::class.java, SpawnDetailAdapter)
.registerTypeAdapter(DropEntry::class.java, DropEntryAdapter)
.registerTypeAdapter(SpawningCondition::class.java, SpawningConditionAdapter)
.registerTypeAdapter(TimeRange::class.java, IntRangesAdapter(TimeRange.timeRanges) { TimeRange(*it) })
.registerTypeAdapter(MoonPhaseRange::class.java, IntRangesAdapter(MoonPhaseRange.moonPhaseRanges) { MoonPhaseRange(*it) })
.registerTypeAdapter(TimeRange::class.java, TimeRangeAdapter)
.registerTypeAdapter(ItemDropMethod::class.java, ItemDropMethod.adapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(SpawnBucket::class.java, SpawnBucketAdapter)
@@ -66,4 +80,146 @@ object SpawnLoader {
.create()
var deserializingConditionClass: Class<out SpawningCondition<*>>? = null
fun load(folder: String): SpawnPool {
val pool = SpawnPool()
val external = getExternalSets(folder)
val internal = loadInternalFolder(folder)
external.forEach { set ->
val matchingInternal = internal.find { it.id == set.id }
if (set.preventOverwrite) {
if (matchingInternal != null) {
internal.remove(matchingInternal)
}
if (set.isEnabled()) {
pool.details.addAll(set.filter { it.isModDependencySatisfied() })
}
} else {
if (matchingInternal != null && matchingInternal.version.isHigherVersion(set.version)) {
if (matchingInternal.isEnabled()) {
pool.details.addAll(matchingInternal.filter { it.isModDependencySatisfied() })
}
internal.remove(matchingInternal)
exportSet(folder, matchingInternal)
}
}
}
internal.forEach { set ->
if (set.isEnabled()) {
pool.details.addAll(set.filter { it.isModDependencySatisfied() })
}
if (config.exportSpawnsToConfig) {
exportSet(folder, set)
}
}
pool.precalculate()
return pool
}
fun exportSet(folder: String, set: SpawnSet) {
val file = File("./config/cobblemon/spawning/spawns/$folder/${set.id.lowercase()}.set.json")
file.parentFile.mkdirs()
file.createNewFile()
val internalStream = Cobblemon::class.java.getResourceAsStream(set.path.toAbsolutePath().toString())!!
val bytes = internalStream.readAllBytes()
internalStream.close()
val externalStream = FileOutputStream(file)
externalStream.write(bytes)
externalStream.close()
}
fun getExternalSets(folder: String): MutableList<SpawnSet> {
val sets = mutableListOf<SpawnSet>()
val files = mutableListOf<File>()
AssetLoading.searchFor(
dir = "config/cobblemon/spawning/spawns/$folder",
suffix = ".json",
list = files
)
val paths = files.map { it.toPath() }
sets.addAll(
paths.mapNotNull {
try {
val file = it.toFile()
val reader = FileReader(file)
val set = gson.fromJson<SpawnSet>(reader)
reader.close()
return@mapNotNull set
} catch (exception : Exception) {
LOGGER.error("Unable to load spawn set from ${it.pathString}")
exception.printStackTrace()
null
}
}
)
return sets
}
fun loadSetFromPath(path: Path): SpawnSet {
val pathString = if (path.toString().startsWith("/")) path.toString() else "/$path"
val inputStream = Cobblemon.javaClass.getResourceAsStream(pathString)
?: throw IllegalArgumentException("Unable to open resource stream for: $pathString")
val reader = InputStreamReader(inputStream)
val set = gson.fromJson<SpawnSet>(reader)
reader.close()
return set
}
fun loadInternalFolder(folder: String): MutableList<SpawnSet> {
val internalFolder = cobblemonResource("spawning/spawns/$folder")
val path = internalFolder.toPath() ?: throw IllegalArgumentException("There is no valid, internal path for $internalFolder")
val internalPaths = AssetLoading.fileSearch(path, { it.toString().endsWith(".json") }, recursive = true)
val sets = mutableListOf<SpawnSet>()
sets.addAll(internalPaths.flatMap { filePath -> loadInternalFile(filePath) })
return sets
}
fun loadInternalFile(path: Path): MutableList<SpawnSet> {
if (path.toString().endsWith(".packed.json")) {
return loadPackedSets(path).onEach { it.path = path }
} else if (path.toString().endsWith(".json")) {
return mutableListOf(loadSetFromPath(path)).onEach { it.path = path }
}
return mutableListOf()
}
fun loadPackedSets(path: Path): MutableList<SpawnSet> {
val fileStream = Cobblemon::class.java.getResourceAsStream(path.toAbsolutePath().toString())
?: throw IllegalArgumentException("Unable to create input stream for $path")
return try {
loadPackedSetsFromStream(fileStream)
} catch (e: Exception) {
LOGGER.error(e.message)
return mutableListOf()
} finally {
fileStream.close()
}
}
fun loadPackedSetsFromStream(inputStream: InputStream): MutableList<SpawnSet> {
val sets = mutableListOf<SpawnSet>()
val stream = InputStreamReader(inputStream)
val jsonArray = gson.fromJson(stream, JsonArray::class.java)
stream.close()
jsonArray.forEach { jsonElement ->
try {
sets.add(gson.fromJson<SpawnSet>(jsonElement))
} catch (exception: Exception) {
exception.printStackTrace()
}
}
return sets
}
}
@@ -20,11 +20,14 @@ import java.nio.file.Path
* @since January 27th, 2022
*/
class SpawnSet : Iterable<SpawnDetail>, ModDependant {
var id = ""
var enabled = true
var version = "1"
var preventOverwrite = false
override var neededInstalledMods = listOf<String>()
override var neededUninstalledMods = listOf<String>()
var spawns = mutableListOf<SpawnDetail>()
@Transient
lateinit var path: Path
fun isEnabled(): Boolean = enabled && isModDependencySatisfied()
@@ -9,8 +9,6 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.MoonPhaseRange
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.spawning.condition.ListCheckMode.ALL
import com.cobblemon.mod.common.api.spawning.condition.ListCheckMode.ANY
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
@@ -39,7 +37,7 @@ abstract class SpawningCondition<T : SpawningContext> {
var dimensions: MutableList<Identifier>? = null
var biomes: MutableSet<RegistryLikeCondition<Biome>>? = null
var moonPhase: MoonPhaseRange? = null
var moonPhase: Int? = null
var canSeeSky: Boolean? = null
var minX: Float? = null
var minY: Float? = null
@@ -78,7 +76,7 @@ abstract class SpawningCondition<T : SpawningContext> {
return false
} else if (dimensions != null && dimensions!!.isNotEmpty() && ctx.world.dimension.effects !in dimensions!!) {
return false
} else if (moonPhase != null && ctx.moonPhase in moonPhase!!) {
} else if (moonPhase != null && moonPhase != ctx.moonPhase) {
return false
} else if (biomes != null && biomes!!.isNotEmpty() && biomes!!.none { condition -> condition.fits(ctx.biome, ctx.biomeRegistry) }) {
return false
@@ -8,11 +8,10 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.context.SubmergedSpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.util.Merger
import net.minecraft.fluid.Fluid
import net.minecraft.util.Identifier
/**
* Base type for a spawning condition that applies to some kind of [SubmergedSpawningContext]. This
@@ -25,7 +24,7 @@ abstract class SubmergedTypeSpawningCondition<T : SubmergedSpawningContext> : Ar
var minDepth: Int? = null
var maxDepth: Int? = null
var fluidIsSource: Boolean? = null
var fluid: RegistryLikeCondition<Fluid>? = null
var fluidBlock: Identifier? = null
override fun fits(ctx: T, detail: SpawnDetail): Boolean {
return if (!super.fits(ctx, detail)) {
@@ -34,9 +33,9 @@ abstract class SubmergedTypeSpawningCondition<T : SubmergedSpawningContext> : Ar
false
} else if (maxDepth != null && ctx.depth > maxDepth!!) {
false
} else if (fluidIsSource != null && ctx.fluid.isStill != fluidIsSource!!) {
} else if (fluidIsSource != null && ctx.fluidState.isStill != fluidIsSource!!) {
false
} else !(ctx.fluid.isEmpty || (fluid != null && !fluid!!.fits(ctx.fluid.fluid, ctx.fluidRegistry)))
} else !(fluidBlock != null && ctx.blockRegistry.getKey(ctx.fluidBlock).get().value != fluidBlock!!)
}
override fun copyFrom(other: SpawningCondition<*>, merger: Merger) {
@@ -45,7 +44,7 @@ abstract class SubmergedTypeSpawningCondition<T : SubmergedSpawningContext> : Ar
minDepth = merger.mergeSingle(minDepth, other.minDepth)
maxDepth = merger.mergeSingle(minDepth, other.minDepth)
fluidIsSource = merger.mergeSingle(fluidIsSource, other.fluidIsSource)
fluid = merger.mergeSingle(fluid, other.fluid)
fluidBlock = merger.mergeSingle(fluidBlock, other.fluidBlock)
}
}
}
@@ -1,60 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.context.SurfaceSpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.util.Merger
import net.minecraft.fluid.Fluid
/**
* Base type for a spawning condition that applies to some kind of [SurfaceSpawningContext]. This
* can be extended for subclasses of [SurfaceSpawningContext].
*
* @author Hiroku
* @since December 15th, 2022
*/
abstract class SurfaceTypeSpawningCondition<T : SurfaceSpawningContext> : AreaTypeSpawningCondition<T>() {
var minDepth: Int? = null
var maxDepth: Int? = null
var fluid: RegistryLikeCondition<Fluid>? = null
override fun fits(ctx: T, detail: SpawnDetail): Boolean {
return if (!super.fits(ctx, detail)) {
false
} else if (minDepth != null && ctx.depth < minDepth!!) {
false
} else if (maxDepth != null && ctx.depth > maxDepth!!) {
false
} else !(ctx.baseBlock.fluidState.isEmpty || (fluid != null && !fluid!!.fits(ctx.baseBlock.fluidState.fluid, ctx.fluidRegistry)))
}
override fun copyFrom(other: SpawningCondition<*>, merger: Merger) {
super.copyFrom(other, merger)
if (other is SurfaceTypeSpawningCondition) {
minDepth = merger.mergeSingle(minDepth, other.minDepth)
maxDepth = merger.mergeSingle(minDepth, other.minDepth)
fluid = merger.mergeSingle(fluid, other.fluid)
}
}
}
/**
* A spawning condition for a [SurfaceSpawningContext].
*
* @author Hiroku
* @since December 15th, 2022
*/
open class SurfaceSpawningCondition : SurfaceTypeSpawningCondition<SurfaceSpawningContext>() {
override fun contextClass() = SurfaceSpawningContext::class.java
companion object {
const val NAME = "surface"
}
}
@@ -6,12 +6,10 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
package com.cobblemon.mod.common.api.spawning.condition
/**
* A range of time ticks for a world mainly to be used in [SpawningCondition]s. A time range
* A range of time ticks for a world to be used in [SpawningCondition]s. A time range
* is defined as any number of tick ranges. For example, you can define a time range
* as being between 0 and 12000 ticks or 13000 and 14000 ticks just for a single time
* range. This is to deal with the limitation that Minecraft days go from 0 ticks to
@@ -21,14 +19,14 @@ import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
* your definitions.
*
* If you want a time range to be referenced by name in [SpawningCondition]s, you need
* to register it, by name, in [timeRanges].
* to register it, by name, in [ranges].
*
* @author Hiroku
* @since January 26th, 2022
*/
class TimeRange : IntRanges {
class TimeRange() {
companion object {
val timeRanges = mutableMapOf(
val ranges = mutableMapOf(
"any" to TimeRange(0..23999),
"day" to TimeRange(23460..23999, 0..12541),
"night" to TimeRange(12542..23459),
@@ -42,6 +40,11 @@ class TimeRange : IntRanges {
)
}
constructor() : super()
constructor(vararg ranges: IntRange) : super(*ranges)
}
constructor(vararg ranges: IntRange) : this() {
this.ranges = ranges.toMutableList()
}
var ranges = mutableListOf<IntRange>()
operator fun contains(timeTicks: Int) = ranges.any { timeTicks in it }
}
@@ -92,18 +92,4 @@ open class LavafloorSpawningContext(
height: Int,
nearbyBlocks: List<BlockState>,
slice: WorldSlice
) : FlooredSpawningContext(cause, world, position, light, canSeeSky, influences, width, height, nearbyBlocks, slice)
open class SurfaceSpawningContext(
cause: SpawnCause,
world: World,
position: BlockPos,
light: Int,
canSeeSky: Boolean,
influences: MutableList<SpawningInfluence>,
width: Int,
height: Int,
val depth: Int,
nearbyBlocks: List<BlockState>,
slice: WorldSlice
) : FlooredSpawningContext(cause, world, position, light, canSeeSky, influences, width, height, nearbyBlocks, slice)
@@ -15,7 +15,6 @@ import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import net.minecraft.block.Block
import net.minecraft.entity.Entity
import net.minecraft.fluid.Fluid
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.registry.Registry
@@ -66,7 +65,6 @@ abstract class SpawningContext {
val biomeRegistry: Registry<Biome> by lazy { world.registryManager.get(Registry.BIOME_KEY) }
val blockRegistry: Registry<Block> by lazy { world.registryManager.get(Registry.BLOCK_KEY) }
val fluidRegistry: Registry<Fluid> by lazy { world.registryManager.get(Registry.FLUID_KEY)}
val biomeName: Identifier
get() = this.biomeRegistry.getId(biome)!!
@@ -34,5 +34,6 @@ open class SubmergedSpawningContext(
nearbyBlocks: List<BlockState>,
slice: WorldSlice
) : AreaSpawningContext(cause, world, position, light, canSeeSky, influences, width, height, nearbyBlocks, slice) {
val fluid = slice.getBlockState(position.x, position.y, position.z).fluidState
val fluidBlock = slice.getBlockState(position.x, position.y, position.z).block
val fluidState = slice.getBlockState(position.x, position.y, position.z).fluidState
}
@@ -13,7 +13,6 @@ import com.cobblemon.mod.common.api.spawning.context.FlooredSpawningContext
import com.cobblemon.mod.common.api.spawning.context.GroundedSpawningContext
import com.cobblemon.mod.common.api.spawning.context.LavafloorSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SeafloorSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SurfaceSpawningContext
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator.Companion.isAirCondition
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator.Companion.isLavaCondition
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator.Companion.isSolidCondition
@@ -121,34 +120,4 @@ object LavafloorSpawningContextCalculator : FlooredSpawningContextCalculator<Lav
nearbyBlocks = getNearbyBlocks(input)
)
}
}
/**
* The context calculator used for [SurfaceSpawningContext]s. Requires a fluid block below it and
* air blocks in its surroundings.
*
* @author Hiroku
* @since December 15th, 2022
*/
object SurfaceSpawningContextCalculator : FlooredSpawningContextCalculator<SurfaceSpawningContext> {
override val baseCondition: (BlockState) -> Boolean = { !it.fluidState.isEmpty }
override val surroundingCondition: (BlockState) -> Boolean = isAirCondition
override fun calculate(input: AreaSpawningInput): SurfaceSpawningContext {
return SurfaceSpawningContext(
cause = input.cause,
world = input.world,
position = input.position.toImmutable(),
light = getLight(input),
canSeeSky = getCanSeeSky(input),
influences = input.spawner.copyInfluences(),
width = getHorizontalSpace(input, surroundingCondition, config.maxHorizontalSpace, offsetY = 1),
height = getHeight(input, surroundingCondition, config.maxVerticalSpace / 2, offsetY = 1),
depth = getDepth(input, baseCondition, config.maxVerticalSpace / 2),
slice = input.slice,
nearbyBlocks = getNearbyBlocks(input)
)
}
}
}
@@ -12,7 +12,8 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.feature.SeasonFeatureHandler
import com.cobblemon.mod.common.pokemon.feature.SEASON
import com.cobblemon.mod.common.pokemon.feature.SeasonFeature
import kotlin.random.Random
/**
@@ -36,7 +37,7 @@ class PokemonSpawnAction(
props.shiny = Cobblemon.config.shinyRate.takeIf { it >= 1 }?.let { Random.Default.nextFloat() < 1 / it }
}
val entity = props.createEntity(ctx.world)
SeasonFeatureHandler.updateSeason(entity.pokemon, ctx.world, ctx.position)
entity.pokemon.getFeature<SeasonFeature>(SEASON)?.update(entity.pokemon, ctx.world, ctx.position)
entity.drops = detail.drops
return entity
}
@@ -8,23 +8,11 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.SpawnLoader
import com.cobblemon.mod.common.api.spawning.SpawnSet
import com.cobblemon.mod.common.api.spawning.condition.FinalPrecalculationResult
import com.cobblemon.mod.common.api.spawning.condition.PrecalculationResult
import com.cobblemon.mod.common.api.spawning.condition.RootPrecalculation
import com.cobblemon.mod.common.api.spawning.condition.SpawningPrecalculation
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import net.minecraft.resource.ResourceType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
/**
* A collection of [SpawnDetail]s with precalculation logic for optimization of searches.
@@ -37,22 +25,7 @@ import net.minecraft.util.Identifier
* @author Hiroku
* @since February 9th, 2022
*/
class SpawnPool(val name: String) : JsonDataRegistry<SpawnSet>, Iterable<SpawnDetail> {
override val id = cobblemonResource("spawn_pool_$name")
override val type = ResourceType.SERVER_DATA
override val observable = SimpleObservable<SpawnPool>()
override val gson: Gson = SpawnLoader.gson
override val typeToken = TypeToken.get(SpawnSet::class.java)
override val resourcePath = id.path
override fun sync(player: ServerPlayerEntity) {}
override fun reload(data: Map<Identifier, SpawnSet>) {
details.clear()
for (set in data.values) {
details.addAll(set.filter { it.isModDependencySatisfied() })
}
precalculate()
}
class SpawnPool : Iterable<SpawnDetail> {
val details = mutableListOf<SpawnDetail>()
var precalculation: PrecalculationResult<*> = RootPrecalculation.generate(details, emptyList())
val precalculators = mutableListOf<SpawningPrecalculation<*>>()
@@ -102,8 +75,8 @@ class SpawnPool(val name: String) : JsonDataRegistry<SpawnSet>, Iterable<SpawnDe
* Creates a de-referenced copy of the pool which can be modified safely without
* this pool being changed.
*/
fun copy(newName: String): SpawnPool {
val copy = SpawnPool(newName)
fun copy(): SpawnPool {
val copy = SpawnPool()
copy.details.addAll(details)
copy.precalculators.addAll(precalculators)
copy.precalculation = precalculation
@@ -33,8 +33,7 @@ class BestSpawnerConfig {
val replaceWithNewVersion = true
val contextWeights = mutableMapOf(
"grounded" to 1F,
"submerged" to 0.95F,
"surface" to 0.05F
"submerged" to 0.7F
)
val buckets = mutableListOf(
SpawnBucket("common", 94.4F),
@@ -55,7 +54,7 @@ class BestSpawnerConfig {
fun load(): BestSpawnerConfig {
val internal = loadInternal()
if (Cobblemon.config.exportSpawnConfig) {
if (Cobblemon.config.exportSpawnConfigToConfig) {
val external = loadExternal()
return if (external == null) {
saveExternal()
@@ -8,16 +8,39 @@
package com.cobblemon.mod.common.api.spawning.preset
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.asset.JsonManifestWalker
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnDetailPresets
import com.cobblemon.mod.common.api.spawning.SpawnLoader
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.TimeRange
import com.cobblemon.mod.common.api.spawning.context.RegisteredSpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.multiplier.WeightMultiplier
import com.cobblemon.mod.common.util.AssetLoading
import com.cobblemon.mod.common.util.MergeMode
import com.cobblemon.mod.common.util.adapters.BiomeLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.BlockLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.RegisteredSpawningContextAdapter
import com.cobblemon.mod.common.util.adapters.SpawnBucketAdapter
import com.cobblemon.mod.common.util.adapters.SpawnDetailAdapter
import com.cobblemon.mod.common.util.adapters.SpawnDetailPresetAdapter
import com.cobblemon.mod.common.util.adapters.SpawningConditionAdapter
import com.cobblemon.mod.common.util.adapters.TimeRangeAdapter
import com.cobblemon.mod.common.util.adapters.pokemonPropertiesShortAdapter
import com.cobblemon.mod.common.util.fromJson
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import com.google.gson.reflect.TypeToken
import java.io.File
import java.io.FileReader
import net.minecraft.block.Block
import net.minecraft.util.Identifier
import net.minecraft.world.biome.Biome
/**
* Base class for spawn detail presets. Presets are a spawn loading mechanism that allows various properties to be
@@ -25,7 +48,7 @@ import com.google.gson.JsonObject
* to shortcut the process of commonly used conditions and other [SpawnDetail] properties as well as make those
* commonly used properties very easy to maintain.
*
* A subclass of this base must be registered using [SpawnDetailPresets.registerPresetType].
* A subclass of this base must be registered using [SpawnDetailPreset.registerPresetType].
*
* Preset loading occurs during initialization and first will load the internal presets. Then the external
* config/cobblemon/spawning/presets directory and its child directories will be searched for presets.
@@ -40,6 +63,75 @@ import com.google.gson.JsonObject
* @since July 8th, 2022
*/
abstract class SpawnDetailPreset {
companion object {
val GSON = GsonBuilder()
.setPrettyPrinting()
.setLenient()
.disableHtmlEscaping()
.registerTypeAdapter(SpawnBucket::class.java, SpawnBucketAdapter)
.registerTypeAdapter(RegisteredSpawningContext::class.java, RegisteredSpawningContextAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(SpawnDetailPreset::class.java, SpawnDetailPresetAdapter)
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
.registerTypeAdapter(SpawningCondition::class.java, SpawningConditionAdapter)
.registerTypeAdapter(TimeRange::class.java, TimeRangeAdapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.create()
val presetTypes = mutableMapOf<String, Class<out SpawnDetailPreset>>()
fun <T : SpawnDetailPreset> registerPresetType(name: String, detailClass: Class<T>) {
presetTypes[name] = detailClass
}
fun load(): MutableMap<String, SpawnDetailPreset> {
val map = mutableMapOf<String, SpawnDetailPreset>()
map.putAll(loadInternal())
map.putAll(loadExternal())
return map
}
fun loadInternal(): MutableMap<String, SpawnDetailPreset> {
val map = mutableMapOf<String, SpawnDetailPreset>()
try {
val presets = JsonManifestWalker.load(
SpawnDetailPreset::class.java,
"spawning/presets",
GSON
)
for (template in presets) {
map[template.name] = template
}
return map
} catch (e: Exception) {
LOGGER.error("Error loading internal spawn detail presets", e)
}
return mutableMapOf()
}
fun loadExternal(): MutableMap<String, SpawnDetailPreset> {
val files = mutableListOf<File>()
val map = mutableMapOf<String, SpawnDetailPreset>()
AssetLoading.searchFor(
dir = "config/${Cobblemon.MODID}/spawning/presets",
suffix = ".json",
list = files
)
files.forEach {
try {
val reader = FileReader(it)
val preset = GSON.fromJson<SpawnDetailPreset>(reader)
reader.close()
map[preset.name] = preset
} catch (e: Exception) {
LOGGER.error("Unable to load preset from file: ${it.name}", e)
}
}
return map
}
}
var name = ""
var bucket: SpawnBucket? = null
var spawnDetailType: String? = null
var context: RegisteredSpawningContext<*>? = null
@@ -13,15 +13,15 @@ import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.SpawnerManager
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.nextBetween
import java.util.UUID
import kotlin.math.atan
import kotlin.math.cos
import kotlin.math.sin
import kotlin.random.Random
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.math.MathHelper.PI
import net.minecraft.util.math.MathHelper.ceil
import net.minecraft.util.math.MathHelper.nextBetween
import net.minecraft.util.math.random.Random
/**
* A spawner that works around a single player. It will do basic tracking of a player's speed
@@ -39,12 +39,12 @@ class PlayerSpawner(player: ServerPlayerEntity, spawns: SpawnPool, manager: Spaw
val sliceDiameter = config.worldSliceDiameter
val sliceHeight = config.worldSliceHeight
val rand = Random.Default
val rand = Random.create()
val center = player.pos
val r = rand.nextBetween(config.minimumSliceDistanceFromPlayer, config.maximumSliceDistanceFromPlayer)
val thetatemp = atan(player.velocity.z / player.velocity.x) + rand.nextBetween(-PI/2, PI/2 )
val r = nextBetween(rand, config.minimumSliceDistanceFromPlayer, config.maximumSliceDistanceFromPlayer)
val thetatemp = atan(player.velocity.z / player.velocity.x) + nextBetween(rand, -PI/2, PI/2 )
val theta = if (player.velocity.horizontalLength() < 0.1) {
rand.nextDouble() * 2 * PI
} else if (player.velocity.x < 0) {
@@ -46,6 +46,7 @@ abstract class TickingSpawner(
var active = true
val spawnedEntities = mutableListOf<Entity>()
var maximumSpawned = Cobblemon.config.maximumSpawnedPokemonPerPlayer
var lastSpawnTime = 0L
var ticksUntilNextSpawn = 100F
@@ -65,7 +66,7 @@ abstract class TickingSpawner(
removalCheckTicks = 0
}
if (!active) {
if (!active || spawnedEntities.size >= maximumSpawned) {
return
}
@@ -44,15 +44,11 @@ open class JSONStoreAdapter(
}
override fun <E, T : PokemonStore<E>> load(file: File, storeClass: Class<out T>, uuid: UUID): T? {
return try {
val br = BufferedReader(FileReader(file))
val json = gson.fromJson<JsonObject>(br)
br.close()
val store = storeClass.getConstructor(UUID::class.java).newInstance(uuid)
store.loadFromJSON(json)
store
} catch (e: Exception) {
null
}
val br = BufferedReader(FileReader(file))
val json = gson.fromJson<JsonObject>(br)
br.close()
val store = storeClass.getConstructor(UUID::class.java).newInstance(uuid)
store.loadFromJSON(json)
return store
}
}
@@ -38,9 +38,9 @@ open class NBTStoreAdapter(
val nbt = NbtIo.readCompressed(file)
store.loadFromNBT(nbt)
store
} catch (e: Exception) {
e.printStackTrace()
return null
} catch (eof: EOFException) {
LOGGER.error("Pokémon save file for ${storeClass.simpleName} ($uuid) was corrupted. A fresh file will be created.")
store
}
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.storage.adapter.flatifle
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.storage.PokemonStore
import com.cobblemon.mod.common.api.storage.StorePosition
import com.cobblemon.mod.common.api.storage.adapter.CobblemonAdapterParent
@@ -68,7 +67,6 @@ abstract class OneToOneFileStoreAdapter<S>(
try {
val tempLoaded = load(tempFile, storeClass, uuid)
if (tempLoaded != null) {
save(file, serialize(tempLoaded))
return tempLoaded
}
} finally {
@@ -78,10 +76,6 @@ abstract class OneToOneFileStoreAdapter<S>(
return if (file.exists()) {
load(file, storeClass, uuid)
?: let {
LOGGER.error("Pokémon save file for ${storeClass.simpleName} ($uuid) was corrupted. A fresh file will be created.")
storeClass.getConstructor(UUID::class.java).newInstance(uuid)
}
} else {
null
}
@@ -20,7 +20,7 @@ import net.minecraft.network.PacketByteBuf
* @since March 9th, 2022
*/
data class BattleFormat(
val mod: String = "cobblemon",
val mod: String = "Cobblemon",
val battleType: BattleType = BattleTypes.SINGLES,
val ruleSet: Set<String> = setOf()
) {
@@ -8,12 +8,15 @@
package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.Cobblemon.showdown
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.battles.runner.GraalShowdown
import com.cobblemon.mod.common.util.DataKeys
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import java.time.Instant
import java.util.Optional
import java.util.UUID
@@ -110,6 +113,10 @@ object BattleRegistry {
return team.joinToString("]")
}
/**
* Temporary starting method for a battle.
* TODO: Replace with a builder for battle definition and then a starting method that takes the built result?
*/
fun startBattle(
battleFormat: BattleFormat,
side1: BattleSide,
@@ -119,8 +126,8 @@ object BattleRegistry {
battleMap[battle.battleId] = battle
// Build request message
val messages = mutableListOf<String>()
messages.add(">start { \"format\": ${battleFormat.toFormatJSON()} }")
val jsonArray = JsonArray()
jsonArray.add(">start { \"format\": ${battleFormat.toFormatJSON()} }")
/*
* Showdown IDs are like p1, p2, p3, etc. Showdown uses these keys to identify who is doing what to whom.
@@ -160,16 +167,21 @@ object BattleRegistry {
// -> Add the players and team
for (actor in battle.actors.sortedBy { it.showdownId }) {
messages.add(""">player ${actor.showdownId} {"name":"${actor.uuid}","team":"${actor.pokemonList.packTeam()}"}""")
jsonArray.add(""">player ${actor.showdownId} {"name":"${actor.uuid}","team":"${actor.pokemonList.packTeam()}"}""")
}
// -> Set team size
for (actor in battle.actors.sortedBy { it.showdownId }) {
messages.add(">${actor.showdownId} team ${actor.pokemonList.count()}")
jsonArray.add(">${actor.showdownId} team ${actor.pokemonList.count()}")
}
// Compiles the request and sends it off
GraalShowdown.startBattle(battle, messages.toTypedArray())
val request = JsonObject()
request.addProperty(DataKeys.REQUEST_TYPE, DataKeys.REQUEST_BATTLE_START)
request.addProperty(DataKeys.REQUEST_BATTLE_ID, battle.battleId.toString())
request.add(DataKeys.REQUEST_MESSAGES, jsonArray)
showdown.write(gson.toJson(request))
return battle
}
@@ -99,7 +99,7 @@ data class MoveActionResponse(var moveName: String, var targetPnx: String? = nul
// No PP or disabled or something
return false
}
val availableTargets = move.target.targetList(activeBattlePokemon)?.takeIf { it.isNotEmpty() } ?: return true
val availableTargets = move.target.targetList(activeBattlePokemon) ?: return true
val pnx = targetPnx ?: return false // If the targets list is non-null then they need to have specified a target
val (_, targetPokemon) = activeBattlePokemon.actor.battle.getActorAndActiveSlotFromPNX(pnx)
@@ -28,6 +28,7 @@ import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.battles.runner.ShowdownConnection
import com.cobblemon.mod.common.net.messages.client.battle.BattleFaintPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleInitializePacket
@@ -95,7 +96,6 @@ object ShowdownInterpreter {
updateInstructions["|-status|"] = this::handleStatusInstruction
updateInstructions["|-end|"] = this::handleEndInstruction
updateInstructions["|-miss|"] = this::handleMissInstruction
updateInstructions["|-hitcount|"] = this::handleHitCountInstruction
sideUpdateInstructions["|request|"] = this::handleRequestInstruction
splitUpdateInstructions["|switch|"] = this::handleSwitchInstruction
@@ -149,15 +149,18 @@ object ShowdownInterpreter {
else -> "HUH!?"
}
fun interpretMessage(battleId: UUID, message: String) {
fun interpretMessage(message: String) {
// Check key map and use function if matching
if (message.startsWith("{\"winner\":\"")) {
val battleId = message.split(ShowdownConnection.LINE_START)[0]
val rawMessage = message.replace(battleId + ShowdownConnection.LINE_START, "")
if (rawMessage.startsWith("{\"winner\":\"")) {
// The post-win message is something we don't care about just yet. It's basically a summary of what happened in the battle.
// Check /docs/example-post-win-message.json for its format.
return
}
val battle = BattleRegistry.getBattle(battleId)
val battle = BattleRegistry.getBattle(UUID.fromString(battleId))
if (battle == null) {
LOGGER.info("No battle could be found with the id: $battleId")
@@ -166,7 +169,7 @@ object ShowdownInterpreter {
runOnServer {
battle.showdownMessages.add(message)
interpret(battle, message)
interpret(battle, rawMessage)
}
}
@@ -901,14 +904,4 @@ object ShowdownInterpreter {
}
}
// |-hitcount|POKEMON|NUM
fun handleHitCountInstruction(battle: PokemonBattle, message: String, remainingLines: MutableList<String>) {
battle.dispatchGo {
val hitCount = message.substringAfterLast("|").toIntOrNull() ?: -1
val lang = if (hitCount == 1) battleLang("hit_count_singular") else battleLang("hit_count", hitCount)
battle.broadcastChatMessage(lang)
}
}
}
@@ -11,17 +11,19 @@ package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.battles.runner.GraalShowdown
import com.cobblemon.mod.common.Cobblemon.showdown
import com.cobblemon.mod.common.battles.runner.JavetShowdownConnection
import com.cobblemon.mod.common.util.FileUtils
import com.cobblemon.mod.common.util.extractTo
import com.cobblemon.mod.common.util.fromJson
import com.google.gson.GsonBuilder
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.io.InputStreamReader
import java.util.concurrent.CompletableFuture
import net.minecraft.client.MinecraftClient
import net.minecraft.util.Identifier
class ShowdownThread : Thread("Cobblemon Showdown") {
var showdownStarted = CompletableFuture<Unit>()
@@ -30,8 +32,7 @@ class ShowdownThread : Thread("Cobblemon Showdown") {
.create()
override fun run() {
var showdownDir = File("showdown")
showdownDir.mkdir()
var showdownDir = File(".")
val showdownMetadata = loadShowdownMetadata()
@@ -62,13 +63,73 @@ class ShowdownThread : Thread("Cobblemon Showdown") {
}
}
// Initialize showdown connection
GraalShowdown.createContext()
GraalShowdown.boot()
showdown = JavetShowdownConnection()
(showdown as JavetShowdownConnection).initializeServer()
LOGGER.info("Showdown has been started!")
// Sleep for two seconds before attempting connection
sleep(2000)
val maxTries = 15
var tries = 0
// If connection fails, wait another two seconds
while (!attemptConnection() && tries < maxTries) {
tries++
sleep(3000)
}
// Max attempts
if (tries == maxTries) {
LOGGER.error("Failed to connect to showdown after 5 tries.")
MinecraftClient.getInstance().close()
}
LOGGER.info("Showdown has been connected!")
showdownStarted.complete(Unit)
// Reset tries as this will be used by reconnection attempts
tries = 0
// While showdown is not closed, continue to check connection and read messages
while (!showdown.isClosed()) {
// Attempt reconnection if not connected
if (!showdown.isConnected()) {
while (!attemptConnection() && tries < maxTries) {
tries++
sleep(3000)
}
// Max attempts
if (tries == maxTries) {
LOGGER.error("Failed to connect to showdown after 5 tries.")
MinecraftClient.getInstance().close()
}
tries = 0
LOGGER.info("Showdown has been reconnected!")
}
// Reads messages and don't destroy the connection if there is an error
try {
showdown.read(ShowdownInterpreter::interpretMessage)
} catch (e: Exception) {
e.printStackTrace()
}
// Read messages every half a second
sleep(500)
}
}
private fun attemptConnection() : Boolean {
try {
showdown.open()
return true
} catch (exception: IOException) {
return false
}
}
private fun loadShowdownMetadata() : ShowdownMetadata? {
@@ -1,33 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.runner
import com.cobblemon.mod.common.Cobblemon
import java.util.logging.Handler
import java.util.logging.Level
import java.util.logging.LogRecord
object GraalLogger : Handler() {
override fun publish(record: LogRecord?) {
if(record == null) {
return
}
when (record.level) {
Level.INFO -> Cobblemon.LOGGER.info(record.message)
Level.WARNING -> Cobblemon.LOGGER.warn(record.message)
Level.SEVERE -> Cobblemon.LOGGER.error(record.message)
else -> Cobblemon.LOGGER.debug(record.message)
}
}
override fun flush() {}
override fun close() {}
}
@@ -1,73 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.runner
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import java.io.File
import java.util.UUID
import org.graalvm.polyglot.Context
import org.graalvm.polyglot.HostAccess
import org.graalvm.polyglot.PolyglotAccess
import org.graalvm.polyglot.Value
object GraalShowdown {
@Transient
lateinit var context: Context
@Transient
lateinit var sendBattleMessageFunction: Value
fun createContext() {
val access = HostAccess.newBuilder(HostAccess.ALL).build()
context = Context.newBuilder("js")
.allowIO(true)
.allowExperimentalOptions(true)
.allowPolyglotAccess(PolyglotAccess.ALL)
.allowHostAccess(access)
.allowAllAccess(true)
.allowCreateThread(true)
.logHandler(GraalLogger)
.option("engine.WarnInterpreterOnly", "false")
.option("js.commonjs-require", "true")
.option("js.commonjs-require-cwd", "showdown")
.option(
"js.commonjs-core-modules-replacements",
"buffer:buffer/,crypto:crypto-browserify,path:path-browserify"
)
.allowHostClassLoading(true)
.allowNativeAccess(true)
.allowCreateProcess(true)
.build()
context.eval("js", """
globalThis.process = {
cwd: function() {
return '';
}
}
""".trimIndent())
}
fun boot() {
context.eval("js", File("showdown/index.js").readText())
sendBattleMessageFunction = context.getBindings("js").getMember("sendBattleMessage")
}
fun startBattle(battle: PokemonBattle, messages: Array<String>) {
val startBattleFunction = context.getBindings("js").getMember("startBattle")
startBattleFunction.execute(GraalShowdown, battle.battleId.toString(), messages)
}
fun sendFromShowdown(battleId: String, message: String) {
ShowdownInterpreter.interpretMessage(UUID.fromString(battleId), message)
}
fun sendToShowdown(battleId: UUID, messages: Array<String>) {
sendBattleMessageFunction.execute(battleId.toString(), messages)
}
}
@@ -0,0 +1,72 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.runner
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.net.InetAddress
import java.net.Socket
import java.nio.charset.Charset
class JavetShowdownConnection : ShowdownConnection {
private lateinit var socket: Socket
private lateinit var writer: OutputStreamWriter
private lateinit var reader: BufferedReader
private var data = ""
private var closed = false
private val serverThread = Thread({ ShowdownServer.start() }, "Cobblemon Showdown Server")
fun initializeServer() {
serverThread.start()
}
override fun open() {
socket = Socket(InetAddress.getLocalHost(), ShowdownServer.port, InetAddress.getLocalHost(), 0)
socket.keepAlive = true
writer = socket.getOutputStream().writer(charset = Charset.forName("ascii"))
reader = BufferedReader(InputStreamReader(socket.getInputStream()))
}
override fun close() {
socket.close()
closed = true
}
override fun write(input: String) {
writer.write(input + ShowdownConnection.LINE_END)
writer.flush()
}
override fun read(messageHandler: (String) -> Unit) {
try {
while (reader.ready()) {
val char = reader.read()
if (char > -1) {
data += char.toChar()
if (data.endsWith(ShowdownConnection.LINE_END)) {
messageHandler(data.replace(ShowdownConnection.LINE_END, ""))
data = ""
}
}
}
} catch (exception: IOException) {
exception.printStackTrace()
}
}
override fun isClosed() : Boolean {
return closed
}
override fun isConnected(): Boolean {
return socket.isConnected
}
}
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.runner
import java.io.File
import java.io.IOException
/**
* Modified from https://lankydan.dev/running-a-kotlin-class-as-a-subprocess
*
* Spawns a process and returns it
*/
@Throws(IOException::class, InterruptedException::class)
fun exec(clazz: Class<*>, args: List<String> = emptyList(), jvmArgs: List<String> = emptyList()): Process {
val javaHome = System.getProperty("java.home")
val javaBin = javaHome + File.separator + "bin" + File.separator + "java"
val classpath = System.getProperty("java.class.path")
val className = clazz.name
val command = ArrayList<String>()
command.add(javaBin)
command.addAll(jvmArgs)
command.add("-cp")
command.add(classpath)
command.add(className)
command.addAll(args)
val builder = ProcessBuilder(command)
return builder.inheritIO().start()
}
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.runner
interface ShowdownConnection {
fun open()
fun close()
fun write(input: String)
fun read(messageHandler: (String) -> Unit)
fun isClosed(): Boolean
fun isConnected(): Boolean
companion object {
const val LINE_END = "{EOT}"
const val LINE_START = "{SOT}"
}
}
@@ -0,0 +1,180 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.runner
import com.caoccao.javet.interop.V8Host
import com.caoccao.javet.interop.V8Runtime
import java.net.DatagramSocket
import java.net.ServerSocket
object ShowdownServer {
var minPort = 25650
var maxPort = 25700
var port = 0
fun findOpenPort(): Int? {
for (port in minPort..maxPort) {
if (isOpenPort(port)) {
return port
}
}
return null
}
fun isOpenPort(port: Int): Boolean {
var ss: ServerSocket? = null
var ds: DatagramSocket? = null
try {
ss = ServerSocket(port)
ss.reuseAddress = true
ds = DatagramSocket(port)
ds.reuseAddress = true
return true
} catch (_: Exception) {
} finally {
try { ss?.close() } catch (_: Exception) {}
try { ds?.close() } catch (_: Exception) {}
}
return false
}
fun start() {
val port = findOpenPort() ?: throw IllegalStateException("No port open in range $minPort-$maxPort for Showdown to start on!")
this.port = port
val runtime = V8Host.getNodeInstance().createV8Runtime<V8Runtime>()
runtime.use {
val executor = it.getExecutor(
"""
const PokemonShowdown = require('pokemon-showdown');
var Net = require('net')
const port = $port;
const battleMap = new Map();
// Use net.createServer() in your code. This is just for illustration purpose.
// Create a new TCP server.
const server = Net.createServer();
// The server listens to a socket for a client to make a connection request.
// Think of a socket as an end point.
server.listen(port, function () {
console.log('Server listening for connection requests on socket localhost: ' + port);
});
// When a client requests a connection with the server, the server creates a new
// socket dedicated to that client.
server.on('connection', function (socket) {
console.log('A new connection has been established.');
// The server can also receive data from the client by reading from its socket.
// The end of the message is the tag {EOT}
let messageBuffer = new MessageBuffer("{EOT}")
socket.on('data', function (chunk) {
messageBuffer.push(chunk);
// When is a message is fully formed from chunking, we handle the data
while (!messageBuffer.isFinished()) {
const message = messageBuffer.handleData()
try {
// Parses the request and goes over message types
const request = JSON.parse(message);
switch(request.RequestType) {
case 'StartBattle': {
const battleStream = new PokemonShowdown.BattleStream();
battleMap.set(request.RequestBattleId, battleStream);
// Join messages with new line
for(const element of request.RequestMessages) {
battleStream.write(element);
}
// Any battle output then gets written to the socket
// TODO: Send the battle id with this
// TODO: Add a way to cancel battle earlier and close stream
(async () => {
for await (const output of battleStream) {
socket.write(request.RequestBattleId + "{SOT}" + output + "{EOT}")
}
})();
break;
}
case 'SendMessage': {
const battleStream = battleMap.get(request.RequestBattleId)
for(const element of request.RequestMessages) {
battleStream.write(element);
}
break;
}
}
} catch (error) {
console.error(error);
}
}
});
// When the client requests to end the TCP connection with the server, the server
// ends the connection.
socket.on('end', function () {
console.log('Closing connection with the client');
server.close();
});
// Don't forget to catch error, for your own sake.
socket.on('error', function (err) {
console.log(`Error: ` + err);
});
});
class MessageBuffer {
constructor(delimiter) {
this.delimiter = delimiter
this.buffer = ""
}
isFinished() {
if (
this.buffer.length === 0 ||
this.buffer.indexOf(this.delimiter) === -1
) {
return true
}
return false
}
push(data) {
this.buffer += data
}
getMessage() {
const delimiterIndex = this.buffer.indexOf(this.delimiter)
if (delimiterIndex !== -1) {
const message = this.buffer.slice(0, delimiterIndex)
this.buffer = this.buffer.replace(message + this.delimiter, "")
return message
}
return null
}
handleData() {
/**
* Try to accumulate the buffer with messages
*
* If the server isn't sending delimiters for some reason
* then nothing will ever come back for these requests
*/
const message = this.getMessage()
return message
}
}
""".trimIndent()
)
executor.resourceName = "./node_modules"
executor.executeVoid()
}
}
}
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.runner
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.net.InetAddress
import java.net.Socket
import java.nio.charset.Charset
class StandardShowdownConnection(host: InetAddress, port: Int): ShowdownConnection {
private lateinit var socket: Socket
private lateinit var writer: OutputStreamWriter
private lateinit var reader: BufferedReader
private var data = ""
private var closed: Boolean = false
override fun open() {
socket = Socket(InetAddress.getLocalHost(), 25567, InetAddress.getLocalHost(), 25566)
socket.keepAlive = true
writer = socket.getOutputStream().writer(charset = Charset.forName("ascii"))
reader = BufferedReader(InputStreamReader(socket.getInputStream()))
}
override fun close() {
socket.close()
closed = true
}
override fun write(input: String) {
writer.write(input + ShowdownConnection.LINE_END)
writer.flush()
}
override fun read(messageHandler: (String) -> Unit) {
try {
while (reader.ready()) {
val char = reader.read()
if (char > -1) {
data += char.toChar()
if (data.endsWith(ShowdownConnection.LINE_END)) {
messageHandler(data.replace(ShowdownConnection.LINE_END, ""))
data = ""
}
}
}
} catch (exception: IOException) {
exception.printStackTrace()
}
}
override fun isClosed(): Boolean {
return closed
}
override fun isConnected(): Boolean {
return socket.isConnected
}
}
@@ -151,7 +151,7 @@ object CobblemonClient {
fun reloadCodedAssets(resourceManager: ResourceManager) {
LOGGER.info("Reloading assets")
BedrockAnimationRepository.loadAnimations(resourceManager)
BedrockAnimationRepository.clear()
PokemonModelRepository.reload(resourceManager)
LOGGER.info("Loaded assets")
// PokeBallModelRepository.reload(resourceManager)
@@ -22,7 +22,6 @@ class ClientBattlePokemon(
val uuid: UUID,
var displayName: MutableText,
var properties: PokemonProperties,
var aspects: Set<String>,
var hpRatio: Float,
var status: PersistentStatus?,
var statChanges: MutableMap<Stat, Int>
@@ -163,7 +163,7 @@ class BattleOverlay : InGameHud(MinecraftClient.getInstance(), MinecraftClient.g
reversed = !left,
species = battlePokemon.species,
level = battlePokemon.level,
aspects = battlePokemon.aspects,
aspects = battlePokemon.properties.aspects,
displayName = battlePokemon.displayName,
gender = battlePokemon.gender,
status = battlePokemon.status,
@@ -172,7 +172,7 @@ class BattleOverlay : InGameHud(MinecraftClient.getInstance(), MinecraftClient.g
colour = Triple(r, g, b),
opacity = opacity.toFloat(),
ballState = activeBattlePokemon.ballCapturing,
trueHealth = truePokemon?.let { (it.hp * battlePokemon.hpRatio).roundToInt() to it.hp }
trueHealth = truePokemon?.let { (it.currentHealth * battlePokemon.hpRatio).roundToInt() to (it.hp * battlePokemon.hpRatio).roundToInt() }
)
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.client.gui.battle.subscreen
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.battle.SingleActionRequest
@@ -18,8 +17,6 @@ import com.cobblemon.mod.common.util.battleLang
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.Selectable
import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.MutableText
import net.minecraft.util.Identifier
@@ -97,9 +94,5 @@ class BattleGeneralActionSelection(
override fun appendNarrations(builder: NarrationMessageBuilder) {
}
override fun playDownSound(soundManager: SoundManager) {
soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK.get(), 1.0F))
}
override fun getType() = Selectable.SelectionType.NONE
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.client.gui.battle.subscreen
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.text.bold
@@ -27,8 +26,6 @@ import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.toRGB
import net.minecraft.client.MinecraftClient
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
import net.minecraft.util.math.MathHelper.floor
@@ -159,7 +156,7 @@ class BattleMoveSelection(
}
moveSelection.playDownSound(MinecraftClient.getInstance().soundManager)
val targets = move.target.targetList(moveSelection.request.activePokemon)
if (targets.isNullOrEmpty()) {
if (targets == null) {
moveSelection.battleGUI.selectAction(moveSelection.request, MoveActionResponse(move.id, null))
} else if (targets.size == 1) {
moveSelection.battleGUI.selectAction(moveSelection.request, MoveActionResponse(move.id, targets[0].getPNX()))
@@ -188,8 +185,4 @@ class BattleMoveSelection(
}
return false
}
override fun playDownSound(soundManager: SoundManager) {
soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK.get(), 1.0F))
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.client.gui.battle.subscreen
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.battles.ShowdownPokemon
import com.cobblemon.mod.common.battles.SwitchActionResponse
import com.cobblemon.mod.common.client.CobblemonClient
@@ -20,8 +19,6 @@ import com.cobblemon.mod.common.util.battleLang
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.Selectable
import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.util.math.MathHelper.ceil
class BattleSwitchPokemonSelection(
@@ -123,9 +120,5 @@ class BattleSwitchPokemonSelection(
override fun appendNarrations(builder: NarrationMessageBuilder) {
}
override fun playDownSound(soundManager: SoundManager) {
soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK.get(), 1.0F))
}
override fun getType() = Selectable.SelectionType.HOVERED
}
@@ -1,35 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.api.storage.pc.PCPosition
import com.cobblemon.mod.common.client.storage.ClientPC
import com.cobblemon.mod.common.pokemon.Pokemon
class BoxStorageSlot(
x: Int, y: Int,
private val parent: StorageWidget,
private val pc: ClientPC,
val position: PCPosition,
onPress: PressAction
) : StorageSlot(x, y, parent, onPress) {
override fun getPokemon(): Pokemon? {
return pc.get(position)
}
override fun shouldRender(): Boolean {
val grabbedSlot = parent.grabbedSlot
return if (grabbedSlot == null) {
true
} else {
grabbedSlot.getPokemon() != getPokemon()
}
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.widget.TexturedButtonWidget
import net.minecraft.client.util.math.MatrixStack
class ExitButton(
pX: Int, pY: Int,
val pWidth: Int, val pHeight: Int,
pXTexStart: Int, pYTexStart: Int, pYDiffText: Int,
onPress: PressAction
): TexturedButtonWidget(pX, pY, pWidth, pHeight, pXTexStart, pYTexStart, pYDiffText, exitButtonResource, onPress) {
companion object {
private val exitButtonResource = cobblemonResource("ui/pc/pc_exit.png")
}
override fun renderButton(pMatrixStack: MatrixStack, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
hovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height
if (isHovered) {
blitk(
matrixStack = pMatrixStack,
x = x + 0.1, y = y - 1F,
texture = exitButtonResource,
width = pWidth, height = pHeight
)
}
}
}
@@ -1,31 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.client.util.math.MatrixStack
class GrabbedStorageSlot(
x: Int, y: Int,
parent: StorageWidget,
private val pokemon: Pokemon
) : StorageSlot(x, y, parent, {}) {
override fun render(matrices: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
renderSlot(matrices, mouseX - (width / 2), mouseY - (height / 2))
}
override fun getPokemon(): Pokemon? {
return pokemon
}
override fun isSelected(): Boolean {
return true
}
}
@@ -8,47 +8,33 @@
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.gui.widget.TexturedButtonWidget
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
class NavigationButton(
pX: Int, pY: Int,
val pWidth: Int, val pHeight: Int,
pXTexStart: Int, pYTexStart: Int, pYDiffText: Int,
private val forward: Boolean,
onPress: PressAction
): ButtonWidget(pX, pY, (WIDTH * SCALE).toInt(), (HEIGHT * SCALE).toInt(), Text.literal("Navigation"), onPress) {
onPress: PressAction,
): TexturedButtonWidget(pX, pY, pWidth, pHeight, pXTexStart, pYTexStart, pYDiffText, forwardButtonResource, onPress) {
companion object {
private const val WIDTH = 8F
private const val HEIGHT = 10F
private const val SCALE = 0.5F
private val forwardButtonResource = cobblemonResource("ui/pc/pc_arrow_next.png")
private val backwardsButtonResource = cobblemonResource("ui/pc/pc_arrow_previous.png")
private val forwardButtonResource = cobblemonResource("ui/pc/pc_box_right.png")
private val backwardsButtonResource = cobblemonResource("ui/pc/pc_box_left.png")
}
override fun renderButton(pMatrixStack: MatrixStack, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
val hovered = (isHovered(pMouseX.toDouble(), pMouseY.toDouble()))
blitk(
matrixStack = pMatrixStack,
x = x / SCALE,
y = y / SCALE,
texture = if (forward) forwardButtonResource else backwardsButtonResource,
width = WIDTH,
height = HEIGHT,
vOffset = if (hovered) HEIGHT else 0,
textureHeight = HEIGHT * 2,
scale = SCALE
)
hovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height
if (isHovered) {
blitk(
matrixStack = pMatrixStack,
x = x + 0.25, y = y - 0.5F,
texture = if (forward) forwardButtonResource else backwardsButtonResource,
width = pWidth, height = pHeight
)
}
}
override fun playDownSound(soundManager: SoundManager) {
soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK.get(), 1.0F))
}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in (x.toFloat()..(x.toFloat() + (WIDTH * SCALE))) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + (HEIGHT * SCALE)))
}
@@ -0,0 +1,81 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.storage.pc.PCPosition
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.storage.ClientPC
import com.cobblemon.mod.common.util.scaleIt
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
import net.minecraft.util.math.Quaternion
import net.minecraft.util.math.Vec3f
class PCBoxMemberWidget(
x: Int, y: Int,
private val parent: PCWidget,
private val pcGui: PCGui,
private val pc: ClientPC,
val position: PCPosition,
onPress: PressAction
) : ButtonWidget(x - PC_BOX_DIMENSION, y, PC_BOX_DIMENSION, PC_BOX_DIMENSION, Text.literal("PCBoxMember"), onPress) {
companion object {
// Box slot
private const val PC_BOX_DIMENSION = 32
// Portrait
private const val PORTRAIT_DIMENSIONS = 27
}
var playingDownSound = false
override fun playDownSound(soundManager: SoundManager) {
}
override fun render(matrices: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
val pokemon = pc.get(position) ?: return
matrices.push()
val minecraft = MinecraftClient.getInstance()
RenderSystem.enableScissor(
this.scaleIt(this.x + 3),
minecraft.window.height - this.scaleIt(this.y + PORTRAIT_DIMENSIONS + 2),
this.scaleIt(PORTRAIT_DIMENSIONS),
this.scaleIt(PORTRAIT_DIMENSIONS)
)
matrices.translate(this.x + (PORTRAIT_DIMENSIONS / 2.0) + 4, this.y + 4.0, 0.0)
matrices.scale(2.5F, 2.5F, 1F)
drawProfilePokemon(
renderablePokemon = pokemon.asRenderablePokemon(),
matrixStack = matrices,
rotation = Quaternion.fromEulerXyzDegrees(Vec3f(13F, 35F, 0F)),
state = null,
scale = 6F
)
RenderSystem.disableScissor()
matrices.pop()
if (position == parent.selectedPosition) {
blitk(
matrixStack = matrices,
texture = PCWidget.selectedResource,
x = x,
y = y,
height = PC_BOX_DIMENSION,
width = PC_BOX_DIMENSION
)
}
}
}
@@ -1,410 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.storage.ClientPC
import com.cobblemon.mod.common.client.storage.ClientParty
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.sound.SoundEvent
import net.minecraft.text.Text
class PCGUI(
private val pc: ClientPC,
private val party: ClientParty
) : Screen(Text.translatable("cobblemon.ui.pc.title")) {
companion object {
const val BASE_WIDTH = 349
const val BASE_HEIGHT = 205
const val TYPE_SPACER_WIDTH = 128
const val TYPE_SPACER_HEIGHT = 12
const val PC_SPACER_WIDTH = 342
const val PC_SPACER_HEIGHT = 14
const val PORTRAIT_SIZE = 66
const val SCALE = 0.5F
private val baseResource = cobblemonResource("ui/pc/pc_base.png")
private val portraitBackgroundResource = cobblemonResource("ui/pc/portrait_background.png")
private val topSpacerResource = cobblemonResource("ui/pc/pc_spacer_top.png")
private val bottomSpacerResource = cobblemonResource("ui/pc/pc_spacer_bottom.png")
private val rightSpacerResource = cobblemonResource("ui/pc/pc_spacer_right.png")
private val typeSpacerResource = cobblemonResource("ui/pc/type_spacer.png")
private val typeSpacerSingleResource = cobblemonResource("ui/pc/type_spacer_single.png")
private val typeSpacerDoubleResource = cobblemonResource("ui/pc/type_spacer_double.png")
}
private lateinit var storageWidget: StorageWidget
private var modelWidget: ModelWidget? = null
internal var previewPokemon: Pokemon? = null
var ticksElapsed = 0
var selectPointerOffsetY = 0
var selectPointerOffsetIncrement = false
override fun init() {
val x = (width - BASE_WIDTH) / 2
val y = (height - BASE_HEIGHT) / 2
// Add Exit Button
this.addDrawableChild(
ExitButton(pX = x + 320, pY = y + 186) {
playSound(CobblemonSounds.PC_OFF.get())
MinecraftClient.getInstance().setScreen(null)
}
)
// Add Forward Button
this.addDrawableChild(
NavigationButton(
pX = x + 221,
pY = y + 17,
forward = true
) { storageWidget.box += 1 }
)
// Add Backwards Button
this.addDrawableChild(
NavigationButton(
pX = x + 119,
pY = y + 17,
forward = false
) { storageWidget.box -= 1 }
)
// Add Storage
this.storageWidget = StorageWidget(
pX = x + 85,
pY = y + 27,
pcGui = this,
pc = pc,
party = party
)
this.setPreviewPokemon(null)
this.addDrawableChild(storageWidget)
super.init()
}
override fun render(matrices: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
renderBackground(matrices)
val x = (width - BASE_WIDTH) / 2
val y = (height - BASE_HEIGHT) / 2
// Render Portrait Background
blitk(
matrixStack = matrices,
texture = portraitBackgroundResource,
x = x + 6,
y = y + 27,
width = PORTRAIT_SIZE,
height = PORTRAIT_SIZE
)
// Render Model Portrait
modelWidget?.render(matrices, mouseX, mouseY, delta)
// Render Base Resource
blitk(
matrixStack = matrices,
texture = baseResource,
x = x, y = y,
width = BASE_WIDTH,
height = BASE_HEIGHT
)
// Render Info Labels
drawScaledText(
matrixStack = matrices,
text = lang("ui.info.nature").bold(),
x = x + 39,
y = y + 129.5,
centered = true,
scale = SCALE
)
drawScaledText(
matrixStack = matrices,
text = lang("ui.info.ability").bold(),
x = x + 39,
y = y + 146.5,
centered = true,
scale = SCALE
)
drawScaledText(
matrixStack = matrices,
text = lang("ui.moves").bold(),
x = x + 39,
y = y + 163.5,
centered = true,
scale = SCALE
)
// Render Pokemon Info
val pokemon = previewPokemon
if (pokemon != null) {
// Status
val status = pokemon.status?.status
if (pokemon.isFainted() || status != null) {
val statusName = if (pokemon.isFainted()) "fnt" else status?.showdownName
blitk(
matrixStack = matrices,
texture = cobblemonResource("ui/battle/battle_status_$statusName.png"),
x = x + 34,
y = y + 1,
height = 7,
width = 39,
uOffset = 35,
textureWidth = 74
)
blitk(
matrixStack = matrices,
texture = cobblemonResource("ui/summary/status_trim.png"),
x = x + 34,
y = y + 2,
height = 6,
width = 3
)
drawScaledText(
matrixStack = matrices,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.status.$statusName").bold(),
x = x + 39,
y = y
)
}
// Level
drawScaledText(
matrixStack = matrices,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.lv").bold(),
x = x + 6,
y = y + 1.5,
shadow = true
)
drawScaledText(
matrixStack = matrices,
font = CobblemonResources.DEFAULT_LARGE,
text = pokemon.level.toString().text().bold(),
x = x + 19,
y = y + 1.5,
shadow = true
)
// Poké Ball
val ballResource = cobblemonResource("textures/items/poke_balls/" + pokemon.caughtBall.name.path + ".png")
blitk(
matrixStack = matrices,
texture = ballResource,
x = (x + 3.5) / SCALE,
y = (y + 12) / SCALE,
width = 16,
height = 16,
scale = SCALE
)
drawScaledText(
matrixStack = matrices,
font = CobblemonResources.DEFAULT_LARGE,
text = pokemon.displayName.bold(),
x = x + 12,
y = y + 11.5,
shadow = true
)
if (pokemon.gender != Gender.GENDERLESS) {
val isMale = pokemon.gender == Gender.MALE
val textSymbol = if (isMale) "♂".text().bold() else "♀".text().bold()
drawScaledText(
matrixStack = matrices,
font = CobblemonResources.DEFAULT_LARGE,
text = textSymbol,
x = x + 69, // 64 when tag icon is implemented
y = y + 11.5,
colour = if (isMale) 0x32CBFF else 0xFC5454,
shadow = true
)
}
blitk(
matrixStack = matrices,
texture = if (pokemon.secondaryType != null) typeSpacerDoubleResource else typeSpacerSingleResource,
x = (x + 7) / SCALE,
y = (y + 118.5) / SCALE,
width = TYPE_SPACER_WIDTH,
height = TYPE_SPACER_HEIGHT,
scale = SCALE
)
TypeIcon(
x = x + 39,
y = y + 117,
type = pokemon.primaryType,
secondaryType = pokemon.secondaryType,
doubleCenteredOffset = 5F,
secondaryOffset = 10F,
small = true,
centeredX = true
).render(matrices)
// Nature
drawScaledText(
matrixStack = matrices,
text = pokemon.nature.displayName.asTranslated(),
x = x + 39,
y = y + 137,
centered = true,
shadow = true,
scale = SCALE
)
// Ability
drawScaledText(
matrixStack = matrices,
text = pokemon.ability.displayName.asTranslated(),
x = x + 39,
y = y + 154,
centered = true,
shadow = true,
scale = SCALE
)
// Moves
val moves = pokemon.moveSet.getMoves()
for (i in moves.indices) {
drawScaledText(
matrixStack = matrices,
text = moves[i].displayName,
x = x + 39,
y = y + 170.5 + (7 * i),
centered = true,
shadow = true,
scale = SCALE
)
}
} else {
blitk(
matrixStack = matrices,
texture = typeSpacerResource,
x = (x + 7) / SCALE,
y = (y + 118.5) / SCALE,
width = TYPE_SPACER_WIDTH,
height = TYPE_SPACER_HEIGHT,
scale = SCALE
)
}
// Box Label
drawScaledText(
matrixStack = matrices,
font = CobblemonResources.DEFAULT_LARGE,
text = Text.translatable("cobblemon.ui.pc.box.title", (this.storageWidget.box + 1).toString()).bold(),
x = x + 172,
y = y + 15,
centered = true
)
blitk(
matrixStack = matrices,
texture = topSpacerResource,
x = (x + 86.5) / SCALE,
y = (y + 13) / SCALE,
width = PC_SPACER_WIDTH,
height = PC_SPACER_HEIGHT,
scale = SCALE
)
blitk(
matrixStack = matrices,
texture = bottomSpacerResource,
x = (x + 86.5) / SCALE,
y = (y + 189) / SCALE,
width = PC_SPACER_WIDTH,
height = PC_SPACER_HEIGHT,
scale = SCALE
)
blitk(
matrixStack = matrices,
texture = rightSpacerResource,
x = (x + 275.5) / SCALE,
y = (y + 184) / SCALE,
width = 64,
height = 24,
scale = SCALE
)
super.render(matrices, mouseX, mouseY, delta)
}
/**
* Whether this Screen should pause the Game in SinglePlayer
*/
override fun shouldPause(): Boolean {
return false
}
override fun tick() {
ticksElapsed++
// Calculate select pointer offset
var delayFactor = 3
if (ticksElapsed % (2 * delayFactor) == 0) selectPointerOffsetIncrement = !selectPointerOffsetIncrement
if (ticksElapsed % delayFactor == 0) selectPointerOffsetY += if (selectPointerOffsetIncrement) 1 else -1
}
private fun playSound(soundEvent: SoundEvent) {
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(soundEvent, 1.0F))
}
fun setPreviewPokemon(pokemon: Pokemon?) {
if (pokemon != null) {
previewPokemon = pokemon
val x = (width - BASE_WIDTH) / 2
val y = (height - BASE_HEIGHT) / 2
modelWidget = ModelWidget(
pX = x + 6,
pY = y + 27,
pWidth = PORTRAIT_SIZE,
pHeight = PORTRAIT_SIZE,
pokemon = pokemon.asRenderablePokemon(),
baseScale = 2F,
rotationY = 325F,
offsetY = -10.0
)
} else {
previewPokemon = null;
modelWidget = null;
}
}
}
@@ -0,0 +1,128 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.api.gui.ColourLibrary
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.storage.ClientPC
import com.cobblemon.mod.common.client.storage.ClientParty
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
class PCGui(
private val pc: ClientPC,
private val party: ClientParty
) : Screen(Text.translatable("cobblemon.ui.pc.title")) {
companion object {
// Size of Background
private const val backgroundHeight = 200
private const val backgroundWidth = 300
// Textures
private val background = cobblemonResource("ui/pc/pc_base.png")
private val underlay = cobblemonResource("ui/pc/pc_underlay.png")
}
private lateinit var pcWidget: PCWidget
override fun init() {
val x = (width - backgroundWidth) / 2
val y = (height - backgroundHeight) / 2
// Add Exit Button
this.addDrawableChild(
ExitButton(
pX = x + 249, pY = y + 4,
pWidth = 16, pHeight = 12,
pXTexStart = 0, pYTexStart = 0, pYDiffText = 0
) {
MinecraftClient.getInstance().setScreen(null)
})
// Add Forward Button
this.addDrawableChild(
NavigationButton(
pX = x + 230, pY = y + 9,
pWidth = 9, pHeight = 14,
pXTexStart = 0, pYTexStart = 0, pYDiffText = 0,
forward = true
) {
pcWidget.box += 1
})
// Add Backwards Button
this.addDrawableChild(
NavigationButton(
pX = x + 108, pY = y + 9,
pWidth = 9, pHeight = 14,
pXTexStart = 0, pYTexStart = 0, pYDiffText = 0,
forward = false
) {
pcWidget.box -= 1
})
// Add PC
this.pcWidget = PCWidget(
pX = x + 116, pY = y + 32,
pWidth = 175, pHeight = 145,
pcGui = this,
pc = pc,
party = party
)
this.addDrawableChild(pcWidget)
super.init()
}
override fun render(matrices: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
renderBackground(matrices)
val x = (width - backgroundWidth) / 2
val y = (height - backgroundHeight) / 2
// Rendering PC Underlay
blitk(
matrixStack = matrices,
texture = underlay,
x = x + 7, y = y + 25,
width = 65, height = 65
)
// Rendering UI Background
blitk(
matrixStack = matrices,
texture = background,
x = x, y = y,
width = backgroundWidth, height = backgroundHeight
)
drawScaledText(
matrixStack = matrices,
font = CobblemonResources.DEFAULT_LARGE,
text = Text.translatable("cobblemon.ui.box.title", (this.pcWidget.box + 1).toString()),
x = (x + 173.5), y = (y + 8.75),
colour = ColourLibrary.WHITE, shadow = false,
centered = true,
scale = 1.5f,
)
super.render(matrices, mouseX, mouseY, delta)
}
/**
* Whether this Screen should pause the Game in SinglePlayer
*/
override fun shouldPause(): Boolean {
return false
}
}
@@ -0,0 +1,80 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonFloatingState
import com.cobblemon.mod.common.client.storage.ClientPC
import com.cobblemon.mod.common.client.storage.ClientParty
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.MinecraftClient
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
import net.minecraft.util.math.Quaternion
import net.minecraft.util.math.Vec3f
class PCPreviewSelectedWidget(
pX: Int, pY: Int,
pWidth: Int, pHeight: Int,
val baseScale: Float = 2.7F,
private val parent: PCWidget,
private val pc: ClientPC,
private val party: ClientParty,
): SoundlessWidget(pX, pY, pWidth, pHeight, Text.literal("PC - PreviewSelectedWidget")) {
companion object {
var render = true
}
var state = PokemonFloatingState()
private val minecraft = MinecraftClient.getInstance()
private var rotVec = Vec3f(13F, 35F, 0F)
override fun render(pMatrixStack: MatrixStack, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
if (!render) {
return
}
hovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height
renderPKM(pMatrixStack)
}
private fun renderPKM(poseStack: MatrixStack) {
val pokemon = parent.getSelectedPokemon() ?: return
poseStack.push()
RenderSystem.enableScissor(
(x * minecraft.window.scaleFactor).toInt(),
(minecraft.window.height - (y * minecraft.window.scaleFactor) - (height * minecraft.window.scaleFactor)).toInt(),
(width * minecraft.window.scaleFactor).toInt(),
(height * minecraft.window.scaleFactor).toInt()
)
poseStack.translate(x + width * 0.5, y.toDouble(), 0.0)
poseStack.scale(baseScale, baseScale, baseScale)
poseStack.push()
drawProfilePokemon(
renderablePokemon = pokemon.asRenderablePokemon(),
matrixStack = poseStack,
rotation = Quaternion.fromEulerXyzDegrees(rotVec),
state = state
)
poseStack.pop()
RenderSystem.disableScissor()
poseStack.pop()
}
override fun onClick(pMouseX: Double, pMouseY: Double) {
}
}
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.storage.ClientPC
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
class PCTrashWidget(
x: Int, y: Int,
private val parent: PCWidget,
private val pc: ClientPC,
onPress: PressAction
) : ButtonWidget(x, y, WIDTH, HEIGHT, Text.literal("PCTrash"), onPress) {
companion object {
private const val WIDTH = 13
private const val HEIGHT = 14
val trashIcon = cobblemonResource("ui/pc/pc_trash.png")
val trashIconHovered = cobblemonResource("ui/pc/pc_trash_hovered.png")
}
override fun render(matrices: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
if (!parent.canDeleteSelected()) {
return
}
val isHovered = mouseX in x..(x + WIDTH) && mouseY in y..(y + HEIGHT)
val icon = if (isHovered) trashIconHovered else trashIcon
blitk(
matrixStack = matrices,
x = x,
y = y,
width = WIDTH,
height = HEIGHT,
texture = icon
)
}
}
@@ -0,0 +1,261 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.api.storage.StorePosition
import com.cobblemon.mod.common.api.storage.party.PartyPosition
import com.cobblemon.mod.common.api.storage.pc.PCPosition
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.settings.ServerSettings
import com.cobblemon.mod.common.client.storage.ClientPC
import com.cobblemon.mod.common.client.storage.ClientParty
import com.cobblemon.mod.common.net.messages.server.storage.SwapPCPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.MovePartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.ReleasePartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.SwapPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonToPartyPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePartyPokemonToPCPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.ReleasePCPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.SwapPCPokemonPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.sound.SoundEvents
import net.minecraft.text.Text
class PCWidget(
pX: Int, pY: Int,
pWidth: Int, pHeight: Int,
private val pcGui: PCGui,
private val pc: ClientPC,
private val party: ClientParty
) : SoundlessWidget(pX, pY, pWidth, pHeight, Text.literal("PCWidget")) {
companion object {
val selectedResource = cobblemonResource("ui/pc/pc_selected.png")
}
var selectedPosition: StorePosition? = null
var box = 0
set(value) {
// If value is within min and max
field = if (value > 0 && value < pc.boxes.size) {
value
}
// If value is less than zero, wrap around to end.
else if (value < 0) {
pc.boxes.size - 1
}
// Else it's greater than max, wrap around to start.
else {
0
}
this.setupMemberWidgets()
}
private val partyWidgets = arrayListOf<PartyMemberWidget>()
private val pcWidgets = arrayListOf<PCBoxMemberWidget>()
private val previewWidget: PCPreviewSelectedWidget
private val trashWidget: PCTrashWidget
fun canDeleteSelected(): Boolean {
return !(selectedPosition is PartyPosition && party.filterNotNull().size <= 1) && selectedPosition != null
}
init {
this.setupMemberWidgets()
this.previewWidget = PCPreviewSelectedWidget(
pX = x - 105,
pY = y,
pWidth = 60,
pHeight = 70,
baseScale = 1.5f,
parent = this,
pc = pc,
party = party
).also {
this.addWidget(it)
}
this.trashWidget = PCTrashWidget(
x = x - 22,
y = y + 150,
parent = this,
pc = pc,
onPress = {
if (canDeleteSelected()) {
val position = selectedPosition ?: return@PCTrashWidget
val pokemon = getSelectedPokemon() ?: return@PCTrashWidget
val packet = when (position) {
is PartyPosition -> ReleasePartyPokemonPacket(pokemon.uuid, position)
is PCPosition -> ReleasePCPokemonPacket(pokemon.uuid, position)
else -> return@PCTrashWidget
}
CobblemonNetwork.sendToServer(packet)
selectedPosition = null
}
}
).also {
addWidget(it)
}
}
private fun setupMemberWidgets() {
this.resetWidgets()
var index = 0
// Box members
for (row in 1..5) {
for (col in 1..6) {
PCBoxMemberWidget(
x = x + (col-1) * 29,
y = y + (row-1) * 29,
parent = this,
pcGui = pcGui,
pc = pc,
position = PCPosition(box, index),
onPress = { this.onPokemonPressed(it) }
).also { widget ->
this.addWidget(widget)
this.pcWidgets.add(widget)
}
index++
}
}
// Party slots
for (partySlot in 0..5) {
val texture = when(partySlot) {
0 -> PartyMemberWidget.slotOneResource
5 -> PartyMemberWidget.slotSixResource
else -> PartyMemberWidget.slotTwoThroughFiveResource
}
PartyMemberWidget(
x = x + 185,
y = (y - 19) + partySlot * 31,
parent = this,
pcGui = pcGui,
pc = pc,
party = party,
position = PartyPosition(partySlot),
texture = texture,
onPress = { this.onPokemonPressed(it) }
).also { widget ->
this.addWidget(widget)
this.partyWidgets.add(widget)
}
}
}
fun getSelectedPokemon(): Pokemon? {
val selectedPosition = this.selectedPosition ?: return null
return when (selectedPosition) {
is PCPosition -> pc.get(selectedPosition)
is PartyPosition -> party.get(selectedPosition)
else -> null
}
}
override fun render(matrices: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
this.previewWidget.render(matrices, mouseX, mouseY, delta)
this.pcWidgets.forEach { widget -> widget.render(matrices, mouseX, mouseY, delta) }
this.partyWidgets.forEach { widget -> widget.render(matrices, mouseX, mouseY, delta) }
this.trashWidget.render(matrices, mouseX, mouseY, delta)
}
private fun resetWidgets() {
this.partyWidgets.forEach(this::removeWidget)
this.partyWidgets.clear()
this.pcWidgets.forEach(this::removeWidget)
this.pcWidgets.clear()
}
fun clickSound() {
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0f))
}
private fun onPokemonPressed(button: ButtonWidget) {
// Only use on member widgets
val clickedPosition = when(button) {
is PCBoxMemberWidget -> button.position
is PartyMemberWidget -> button.position
else -> return
}
// Clicking on itself, so unselect position.
if (this.selectedPosition != null && this.selectedPosition == clickedPosition) {
this.selectedPosition = null
clickSound()
return
}
// Check for selecting a Pokémon.
val clickedPokemon = when(button) {
is PCBoxMemberWidget -> pc.get(clickedPosition as PCPosition)
is PartyMemberWidget -> party.get(clickedPosition as PartyPosition)
else -> null
}
if (this.selectedPosition == null && clickedPokemon != null) {
this.selectedPosition = clickedPosition
clickSound()
return
}
// Handle movement within the PC.
val selectedPokemon = when(this.selectedPosition) {
is PCPosition -> pc.get(this.selectedPosition as PCPosition)
is PartyPosition -> party.get(this.selectedPosition as PartyPosition)
else -> null
} ?: return
// PC -> PC
if (this.selectedPosition is PCPosition && clickedPosition is PCPosition) {
val packet = clickedPokemon?.let { SwapPCPokemonPacket(it.uuid, clickedPosition, selectedPokemon.uuid, this.selectedPosition as PCPosition) }
?: MovePCPokemonPacket(selectedPokemon.uuid, selectedPosition as PCPosition, clickedPosition)
packet.sendToServer()
clickSound()
this.selectedPosition = null
}
// PC -> Party
else if (this.selectedPosition is PCPosition && clickedPosition is PartyPosition) {
val packet = clickedPokemon?.let { SwapPCPartyPokemonPacket(clickedPokemon.uuid, clickedPosition, selectedPokemon.uuid, this.selectedPosition as PCPosition) }
?: MovePCPokemonToPartyPacket(selectedPokemon.uuid, this.selectedPosition as PCPosition, clickedPosition)
packet.sendToServer()
this.selectedPosition = null
}
// Party -> PC
else if (this.selectedPosition is PartyPosition && clickedPosition is PCPosition) {
if (ServerSettings.preventCompletePartyDeposit && this.party.filterNotNull().size == 1 && clickedPokemon == null) {
return
}
val packet = clickedPokemon?.let { SwapPCPartyPokemonPacket(selectedPokemon.uuid, this.selectedPosition as PartyPosition, clickedPokemon.uuid, clickedPosition) }
?: MovePartyPokemonToPCPacket(selectedPokemon.uuid, this.selectedPosition as PartyPosition, clickedPosition)
packet.sendToServer()
this.selectedPosition = null
}
// Party -> Party
else if (this.selectedPosition is PartyPosition && clickedPosition is PartyPosition) {
val packet = clickedPokemon?.let { SwapPartyPokemonPacket(it.uuid, clickedPosition, selectedPokemon.uuid, this.selectedPosition as PartyPosition) }
?: MovePartyPokemonPacket(selectedPokemon.uuid, selectedPosition as PartyPosition, clickedPosition)
packet.sendToServer()
this.selectedPosition = null
}
}
}
@@ -0,0 +1,107 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.storage.party.PartyPosition
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.storage.ClientPC
import com.cobblemon.mod.common.client.storage.ClientParty
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.scaleIt
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import net.minecraft.util.math.Quaternion
import net.minecraft.util.math.Vec3f
class PartyMemberWidget(
x: Int, y: Int,
private val parent: PCWidget,
private val pcGui: PCGui,
private val pc: ClientPC,
private val party: ClientParty,
val position: PartyPosition,
private val texture: Identifier,
onPress: PressAction
) : ButtonWidget(x - PC_BOX_DIMENSION, y, PC_BOX_DIMENSION, PC_BOX_DIMENSION, Text.literal("PartyMember"), onPress) {
companion object {
// Box slot
private const val PC_BOX_DIMENSION = 32
// Portrait
private const val PORTRAIT_DIMENSIONS = 29
// Slot Textures
val slotOneResource = cobblemonResource("ui/pc/pc_party_1.png")
val slotTwoThroughFiveResource = cobblemonResource("ui/pc/pc_party_2-5.png")
val slotSixResource = cobblemonResource("ui/pc/pc_party_6.png")
}
override fun playDownSound(soundManager: SoundManager) {
}
override fun render(matrices: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
matrices.push()
val xOffset = if (texture == slotSixResource) -2f else 0f
val slotWidth = if (texture == slotSixResource) 35 else 33
blitk(
matrixStack = matrices,
x = x + xOffset + -0.9f, y = y - 1F,
texture = this.texture,
width = slotWidth, height = 33
)
val pokemon = party.get(position)
if (pokemon != null) {
val pokemonX = this.x - 3
val pokemonY = this.y - 1
val minecraft = MinecraftClient.getInstance()
RenderSystem.enableScissor(
this.scaleIt(pokemonX + 3),
minecraft.window.height - this.scaleIt(pokemonY + PORTRAIT_DIMENSIONS + 2),
this.scaleIt(PORTRAIT_DIMENSIONS),
this.scaleIt(PORTRAIT_DIMENSIONS)
)
matrices.push()
matrices.translate(pokemonX + (PORTRAIT_DIMENSIONS / 2.0) + 4, pokemonY + 4.0, 0.0)
matrices.scale(2.5F, 2.5F, 1F)
drawProfilePokemon(
renderablePokemon = pokemon.asRenderablePokemon(),
matrixStack = matrices,
rotation = Quaternion.fromEulerXyzDegrees(Vec3f(13F, 35F, 0F)),
state = null,
scale = 6F
)
RenderSystem.disableScissor()
matrices.pop()
if (position == parent.selectedPosition) {
blitk(
matrixStack = matrices,
texture = PCWidget.selectedResource,
x = x,
y = y + 1,
height = PORTRAIT_DIMENSIONS,
width = PORTRAIT_DIMENSIONS
)
}
}
matrices.pop()
}
}
@@ -1,35 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.api.storage.party.PartyPosition
import com.cobblemon.mod.common.client.storage.ClientParty
import com.cobblemon.mod.common.pokemon.Pokemon
class PartyStorageSlot(
x: Int, y: Int,
private val parent: StorageWidget,
private val party: ClientParty,
val position: PartyPosition,
onPress: PressAction
) : StorageSlot(x, y, parent, onPress) {
override fun getPokemon(): Pokemon? {
return party.get(position)
}
override fun shouldRender(): Boolean {
val grabbedSlot = parent.grabbedSlot
return if (grabbedSlot == null) {
true
} else {
grabbedSlot.getPokemon() != getPokemon()
}
}
}
@@ -1,64 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
class ReleaseButton(
x: Int, y: Int,
private val parent: StorageWidget,
onPress: PressAction
) : ButtonWidget(x, y, WIDTH, HEIGHT, Text.literal("Release"), onPress) {
companion object {
private const val WIDTH = 58
private const val HEIGHT = 16
private val buttonResource = cobblemonResource("ui/pc/pc_release_button.png")
}
override fun render(matrices: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
if (parent.canDeleteSelected() && !parent.displayConfirmRelease) {
blitk(
matrixStack = matrices,
texture = buttonResource,
x = x,
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
drawScaledText(
matrixStack = matrices,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.pc.release").bold(),
x = x + (WIDTH / 2),
y = y + 3.5,
centered = true,
shadow = true
)
}
}
override fun playDownSound(pHandler: SoundManager) {
}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in (x.toFloat()..(x.toFloat() + WIDTH)) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + HEIGHT))
}
@@ -1,65 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
class ReleaseConfirmButton(
x: Int, y: Int,
private val parent: StorageWidget,
private val subKey: String,
onPress: PressAction
) : ButtonWidget(x, y, WIDTH, HEIGHT, Text.literal("ReleaseConfirm"), onPress) {
companion object {
private const val WIDTH = 30
private const val HEIGHT = 13
private val buttonResource = cobblemonResource("ui/pc/pc_release_button_confirm.png")
}
override fun render(matrices: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
if (parent.canDeleteSelected() && parent.displayConfirmRelease) {
blitk(
matrixStack = matrices,
texture = buttonResource,
x = x,
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(mouseX.toDouble(), mouseY.toDouble())) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
drawScaledText(
matrixStack = matrices,
font = CobblemonResources.DEFAULT_LARGE,
text = lang(subKey).bold(),
x = x + (WIDTH / 2),
y = y + 2,
centered = true,
shadow = true
)
}
}
override fun playDownSound(soundManager: SoundManager) {
}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in (x.toFloat()..(x.toFloat() + WIDTH)) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + HEIGHT))
}
@@ -1,128 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
import net.minecraft.util.math.Quaternion
import net.minecraft.util.math.Vec3f
open class StorageSlot(
x: Int, y: Int,
private val parent: StorageWidget,
onPress: PressAction
) : ButtonWidget(x, y, SIZE, SIZE, Text.literal("StorageSlot"), onPress) {
companion object {
const val SIZE = 25
private val genderIconMale = cobblemonResource("ui/pc/gender_icon_male.png")
private val genderIconFemale = cobblemonResource("ui/pc/gender_icon_female.png")
private val selectPointerResource = cobblemonResource("ui/pc/pc_pointer.png")
}
override fun playDownSound(soundManager: SoundManager) {
}
override fun render(matrices: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
if (shouldRender()) {
renderSlot(matrices, x, y)
}
}
fun renderSlot(matrices: MatrixStack, posX: Int, posY: Int) {
val pokemon = getPokemon() ?: return
val minecraft = MinecraftClient.getInstance()
RenderSystem.enableScissor(
((posX - 2) * minecraft.window.scaleFactor).toInt(),
(minecraft.window.height - (posY + SIZE + 2) * minecraft.window.scaleFactor).toInt(),
((SIZE + 4) * minecraft.window.scaleFactor).toInt(),
((SIZE + 4) * minecraft.window.scaleFactor).toInt()
)
// Render Pokémon
matrices.push()
matrices.translate(posX + (SIZE / 2.0), posY + 3.0, 0.0)
matrices.scale(2.5F, 2.5F, 1F)
drawProfilePokemon(
renderablePokemon = pokemon.asRenderablePokemon(),
matrixStack = matrices,
rotation = Quaternion.fromEulerXyzDegrees(Vec3f(13F, 35F, 0F)),
state = null,
scale = 4.5F
)
matrices.pop()
RenderSystem.disableScissor()
// Ensure labels are not hidden behind Pokémon render
matrices.push()
matrices.translate(0.0, 0.0, 100.0)
// Level
drawScaledText(
matrixStack = matrices,
text = lang("ui.lv.number", pokemon.level),
x = posX + 1,
y = posY + 1,
shadow = true,
scale = PCGUI.SCALE
)
if (pokemon.gender != Gender.GENDERLESS) {
blitk(
matrixStack = matrices,
texture = if (pokemon.gender == Gender.MALE) genderIconMale else genderIconFemale,
x = (posX + 21) / PCGUI.SCALE,
y = (posY + 1) / PCGUI.SCALE,
width = 6,
height = 8,
scale = PCGUI.SCALE
)
}
matrices.pop()
if (isSelected()) {
blitk(
matrixStack = matrices,
texture = selectPointerResource,
x = (posX + 10) / PCGUI.SCALE,
y = ((posY - 3) / PCGUI.SCALE) - parent.pcGui.selectPointerOffsetY,
width = 11,
height = 8,
scale = PCGUI.SCALE
)
}
}
open fun getPokemon(): Pokemon? {
return null
}
open fun isSelected(): Boolean {
return getPokemon() == parent.pcGui.previewPokemon
}
open fun shouldRender(): Boolean {
return true
}
fun isHovered(mouseX: Int, mouseY: Int) = mouseX.toFloat() in (x.toFloat()..(x.toFloat() + SIZE)) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + SIZE))
}
@@ -1,379 +0,0 @@
/*
* Copyright (C) 2022 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.storage.StorePosition
import com.cobblemon.mod.common.api.storage.party.PartyPosition
import com.cobblemon.mod.common.api.storage.pc.PCPosition
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.settings.ServerSettings
import com.cobblemon.mod.common.client.storage.ClientPC
import com.cobblemon.mod.common.client.storage.ClientParty
import com.cobblemon.mod.common.net.messages.server.storage.SwapPCPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.MovePartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.ReleasePartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.SwapPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonToPartyPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePartyPokemonToPCPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.ReleasePCPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.SwapPCPokemonPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.sound.SoundEvent
import net.minecraft.text.Text
class StorageWidget(
pX: Int, pY: Int,
val pcGui: PCGUI,
private val pc: ClientPC,
private val party: ClientParty
) : SoundlessWidget(pX, pY, WIDTH, HEIGHT, Text.literal("PCWidget")) {
companion object {
const val WIDTH = 263
const val HEIGHT = 155
const val BOX_SLOT_START_OFFSET_X = 7
const val BOX_SLOT_START_OFFSET_Y = 11
const val PARTY_SLOT_START_OFFSET_X = 193
const val PARTY_SLOT_START_OFFSET_Y = 8
const val BOX_SLOT_PADDING = 2
const val PARTY_SLOT_PADDING = 6
private val screenOverlayResource = cobblemonResource("ui/pc/pc_screen_overlay.png")
}
private val partySlots = arrayListOf<PartyStorageSlot>()
private val boxSlots = arrayListOf<BoxStorageSlot>()
private val releaseButton: ReleaseButton
private val releaseYesButton: ReleaseConfirmButton
private val releaseNoButton: ReleaseConfirmButton
var displayConfirmRelease = false
var screenLoaded = false
var selectedPosition: StorePosition? = null
var grabbedSlot: GrabbedStorageSlot? = null
var box = 0
set(value) {
// If value is within min and max
field = if (value > 0 && value < pc.boxes.size) value
// If value is less than zero, wrap around to end
else if (value < 0) pc.boxes.size - 1
// Else it's greater than max, wrap around to start
else 0
this.setupStorageSlots()
}
init {
this.setupStorageSlots()
this.releaseButton = ReleaseButton(
x = x + 194,
y = y + 124,
parent = this,
onPress = {
if (!displayConfirmRelease) {
displayConfirmRelease = true
playSound(CobblemonSounds.GUI_CLICK.get())
}
}
)
this.releaseYesButton = ReleaseConfirmButton(
x = x + 190,
y = y + 131,
parent = this,
subKey = "ui.generic.yes",
onPress = {
if (canDeleteSelected() && displayConfirmRelease) {
val position = selectedPosition ?: return@ReleaseConfirmButton
val pokemon = getSelectedPokemon() ?: return@ReleaseConfirmButton
val packet = when (position) {
is PartyPosition -> ReleasePartyPokemonPacket(pokemon.uuid, position)
is PCPosition -> ReleasePCPokemonPacket(pokemon.uuid, position)
else -> return@ReleaseConfirmButton
}
CobblemonNetwork.sendToServer(packet)
playSound(CobblemonSounds.PC_RELEASE.get())
resetSelected()
displayConfirmRelease = false
}
}
)
this.releaseNoButton = ReleaseConfirmButton(
x = x + 226,
y = y + 131,
parent = this,
subKey = "ui.generic.no",
onPress = {
if (displayConfirmRelease) {
displayConfirmRelease = false
playSound(CobblemonSounds.GUI_CLICK.get())
}
}
)
}
fun canDeleteSelected(): Boolean {
return !(selectedPosition is PartyPosition && party.filterNotNull().size <= 1)
&& selectedPosition != null
&& grabbedSlot != null
}
private fun setupStorageSlots() {
this.resetStorageSlots()
var index = 0
// Box Slots
val boxStartX = x + BOX_SLOT_START_OFFSET_X
val boxStartY = y + BOX_SLOT_START_OFFSET_Y
for (row in 1..5) {
for (col in 1..6) {
BoxStorageSlot(
x = boxStartX + ((col - 1) * (StorageSlot.SIZE + BOX_SLOT_PADDING)),
y = boxStartY + ((row - 1) * (StorageSlot.SIZE + BOX_SLOT_PADDING)),
parent = this,
pc = pc,
position = PCPosition(box, index),
onPress = { this.onStorageSlotClicked(it) }
).also { widget ->
this.addWidget(widget)
this.boxSlots.add(widget)
}
index++
}
}
// Party Slots
for (partyIndex in 0..5) {
var partyX = x + PARTY_SLOT_START_OFFSET_X
var partyY = y + PARTY_SLOT_START_OFFSET_Y
if (partyIndex > 0) {
val isEven = partyIndex % 2 == 0
val offsetIndex = (partyIndex - (if (isEven) 0 else 1)) / 2
val offsetX = if (isEven) 0 else (StorageSlot.SIZE + PARTY_SLOT_PADDING)
val offsetY = if (isEven) 0 else 8
partyX += offsetX
partyY += ((StorageSlot.SIZE + PARTY_SLOT_PADDING) * offsetIndex) + offsetY
}
PartyStorageSlot(
x = partyX,
y = partyY,
parent = this,
party = party,
position = PartyPosition(partyIndex),
onPress = { this.onStorageSlotClicked(it) }
).also { widget ->
this.addWidget(widget)
this.partySlots.add(widget)
}
}
}
private fun getSelectedPokemon(): Pokemon? {
val selectedPosition = this.selectedPosition ?: return null
return when (selectedPosition) {
is PCPosition -> pc.get(selectedPosition)
is PartyPosition -> party.get(selectedPosition)
else -> null
}
}
override fun render(matrices: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
// Party Label
drawScaledText(
matrixStack = matrices,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.party").bold(),
x = x + 213,
y = y - 15.5,
centered = true,
shadow = true
)
if (canDeleteSelected() && displayConfirmRelease) {
drawScaledText(
matrixStack = matrices,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.pc.release").bold(),
x = x + 223,
y = y + 119,
centered = true
)
}
this.releaseButton.render(matrices, mouseX, mouseY, delta)
this.releaseYesButton.render(matrices, mouseX, mouseY, delta)
this.releaseNoButton.render(matrices, mouseX, mouseY, delta)
// Screen Overlay
blitk(
matrixStack = matrices,
texture = screenOverlayResource,
x = x - 17,
y = y - 17,
width = 208,
height = 189,
alpha = if (screenLoaded) 1F else ((pcGui.ticksElapsed).toFloat() / 10F).coerceIn(0F, 1F)
)
if (screenLoaded) {
this.boxSlots.forEach { slot ->
slot.render(matrices, mouseX, mouseY, delta)
val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
&& pokemon != null && pokemon != pcGui.previewPokemon) pcGui.setPreviewPokemon(pokemon)
}
} else {
if (pcGui.ticksElapsed >= 10) screenLoaded = true
}
this.partySlots.forEach { slot ->
slot.render(matrices, mouseX, mouseY, delta)
val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
&& pokemon != null && pokemon != pcGui.previewPokemon) pcGui.setPreviewPokemon(pokemon)
}
grabbedSlot?.render(matrices, mouseX, mouseY, delta)
}
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (displayConfirmRelease) {
if (releaseYesButton.isHovered(pMouseX, pMouseY)) releaseYesButton.mouseClicked(pMouseX, pMouseY, pButton)
if (releaseNoButton.isHovered(pMouseX, pMouseY)) releaseNoButton.mouseClicked(pMouseX, pMouseY, pButton)
} else {
if (releaseButton.isHovered(pMouseX, pMouseY)) releaseButton.mouseClicked(pMouseX, pMouseY, pButton)
}
return super.mouseClicked(pMouseX, pMouseY, pButton)
}
private fun resetStorageSlots() {
this.partySlots.forEach(this::removeWidget)
this.partySlots.clear()
this.boxSlots.forEach(this::removeWidget)
this.boxSlots.clear()
}
private fun playSound(soundEvent: SoundEvent) {
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(soundEvent, 1.0F))
}
private fun onStorageSlotClicked(button: ButtonWidget) {
// Check if storage slot
val clickedPosition = when(button) {
is BoxStorageSlot -> button.position
is PartyStorageSlot -> button.position
else -> return
}
// Reset release confirmation
displayConfirmRelease = false
// Clicking on itself, so unselect position
if (this.selectedPosition != null && this.selectedPosition == clickedPosition) {
if (grabbedSlot != null) playSound(CobblemonSounds.PC_DROP.get())
resetSelected()
return
}
// Check for selecting a Pokémon
val clickedPokemon = when(button) {
is BoxStorageSlot -> pc.get(clickedPosition as PCPosition)
is PartyStorageSlot -> party.get(clickedPosition as PartyPosition)
else -> null
}
if (grabbedSlot == null) {
if (clickedPokemon != null) {
this.selectedPosition = clickedPosition
this.pcGui.setPreviewPokemon(clickedPokemon)
grabbedSlot = GrabbedStorageSlot(
x = button.x,
y = button.y,
parent = this,
pokemon = clickedPokemon
)
playSound(CobblemonSounds.PC_GRAB.get())
}
} else {
// Handle movement within the Box
val selectedPokemon = when(this.selectedPosition) {
is PCPosition -> pc.get(this.selectedPosition as PCPosition)
is PartyPosition -> party.get(this.selectedPosition as PartyPosition)
else -> null
} ?: return
// Box to Box
if (this.selectedPosition is PCPosition && clickedPosition is PCPosition) {
val packet = clickedPokemon?.let { SwapPCPokemonPacket(it.uuid, clickedPosition, selectedPokemon.uuid, this.selectedPosition as PCPosition) }
?: MovePCPokemonPacket(selectedPokemon.uuid, selectedPosition as PCPosition, clickedPosition)
packet.sendToServer()
playSound(CobblemonSounds.PC_DROP.get())
resetSelected()
}
// Box to Party
else if (this.selectedPosition is PCPosition && clickedPosition is PartyPosition) {
val packet = clickedPokemon?.let { SwapPCPartyPokemonPacket(clickedPokemon.uuid, clickedPosition, selectedPokemon.uuid, this.selectedPosition as PCPosition) }
?: MovePCPokemonToPartyPacket(selectedPokemon.uuid, this.selectedPosition as PCPosition, clickedPosition)
packet.sendToServer()
playSound(CobblemonSounds.PC_DROP.get())
resetSelected()
}
// Party to Box
else if (this.selectedPosition is PartyPosition && clickedPosition is PCPosition) {
if (ServerSettings.preventCompletePartyDeposit && this.party.filterNotNull().size == 1 && clickedPokemon == null) {
return
}
val packet = clickedPokemon?.let { SwapPCPartyPokemonPacket(selectedPokemon.uuid, this.selectedPosition as PartyPosition, clickedPokemon.uuid, clickedPosition) }
?: MovePartyPokemonToPCPacket(selectedPokemon.uuid, this.selectedPosition as PartyPosition, clickedPosition)
packet.sendToServer()
playSound(CobblemonSounds.PC_DROP.get())
resetSelected()
}
// Party to Party
else if (this.selectedPosition is PartyPosition && clickedPosition is PartyPosition) {
val packet = clickedPokemon?.let { SwapPartyPokemonPacket(it.uuid, clickedPosition, selectedPokemon.uuid, this.selectedPosition as PartyPosition) }
?: MovePartyPokemonPacket(selectedPokemon.uuid, selectedPosition as PartyPosition, clickedPosition)
packet.sendToServer()
playSound(CobblemonSounds.PC_DROP.get())
resetSelected()
}
}
}
private fun resetSelected() {
selectedPosition = null
grabbedSlot = null
pcGui.setPreviewPokemon(null)
}
}
@@ -6,53 +6,39 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui
package com.cobblemon.mod.common.client.gui.summary
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
class ExitButton(
pX: Int, pY: Int,
onPress: PressAction
): ButtonWidget(pX, pY, WIDTH.toInt(), HEIGHT.toInt(), Text.literal("Exit"), onPress) {
): ButtonWidget(pX, pY, (WIDTH * SCALE).toInt(), (HEIGHT * SCALE).toInt(), Text.literal(""), onPress) {
companion object {
private const val WIDTH = 26F
private const val HEIGHT = 13F
private const val WIDTH = 52F
private const val HEIGHT = 26F
private const val SCALE = 0.5F
private val buttonResource = cobblemonResource("ui/common/back_button.png")
private val iconResource = cobblemonResource("ui/common/back_button_icon.png")
private val exitButtonResource = cobblemonResource("ui/summary/summary_back_button.png")
}
override fun renderButton(pMatrixStack: MatrixStack, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
blitk(
matrixStack = pMatrixStack,
texture = buttonResource,
x = x,
y = y,
texture = exitButtonResource,
x = x * 2,
y = y * 2,
width = WIDTH,
height = HEIGHT,
vOffset = if (isHovered(pMouseX.toDouble(), pMouseY.toDouble())) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
blitk(
matrixStack = pMatrixStack,
texture = iconResource,
x = (x + 7) / SCALE,
y = (y + 4) / SCALE,
width = 21,
height = 11,
textureHeight = HEIGHT * 2,
scale = SCALE
)
}
override fun playDownSound(soundManager: SoundManager) {
}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in (x.toFloat()..(x.toFloat() + WIDTH)) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + HEIGHT))
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in (x.toFloat()..(x.toFloat() + (WIDTH * SCALE))) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + (HEIGHT * SCALE)))
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.client.gui.summary
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.moves.Move
import com.cobblemon.mod.common.api.moves.MoveSet
@@ -20,7 +19,6 @@ import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.widgets.EvolutionSelectScreen
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
@@ -28,7 +26,7 @@ import com.cobblemon.mod.common.client.gui.summary.widgets.PartyWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.SummaryTab
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.info.InfoWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MovesWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MoveSwapScreen
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.change.MoveSwapScreen
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.stats.StatWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.storage.ClientParty
@@ -45,9 +43,7 @@ import net.minecraft.client.gui.Element
import net.minecraft.client.gui.Selectable
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.gui.widget.ClickableWidget
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.sound.SoundEvent
import net.minecraft.text.Text
class Summary private constructor(): Screen(Text.translatable("cobblemon.ui.summary.title")) {
@@ -158,12 +154,7 @@ class Summary private constructor(): Screen(Text.translatable("cobblemon.ui.summ
pX = x + 78,
pY = y - 1,
label = lang("ui.info")
) {
if (mainScreenIndex != INFO) {
displayMainScreen(INFO)
playSound(CobblemonSounds.GUI_CLICK.get())
}
}
) { if (mainScreenIndex != INFO) displayMainScreen(INFO) }
)
summaryTabs.add(
@@ -171,12 +162,7 @@ class Summary private constructor(): Screen(Text.translatable("cobblemon.ui.summ
pX = x + 119,
pY = y - 1,
label = lang("ui.moves")
) {
if (mainScreenIndex != MOVES) {
displayMainScreen(MOVES)
playSound(CobblemonSounds.GUI_CLICK.get())
}
}
) { if (mainScreenIndex != MOVES) displayMainScreen(MOVES) }
)
summaryTabs.add(
@@ -184,12 +170,7 @@ class Summary private constructor(): Screen(Text.translatable("cobblemon.ui.summ
pX = x + 160,
pY = y - 1,
label = lang("ui.stats")
) {
if (mainScreenIndex != STATS) {
displayMainScreen(STATS)
playSound(CobblemonSounds.GUI_CLICK.get())
}
}
) { if (mainScreenIndex != STATS) displayMainScreen(STATS) }
)
summaryTabs[mainScreenIndex].toggleTab()
@@ -201,7 +182,6 @@ class Summary private constructor(): Screen(Text.translatable("cobblemon.ui.summ
pX = x + 302,
pY = y + 145
) {
playSound(CobblemonSounds.GUI_CLICK.get())
MinecraftClient.getInstance().setScreen(null)
}
)
@@ -532,8 +512,4 @@ class Summary private constructor(): Screen(Text.translatable("cobblemon.ui.summ
override fun mouseClicked(d: Double, e: Double, i: Int): Boolean {
return children().any { it.mouseClicked(d, e, i) }
}
fun playSound(soundEvent: SoundEvent) {
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(soundEvent, 1.0F))
}
}
@@ -8,14 +8,12 @@
package com.cobblemon.mod.common.client.gui.summary
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.render.drawScaledText
import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.MutableText
@@ -88,9 +86,9 @@ class SummaryButton(
return false
}
override fun playDownSound(soundManager: SoundManager) {
override fun playDownSound(soundManager: SoundManager?) {
if (!this.silent) {
soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK.get(), 1.0F))
super.playDownSound(soundManager)
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.client.gui.summary.widgets
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
@@ -198,7 +197,6 @@ class PartySlotWidget(
} else {
if ((index > -1) && (summary.selectedPokemon.uuid != pokemon?.uuid)) {
summary.switchSelection(index)
if (pokemon != null) partyWidget.playSound(CobblemonSounds.GUI_CLICK.get())
return true
}
}
@@ -215,7 +213,6 @@ class PartySlotWidget(
if (partyWidget.swapEnabled && partyWidget.isWithinScreen(mouseX, mouseY) && index < 0) {
repositionSlot(mouseX, mouseY)
} else {
if (partyWidget.swapSource != null) partyWidget.playSound(CobblemonSounds.PC_DROP.get())
toggleDrag(false)
partyWidget.swapSource = null
partyWidget.draggedSlot = null
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.client.gui.summary.widgets
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
@@ -18,11 +17,8 @@ import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.MinecraftClient
import net.minecraft.client.sound.PositionedSoundInstance
import java.security.InvalidParameterException
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.sound.SoundEvent
import net.minecraft.text.Text
class PartyWidget(
@@ -50,7 +46,7 @@ class PartyWidget(
private val partySize = partyList.size
private val partySlots = arrayListOf<PartySlotWidget>()
private val swapButton: SummaryButton = SummaryButton(
val swapButton: SummaryButton = SummaryButton(
buttonX = x + 80F,
buttonY = y - 9F,
buttonWidth = 26,
@@ -163,7 +159,6 @@ class PartyWidget(
index = -1,
isClientPartyMember = isParty
)
playSound(CobblemonSounds.PC_GRAB.get())
}
}
}
@@ -179,7 +174,6 @@ class PartyWidget(
}
swapSource = null
draggedSlot = null
playSound(CobblemonSounds.PC_DROP.get())
}
}
return super.mouseReleased(pMouseX, pMouseY, pButton)
@@ -211,10 +205,6 @@ class PartyWidget(
return -1
}
fun playSound(soundEvent: SoundEvent) {
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(soundEvent, 1.0F))
}
fun isWithinScreen(mouseX: Double, mouseY: Double): Boolean = mouseX.toInt() in x..(x + WIDTH)
&& mouseY.toInt() in y..(y + HEIGHT)
}
@@ -8,15 +8,12 @@
package com.cobblemon.mod.common.client.gui.summary.widgets.screens
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.MutableText
@@ -50,9 +47,6 @@ class SummaryTab(
)
}
override fun playDownSound(soundManager: SoundManager) {
}
fun toggleTab(state: Boolean = true) {
isActive = state
}
@@ -18,7 +18,6 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.MinecraftClient
@@ -148,7 +147,7 @@ class InfoWidget(
drawScaledText(
matrixStack = pMatrixStack,
font = CobblemonResources.DEFAULT_LARGE,
text = pokemon.nature.displayName.asTranslated().bold(),
text = pokemon.nature.displayName.bold(),
x = x + 53,
y = y + 66,
shadow = true
@@ -167,7 +166,7 @@ class InfoWidget(
drawScaledText(
matrixStack = pMatrixStack,
font = CobblemonResources.DEFAULT_LARGE,
text = pokemon.ability.displayName.asTranslated().bold(),
text = pokemon.ability.displayName.bold(),
x = x + 53,
y = y + 81,
shadow = true
@@ -178,7 +177,7 @@ class InfoWidget(
pMatrixStack.push()
pMatrixStack.scale(smallTextScale, smallTextScale, 1F)
MultiLineLabelK.create(
component = pokemon.ability.description.asTranslated(),
component = pokemon.ability.description,
width = 117 / smallTextScale,
maxLines = 3
).renderLeftAligned(
@@ -8,12 +8,9 @@
package com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
@@ -66,9 +63,5 @@ class ReorderMoveButton(
}
}
override fun playDownSound(soundManager: SoundManager) {
soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK.get(), 1.0F))
}
fun isHovered(mouseX: Double, mouseY: Double, offsetY: Float) = mouseX.toFloat() in ((pX - OFFSET_X)..((pX - OFFSET_X) + (WIDTH * SCALE))) && mouseY.toFloat() in ((pY + offsetY)..((pY + offsetY) + (HEIGHT * SCALE) - 0.5F))
}
@@ -8,13 +8,11 @@
package com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.change.MoveSwapScreen
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.sound.PositionedSoundInstance
import net.minecraft.client.sound.SoundManager
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.Text
@@ -59,9 +57,5 @@ class SwapMoveButton(
)
}
override fun playDownSound(soundManager: SoundManager) {
soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK.get(), 1.0F))
}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in ((pX + OFFSET_X)..((pX + OFFSET_X) + (WIDTH * SCALE))) && mouseY.toFloat() in ((pY + OFFSET_Y)..((pY + OFFSET_Y) + (HEIGHT * SCALE) - 0.5F))
}
@@ -6,10 +6,9 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves
package com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.change
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.moves.Move
import com.cobblemon.mod.common.api.moves.MoveTemplate
@@ -19,6 +18,7 @@ import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.widgets.common.SummaryScrollList
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MovesWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.BenchMovePacket
import com.cobblemon.mod.common.util.cobblemonResource
@@ -195,7 +195,6 @@ class MoveSwapScreen(
newMove = move
)
)
pane.movesWidget.summary.playSound(CobblemonSounds.GUI_CLICK.get())
pane.movesWidget.summary.displaySideScreen(Summary.PARTY)
return true
}
Loaded 100 of 2292 files, more files were not shown because too many files have changed in this diff. Show more