mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-26 17:16:25 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16007d3c1b | ||
|
|
ccee0065c2 | ||
|
|
30137a9308 | ||
|
|
c853644c16 | ||
|
|
794d1b391d | ||
|
|
3ac2687f1e | ||
|
|
511fd00652 | ||
|
|
405d3fd774 | ||
|
|
7a26d613fd | ||
|
|
0a02edf6f3 | ||
|
|
e8c7b31144 | ||
|
|
d6f6742658 | ||
|
|
c76dfe9e96 | ||
|
|
3ba88265eb | ||
|
|
dcc8975757 | ||
|
|
e824234057 | ||
|
|
0cc8bd6e0f | ||
|
|
1e45ff34f0 | ||
|
|
2532511d53 | ||
|
|
14f7c5ecc3 | ||
|
|
1ec6af6d34 | ||
|
|
e472ee5fbd | ||
|
|
7b4f43fd12 | ||
|
|
8a49b88c7a | ||
|
|
2138022f6d | ||
|
|
fd3bc667a9 | ||
|
|
4db5e54d88 | ||
|
|
62748437de | ||
|
|
163b2df7f6 | ||
|
|
386bad2c1c | ||
|
|
d304028389 | ||
|
|
964894f376 | ||
|
|
ec8dfd746d | ||
|
|
732547213a | ||
|
|
df888a7f36 | ||
|
|
bd01b9d682 | ||
|
|
ab390703da | ||
|
|
28cd8f0265 | ||
|
|
ca47636ffe | ||
|
|
a852b8e535 | ||
|
|
fa6f52d305 | ||
|
|
3e8d8daa9b | ||
|
|
8196a686df | ||
|
|
da157a1b28 | ||
|
|
167191a135 | ||
|
|
e6f881df6e | ||
|
|
53f82b1971 | ||
|
|
9f775f2c92 | ||
|
|
07f89dc691 | ||
|
|
0c70a5cbac | ||
|
|
3ceda765c9 | ||
|
|
e3360e2d18 | ||
|
|
b4e2ecc8f1 | ||
|
|
bdd6ea48a0 | ||
|
|
84244c0376 | ||
|
|
6fec1417b1 | ||
|
|
142c47de1c | ||
|
|
e796883660 | ||
|
|
c6779385a7 | ||
|
|
1a0ea5db72 | ||
|
|
342a1ceb6d | ||
|
|
0e7d3438a5 | ||
|
|
cadcdab39b | ||
|
|
0bc7614809 | ||
|
|
ecbde40aa6 | ||
|
|
672ed74309 | ||
|
|
469e9e9065 | ||
|
|
2dba103c14 | ||
|
|
be7c0220d4 | ||
|
|
14f9640556 | ||
|
|
3b2940ac59 | ||
|
|
036d9c5e6b | ||
|
|
76cdecbb94 | ||
|
|
1d6317b5d6 | ||
|
|
26f963f9b9 | ||
|
|
9ee90508ed | ||
|
|
9b6da631ee | ||
|
|
c26edc55f5 | ||
|
|
f1c306051c | ||
|
|
dacb16044d | ||
|
|
68478f84d8 | ||
|
|
6272bfe2d6 | ||
|
|
e561bfd148 | ||
|
|
2f9cc2b41c | ||
|
|
8d39e2536b | ||
|
|
17fa80e752 | ||
|
|
4d2641308a | ||
|
|
27e02963eb | ||
|
|
d71c03e48a | ||
|
|
43b69ba860 | ||
|
|
64bfb72c53 | ||
|
|
6810232183 | ||
|
|
6e25c2591e | ||
|
|
0be43ec28c | ||
|
|
78f0f572b2 | ||
|
|
6d99c7e643 | ||
|
|
c6f4fc8992 | ||
|
|
320c471b4e | ||
|
|
0ea8d8932e | ||
|
|
7325f4b6ac | ||
|
|
4fd035aa16 | ||
|
|
5f1038d7a4 | ||
|
|
6449cb7295 | ||
|
|
1fa0e81b8e | ||
|
|
b243a0a098 | ||
|
|
139253454c | ||
|
|
7224ac123f | ||
|
|
c53018942b | ||
|
|
6bf09dbd2a | ||
|
|
010f9b16f9 | ||
|
|
45eaf6fb3b | ||
|
|
8e4141ccc1 | ||
|
|
41c6accc16 | ||
|
|
470f0d37a4 | ||
|
|
f7bd3af13b | ||
|
|
f4f7c0f19a | ||
|
|
7ff0a66aae | ||
|
|
0487d431e1 | ||
|
|
5429f70a26 | ||
|
|
aa8d0d80f1 | ||
|
|
9547be150b | ||
|
|
346e3a3047 | ||
|
|
594bf59496 | ||
|
|
7f6fa2f009 | ||
|
|
b7720248ad | ||
|
|
60593c7cdf | ||
|
|
61e545ace3 | ||
|
|
045398d204 | ||
|
|
c4e318cb1c | ||
|
|
048bae7585 | ||
|
|
fee858e5dc | ||
|
|
f938e99036 | ||
|
|
02ecc87608 | ||
|
|
5a0d46410a | ||
|
|
e516b6dea2 | ||
|
|
3e3a2a7ec0 | ||
|
|
bd1f7c59c3 | ||
|
|
1fdf928b7b | ||
|
|
ee863a4e04 | ||
|
|
5848e7b21c | ||
|
|
2a2c0da200 |
+5
-1
@@ -28,4 +28,8 @@ forge*changelog.txt
|
||||
/logs/
|
||||
.vscode/launch.json
|
||||
.vscode
|
||||
common/src/main/resources/assets/cobblemon/showdown
|
||||
common/src/main/resources/assets/cobblemon/showdown
|
||||
|
||||
# Generated files
|
||||
*/generated/resources/*
|
||||
/fabric/src/generated/resources/cobblemon-common.accesswidener
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ stages:
|
||||
build-job:
|
||||
stage: build
|
||||
script:
|
||||
- export GRADLE_OPTS="-Xmx3G -Xms3G"
|
||||
- export GRADLE_OPTS="-Xmx4G -Xms4G"
|
||||
- chmod +x ./gradlew
|
||||
- ./gradlew build
|
||||
cache:
|
||||
|
||||
+56
-6
@@ -1,14 +1,63 @@
|
||||
# Changelog
|
||||
|
||||
## 1.1.1
|
||||
## [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
|
||||
## [1.1.0 - The Swim and Sleep Update (November 27th, 2022)](#1-1-0)
|
||||
#### "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.
|
||||
@@ -62,6 +111,7 @@
|
||||
- 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 (2022-11-12)
|
||||
- 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)
|
||||
- Initial release.
|
||||
@@ -19,6 +19,7 @@ tasks {
|
||||
archiveClassifier.set("dev-shadow")
|
||||
archiveBaseName.set("Cobblemon-${project.name}")
|
||||
configurations = listOf(bundle)
|
||||
mergeServiceFiles()
|
||||
}
|
||||
|
||||
remapJar {
|
||||
|
||||
@@ -21,8 +21,7 @@ dependencies {
|
||||
modApi(libs.mclib)
|
||||
|
||||
// For Showdown
|
||||
modCompileOnly(libs.javet) // Linux or Windows
|
||||
modCompileOnly(libs.javetMac) // Mac OS (x86_64 Only)
|
||||
modCompileOnly(libs.graal)
|
||||
|
||||
//shadowCommon group: 'commons-io', name: 'commons-io', version: '2.6'
|
||||
|
||||
@@ -33,11 +32,6 @@ 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,9 +35,10 @@ 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.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.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.stats.EvCalculator
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Generation8EvCalculator
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.StatProvider
|
||||
@@ -47,7 +48,6 @@ 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,7 +67,6 @@ 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
|
||||
@@ -79,21 +78,12 @@ 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.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.feature.TagSeasonResolver
|
||||
import com.cobblemon.mod.common.pokemon.properties.HiddenAbilityPropertyType
|
||||
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
|
||||
import com.cobblemon.mod.common.pokemon.properties.UntradeableProperty
|
||||
@@ -101,7 +91,6 @@ 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
|
||||
@@ -132,12 +121,11 @@ import org.apache.logging.log4j.LogManager
|
||||
|
||||
object Cobblemon {
|
||||
const val MODID = "cobblemon"
|
||||
const val VERSION = "1.1.0"
|
||||
const val VERSION = "1.2.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
|
||||
@@ -162,7 +150,6 @@ object Cobblemon {
|
||||
DropEntry.register("item", ItemDropEntry::class.java, isDefault = true)
|
||||
|
||||
ExperienceGroups.registerDefaults()
|
||||
PokemonSpecies.observable.subscribe { CobblemonSpawnPools.load() }
|
||||
|
||||
this.loadConfig()
|
||||
this.implementation = implementation
|
||||
@@ -225,21 +212,12 @@ object Cobblemon {
|
||||
|
||||
SHINY_ASPECT.register()
|
||||
GENDER_ASPECT.register()
|
||||
SnakePatternAspect.register()
|
||||
FishStripesAspect.register()
|
||||
SeasonAspect.register()
|
||||
|
||||
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)
|
||||
SpeciesFeatures.types["choice"] = ChoiceSpeciesFeatureProvider::class.java
|
||||
SpeciesFeatures.types["flag"] = FlagSpeciesFeatureProvider::class.java
|
||||
|
||||
GlobalSpeciesFeatures.register(DamageTakenFeature.ID) { DamageTakenFeature() }
|
||||
GlobalSpeciesFeatures.register(BattleCriticalHitsFeature.ID) { BattleCriticalHitsFeature() }
|
||||
|
||||
CustomPokemonProperty.register(UntradeableProperty)
|
||||
CustomPokemonProperty.register(UncatchableProperty)
|
||||
@@ -280,7 +258,6 @@ object Cobblemon {
|
||||
SERVER_STOPPED.subscribe {
|
||||
storage.unregisterAll()
|
||||
playerData.saveAll()
|
||||
showdown.close()
|
||||
}
|
||||
SERVER_STARTED.subscribe {
|
||||
bestSpawner.onServerStarted()
|
||||
@@ -370,19 +347,23 @@ object Cobblemon {
|
||||
}
|
||||
|
||||
fun loadStarterConfig(): StarterConfig {
|
||||
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()
|
||||
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()
|
||||
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,15 +32,8 @@ object CobblemonEntities : CompletableRegistry<EntityType<*>>(Registry.ENTITY_TY
|
||||
EntityAttributeRegistry.register(
|
||||
{ POKEMON.get() },
|
||||
{
|
||||
DefaultAttributeContainer
|
||||
.builder()
|
||||
LivingEntity.createLivingAttributes()
|
||||
.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,6 +52,7 @@ 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
|
||||
@@ -207,6 +208,9 @@ object CobblemonNetwork {
|
||||
buildClientMessage<SpeciesRegistrySyncPacket>()
|
||||
buildClientMessage<PropertiesCompletionRegistrySyncPacket>()
|
||||
|
||||
// Hax
|
||||
buildClientMessage<UnvalidatedPlaySoundS2CPacket>()
|
||||
|
||||
/**
|
||||
* Server Packets
|
||||
*/
|
||||
|
||||
@@ -16,6 +16,14 @@ 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")
|
||||
@@ -23,5 +31,6 @@ 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,7 +22,6 @@ 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
|
||||
@@ -83,6 +82,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)!!
|
||||
fun getOrException(name: String) = get(name) ?: throw IllegalArgumentException("Unable to find ability of name: $name")
|
||||
fun count() = allAbilities.size
|
||||
}
|
||||
@@ -21,27 +21,35 @@ import net.minecraft.text.MutableText
|
||||
* @author Qu
|
||||
* @since January 9th, 2022
|
||||
*/
|
||||
open class Ability internal constructor(var template: AbilityTemplate) {
|
||||
open class Ability internal constructor(var template: AbilityTemplate, var forced: Boolean) {
|
||||
|
||||
val name: String
|
||||
get() = template.name
|
||||
|
||||
val displayName: MutableText
|
||||
val displayName: String
|
||||
get() = template.displayName
|
||||
|
||||
val description: MutableText
|
||||
val description: String
|
||||
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) = this
|
||||
open fun loadFromJSON(json: JsonObject) = this
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,8 @@
|
||||
|
||||
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.
|
||||
@@ -21,14 +19,14 @@ import net.minecraft.text.MutableText
|
||||
*/
|
||||
class AbilityTemplate(
|
||||
val name: String = "",
|
||||
var builder: (AbilityTemplate) -> Ability = { Ability(it) },
|
||||
val displayName: MutableText = lang("ability.$name"),
|
||||
val description: MutableText = lang("ability.$name.desc")
|
||||
var builder: (AbilityTemplate, forced: Boolean) -> Ability = { template, forced -> Ability(template, forced) },
|
||||
val displayName: String = "cobblemon.ability.$name",
|
||||
val description: String = "cobblemon.ability.$name.desc"
|
||||
) {
|
||||
/**
|
||||
* Returns the Ability or if applicable the extension connected to this template
|
||||
*/
|
||||
fun create() = builder(this)
|
||||
fun create(forced: Boolean = false) = builder(this, forced)
|
||||
|
||||
|
||||
/**
|
||||
@@ -36,12 +34,12 @@ class AbilityTemplate(
|
||||
*
|
||||
* Ability extensions need to write and read their needed data from here.
|
||||
*/
|
||||
fun create(nbt: NbtCompound) = builder(this).loadFromNBT(nbt)
|
||||
fun create(nbt: NbtCompound) = create().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) = builder(this).loadFromJSON(json)
|
||||
fun create(json: JsonObject) = create().loadFromJSON(json)
|
||||
}
|
||||
@@ -10,7 +10,6 @@ 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
|
||||
@@ -26,13 +25,11 @@ 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
|
||||
@@ -50,6 +47,7 @@ open class PokemonBattle(
|
||||
) {
|
||||
/** Whether or not logging will be silenced for this battle. */
|
||||
var mute = true
|
||||
|
||||
init {
|
||||
side1.battle = this
|
||||
side2.battle = this
|
||||
@@ -148,17 +146,8 @@ open class PokemonBattle(
|
||||
}
|
||||
|
||||
fun writeShowdownAction(vararg messages: String) {
|
||||
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)
|
||||
log(messages.joinToString("\n"))
|
||||
GraalShowdown.sendToShowdown(battleId, messages.toList().toTypedArray())
|
||||
}
|
||||
|
||||
fun turn(newTurnNumber: Int) {
|
||||
|
||||
@@ -25,7 +25,7 @@ interface DataProvider {
|
||||
*
|
||||
* @param registry The [DataRegistry] being registered.
|
||||
*/
|
||||
fun register(registry: DataRegistry)
|
||||
fun <T : DataRegistry> register(registry: T): T
|
||||
|
||||
/**
|
||||
* Attempts to find a [DataRegistry] with the given [Identifier].
|
||||
|
||||
@@ -9,8 +9,11 @@
|
||||
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
|
||||
@@ -30,4 +33,17 @@ 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"), lang("nature.hardy"),
|
||||
Nature(cobblemonResource("hardy"), "cobblemon.nature.hardy",
|
||||
null, null, null, null)
|
||||
)
|
||||
|
||||
val LONELY = registerNature(
|
||||
Nature(cobblemonResource("lonely"), lang("nature.lonely"),
|
||||
Nature(cobblemonResource("lonely"), "cobblemon.nature.lonely",
|
||||
Stats.ATTACK, Stats.DEFENCE, Flavor.SPICY, Flavor.SOUR)
|
||||
)
|
||||
|
||||
val BRAVE = registerNature(
|
||||
Nature(cobblemonResource("brave"), lang("nature.brave"),
|
||||
Nature(cobblemonResource("brave"), "cobblemon.nature.brave",
|
||||
Stats.ATTACK, Stats.SPEED, Flavor.SPICY, Flavor.SWEET)
|
||||
)
|
||||
|
||||
val ADAMANT = registerNature(
|
||||
Nature(cobblemonResource("adamant"), lang("nature.adamant"),
|
||||
Nature(cobblemonResource("adamant"), "cobblemon.nature.adamant",
|
||||
Stats.ATTACK, Stats.SPECIAL_ATTACK, Flavor.SPICY, Flavor.DRY)
|
||||
)
|
||||
|
||||
val NAUGHTY = registerNature(
|
||||
Nature(cobblemonResource("naughty"), lang("nature.naughty"),
|
||||
Nature(cobblemonResource("naughty"), "cobblemon.nature.naughty",
|
||||
Stats.ATTACK, Stats.SPECIAL_DEFENCE, Flavor.SPICY, Flavor.BITTER)
|
||||
)
|
||||
|
||||
val BOLD = registerNature(
|
||||
Nature(cobblemonResource("bold"), lang("nature.bold"),
|
||||
Nature(cobblemonResource("bold"), "cobblemon.nature.bold",
|
||||
Stats.DEFENCE, Stats.ATTACK, Flavor.SOUR, Flavor.SPICY)
|
||||
)
|
||||
|
||||
val DOCILE = registerNature(
|
||||
Nature(cobblemonResource("docile"), lang("nature.docile"),
|
||||
Nature(cobblemonResource("docile"), "cobblemon.nature.docile",
|
||||
null, null, null, null)
|
||||
)
|
||||
|
||||
val RELAXED = registerNature(
|
||||
Nature(cobblemonResource("relaxed"), lang("nature.relaxed"),
|
||||
Nature(cobblemonResource("relaxed"), "cobblemon.nature.relaxed",
|
||||
Stats.DEFENCE, Stats.SPEED, Flavor.SOUR, Flavor.SWEET)
|
||||
)
|
||||
|
||||
val IMPISH = registerNature(
|
||||
Nature(cobblemonResource("impish"), lang("nature.impish"),
|
||||
Nature(cobblemonResource("impish"), "cobblemon.nature.impish",
|
||||
Stats.DEFENCE, Stats.SPECIAL_ATTACK, Flavor.SOUR, Flavor.DRY)
|
||||
)
|
||||
|
||||
val LAX = registerNature(
|
||||
Nature(cobblemonResource("lax"), lang("nature.lax"),
|
||||
Nature(cobblemonResource("lax"), "cobblemon.nature.lax",
|
||||
Stats.DEFENCE, Stats.SPECIAL_DEFENCE, Flavor.SOUR, Flavor.BITTER)
|
||||
)
|
||||
|
||||
val TIMID = registerNature(
|
||||
Nature(cobblemonResource("timid"), lang("nature.timid"),
|
||||
Nature(cobblemonResource("timid"), "cobblemon.nature.timid",
|
||||
Stats.SPEED, Stats.ATTACK, Flavor.SWEET, Flavor.SPICY)
|
||||
)
|
||||
|
||||
val HASTY = registerNature(
|
||||
Nature(cobblemonResource("hasty"), lang("nature.hasty"),
|
||||
Nature(cobblemonResource("hasty"), "cobblemon.nature.hasty",
|
||||
Stats.SPEED, Stats.DEFENCE, Flavor.SWEET, Flavor.SOUR)
|
||||
)
|
||||
|
||||
val SERIOUS = registerNature(
|
||||
Nature(cobblemonResource("serious"), lang("nature.serious"),
|
||||
Nature(cobblemonResource("serious"), "cobblemon.nature.serious",
|
||||
null, null, null, null)
|
||||
)
|
||||
|
||||
val JOLLY = registerNature(
|
||||
Nature(cobblemonResource("jolly"), lang("nature.jolly"),
|
||||
Nature(cobblemonResource("jolly"), "cobblemon.nature.jolly",
|
||||
Stats.SPEED, Stats.SPECIAL_ATTACK, Flavor.SWEET, Flavor.DRY)
|
||||
)
|
||||
|
||||
val NAIVE = registerNature(
|
||||
Nature(cobblemonResource("naive"), lang("nature.naive"),
|
||||
Nature(cobblemonResource("naive"), "cobblemon.nature.naive",
|
||||
Stats.SPEED, Stats.SPECIAL_DEFENCE, Flavor.SWEET, Flavor.BITTER)
|
||||
)
|
||||
|
||||
val MODEST = registerNature(
|
||||
Nature(cobblemonResource("modest"), lang("nature.modest"),
|
||||
Nature(cobblemonResource("modest"), "cobblemon.nature.modest",
|
||||
Stats.SPECIAL_ATTACK, Stats.ATTACK, null, null)
|
||||
)
|
||||
|
||||
val MILD = registerNature(
|
||||
Nature(cobblemonResource("mild"), lang("nature.mild"),
|
||||
Nature(cobblemonResource("mild"), "cobblemon.nature.mild",
|
||||
Stats.SPECIAL_ATTACK, Stats.DEFENCE, Flavor.DRY, Flavor.SOUR)
|
||||
)
|
||||
|
||||
val QUIET = registerNature(
|
||||
Nature(cobblemonResource("quiet"), lang("nature.quiet"),
|
||||
Nature(cobblemonResource("quiet"), "cobblemon.nature.quiet",
|
||||
Stats.SPECIAL_ATTACK, Stats.SPEED, Flavor.DRY, Flavor.SWEET)
|
||||
)
|
||||
|
||||
val BASHFUL = registerNature(
|
||||
Nature(cobblemonResource("bashful"), lang("nature.bashful"),
|
||||
Nature(cobblemonResource("bashful"), "cobblemon.nature.bashful",
|
||||
null, null, null, null)
|
||||
)
|
||||
|
||||
val RASH = registerNature(
|
||||
Nature(cobblemonResource("rash"), lang("nature.rash"),
|
||||
Nature(cobblemonResource("rash"), "cobblemon.nature.rash",
|
||||
Stats.SPECIAL_ATTACK, Stats.SPECIAL_DEFENCE, Flavor.DRY, Flavor.BITTER)
|
||||
)
|
||||
|
||||
val CALM = registerNature(
|
||||
Nature(cobblemonResource("calm"), lang("nature.calm"),
|
||||
Nature(cobblemonResource("calm"), "cobblemon.nature.calm",
|
||||
Stats.SPECIAL_DEFENCE, Stats.ATTACK, Flavor.BITTER, Flavor.SPICY)
|
||||
)
|
||||
|
||||
val GENTLE = registerNature(
|
||||
Nature(cobblemonResource("gentle"), lang("nature.gentle"),
|
||||
Nature(cobblemonResource("gentle"), "cobblemon.nature.gentle",
|
||||
Stats.SPECIAL_DEFENCE, Stats.DEFENCE, Flavor.BITTER, Flavor.SOUR)
|
||||
)
|
||||
|
||||
val SASSY = registerNature(
|
||||
Nature(cobblemonResource("sassy"), lang("nature.sassy"),
|
||||
Nature(cobblemonResource("sassy"), "cobblemon.nature.sassy",
|
||||
Stats.SPECIAL_DEFENCE, Stats.SPEED, Flavor.BITTER, Flavor.SWEET)
|
||||
)
|
||||
|
||||
val CAREFUL = registerNature(
|
||||
Nature(cobblemonResource("careful"), lang("nature.careful"),
|
||||
Nature(cobblemonResource("careful"), "cobblemon.nature.careful",
|
||||
Stats.SPECIAL_DEFENCE, Stats.SPECIAL_ATTACK, Flavor.BITTER, Flavor.DRY)
|
||||
)
|
||||
|
||||
val QUIRKY = registerNature(
|
||||
Nature(cobblemonResource("quirky"), lang("nature.quirky"),
|
||||
Nature(cobblemonResource("quirky"), "cobblemon.nature.quirky",
|
||||
null, null, null, null)
|
||||
)
|
||||
|
||||
@@ -174,4 +174,7 @@ object Natures {
|
||||
fun getRandomNature(): Nature {
|
||||
return allNatures.random()
|
||||
}
|
||||
|
||||
fun all(): Collection<Nature> = this.allNatures.toList()
|
||||
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
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
|
||||
@@ -25,6 +26,7 @@ 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
|
||||
|
||||
@@ -74,6 +76,8 @@ 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
|
||||
}
|
||||
@@ -92,6 +96,17 @@ 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) {
|
||||
@@ -186,6 +201,8 @@ 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>()
|
||||
@@ -218,6 +235,8 @@ 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) }
|
||||
}
|
||||
|
||||
@@ -238,6 +257,8 @@ 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) }
|
||||
}
|
||||
|
||||
@@ -261,6 +282,8 @@ 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) }
|
||||
}
|
||||
|
||||
@@ -282,11 +305,20 @@ 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 {
|
||||
return Pokemon().also { apply(it) }.also { if (it.moveSet.none { it != null }) it.initializeMoveset() }
|
||||
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
|
||||
}
|
||||
|
||||
fun createEntity(world: World): PokemonEntity {
|
||||
@@ -302,6 +334,8 @@ 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)
|
||||
@@ -316,6 +350,8 @@ 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) }
|
||||
@@ -332,9 +368,12 @@ 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
|
||||
}
|
||||
|
||||
@@ -346,6 +385,8 @@ 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) }
|
||||
@@ -361,6 +402,8 @@ 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,8 +8,6 @@
|
||||
|
||||
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
|
||||
@@ -32,10 +30,11 @@ 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.condition.TimeRange
|
||||
import com.cobblemon.mod.common.api.spawning.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
|
||||
@@ -51,11 +50,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
|
||||
@@ -64,7 +63,6 @@ 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
|
||||
@@ -99,7 +97,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
|
||||
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
|
||||
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
|
||||
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(TimeRange::class.java, TimeRangeAdapter)
|
||||
.registerTypeAdapter(TimeRange::class.java, IntRangesAdapter(TimeRange.timeRanges) { TimeRange(*it) })
|
||||
.registerTypeAdapter(ItemDropMethod::class.java, ItemDropMethod.adapter)
|
||||
.registerTypeAdapter(SleepDepth::class.java, SleepDepth.adapter)
|
||||
.registerTypeAdapter(DropEntry::class.java, DropEntryAdapter)
|
||||
@@ -218,45 +216,40 @@ object PokemonSpecies : JsonDataRegistry<Species> {
|
||||
this.speciesByDex.remove(species.resourceIdentifier.namespace, species.nationalPokedexNumber)
|
||||
}
|
||||
}
|
||||
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(
|
||||
|
||||
|
||||
// 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(
|
||||
"""
|
||||
const PokemonShowdown = require('pokemon-showdown');
|
||||
PokemonShowdown.Dex.modsLoaded = false;
|
||||
PokemonShowdown.Dex.includeMods();
|
||||
""".trimIndent()
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", {value: true});
|
||||
const Pokedex = {
|
||||
$pokedexDataHolder
|
||||
};
|
||||
exports.Pokedex = Pokedex;
|
||||
""".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")
|
||||
}
|
||||
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
-66
@@ -1,66 +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 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
|
||||
}
|
||||
-36
@@ -1,36 +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.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() }
|
||||
}
|
||||
}
|
||||
+76
-19
@@ -8,18 +8,19 @@
|
||||
|
||||
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]
|
||||
* to provide a convenient means of registering it with a [CustomPokemonPropertyType]. That can be done
|
||||
* smoothly using [FlagSpeciesFeature.registerWithProperty].
|
||||
* A simple [SpeciesFeature] that is a true/false flag value. It implements [CustomPokemonProperty] for use in
|
||||
* [PokemonProperties]. [FlagSpeciesFeatureProvider]s must be registered within [SpeciesFeatures].
|
||||
*
|
||||
* Implementations of this class don't need to implement anything.
|
||||
*
|
||||
@@ -27,21 +28,11 @@ import net.minecraft.nbt.NbtCompound
|
||||
* @since May 13th, 2022
|
||||
*/
|
||||
open class FlagSpeciesFeature(override val name: String) : SpeciesFeature, CustomPokemonProperty {
|
||||
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))
|
||||
}
|
||||
constructor(name: String, enabled: Boolean): this(name) {
|
||||
this.enabled = enabled
|
||||
}
|
||||
|
||||
open var enabled = false
|
||||
|
||||
var enabled = false
|
||||
override fun saveToNBT(pokemonNBT: NbtCompound): NbtCompound {
|
||||
pokemonNBT.putBoolean(name, enabled)
|
||||
return pokemonNBT
|
||||
@@ -66,9 +57,75 @@ open class FlagSpeciesFeature(override val name: String) : SpeciesFeature, Custo
|
||||
override fun asString() = "$name=$enabled"
|
||||
|
||||
override fun apply(pokemon: Pokemon) {
|
||||
pokemon.getFeature<FlagSpeciesFeature>(name)?.enabled = enabled
|
||||
pokemon.updateAspects()
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
-37
@@ -1,37 +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.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")
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
+3
-32
@@ -16,8 +16,9 @@ import net.minecraft.nbt.NbtCompound
|
||||
|
||||
/**
|
||||
* A piece of state that can be added to some species of Pokémon. Registering an implementing class
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
@@ -27,36 +28,6 @@ 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
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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?
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
+10
-11
@@ -37,6 +37,12 @@ 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.
|
||||
@@ -93,12 +99,6 @@ 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,13 +158,12 @@ object CobblemonPokemonLabels {
|
||||
const val GENERATION_8 = "gen8"
|
||||
|
||||
/**
|
||||
* Official Pokémon changed by a datapack, there is no guarantee authors will adhere to this principle
|
||||
* Official Pokémon changed by a data pack, there is no guarantee authors will adhere to this principle
|
||||
*/
|
||||
const val CUSTOMIZED_OFFICIAL = "customized_official"
|
||||
|
||||
/**
|
||||
* Unofficial Pokémon created by a datapack, there is no guarantee authors will adhere to this principle
|
||||
* Unofficial Pokémon created by a data pack, there is no guarantee authors will adhere to this principle
|
||||
*/
|
||||
const val FAKEMON = "fakemon"
|
||||
|
||||
}
|
||||
const val CUSTOM = "custom"
|
||||
}
|
||||
|
||||
+4
@@ -48,6 +48,10 @@ 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,17 +15,20 @@ 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
|
||||
@@ -33,7 +36,6 @@ 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
|
||||
@@ -79,7 +81,6 @@ 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 })
|
||||
|
||||
@@ -89,17 +90,20 @@ 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.")
|
||||
|
||||
@@ -108,10 +112,8 @@ object BestSpawner {
|
||||
|
||||
config = BestSpawnerConfig.load()
|
||||
|
||||
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.")
|
||||
SpawnDetailPresets.registerPresetType(BasicSpawnDetailPreset.NAME, BasicSpawnDetailPreset::class.java)
|
||||
SpawnDetailPresets.registerPresetType(PokemonSpawnDetailPreset.NAME, PokemonSpawnDetailPreset::class.java)
|
||||
}
|
||||
|
||||
fun onServerStarted() {
|
||||
|
||||
@@ -11,6 +11,7 @@ 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
|
||||
@@ -25,6 +26,6 @@ object CobblemonSpawnPools {
|
||||
lateinit var WORLD_SPAWN_POOL: SpawnPool
|
||||
|
||||
fun load() {
|
||||
WORLD_SPAWN_POOL = SpawnLoader.load("world").addPrecalculators(ContextPrecalculation, BucketPrecalculation)
|
||||
WORLD_SPAWN_POOL = CobblemonDataProvider.register(SpawnPool("world").addPrecalculators(ContextPrecalculation, BucketPrecalculation))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 }
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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,46 +8,30 @@
|
||||
|
||||
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
|
||||
@@ -66,12 +50,14 @@ 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, TimeRangeAdapter)
|
||||
.registerTypeAdapter(TimeRange::class.java, IntRangesAdapter(TimeRange.timeRanges) { TimeRange(*it) })
|
||||
.registerTypeAdapter(MoonPhaseRange::class.java, IntRangesAdapter(MoonPhaseRange.moonPhaseRanges) { MoonPhaseRange(*it) })
|
||||
.registerTypeAdapter(ItemDropMethod::class.java, ItemDropMethod.adapter)
|
||||
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
|
||||
.registerTypeAdapter(SpawnBucket::class.java, SpawnBucketAdapter)
|
||||
@@ -80,146 +66,4 @@ 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,14 +20,11 @@ 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()
|
||||
|
||||
+10
-13
@@ -6,10 +6,12 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.spawning.condition
|
||||
package com.cobblemon.mod.common.api.spawning
|
||||
|
||||
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
|
||||
|
||||
/**
|
||||
* A range of time ticks for a world to be used in [SpawningCondition]s. A time range
|
||||
* A range of time ticks for a world mainly 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
|
||||
@@ -19,14 +21,14 @@ package com.cobblemon.mod.common.api.spawning.condition
|
||||
* your definitions.
|
||||
*
|
||||
* If you want a time range to be referenced by name in [SpawningCondition]s, you need
|
||||
* to register it, by name, in [ranges].
|
||||
* to register it, by name, in [timeRanges].
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since January 26th, 2022
|
||||
*/
|
||||
class TimeRange() {
|
||||
class TimeRange : IntRanges {
|
||||
companion object {
|
||||
val ranges = mutableMapOf(
|
||||
val timeRanges = mutableMapOf(
|
||||
"any" to TimeRange(0..23999),
|
||||
"day" to TimeRange(23460..23999, 0..12541),
|
||||
"night" to TimeRange(12542..23459),
|
||||
@@ -40,11 +42,6 @@ class TimeRange() {
|
||||
)
|
||||
}
|
||||
|
||||
constructor(vararg ranges: IntRange) : this() {
|
||||
this.ranges = ranges.toMutableList()
|
||||
}
|
||||
|
||||
var ranges = mutableListOf<IntRange>()
|
||||
|
||||
operator fun contains(timeTicks: Int) = ranges.any { timeTicks in it }
|
||||
}
|
||||
constructor() : super()
|
||||
constructor(vararg ranges: IntRange) : super(*ranges)
|
||||
}
|
||||
+4
-2
@@ -9,6 +9,8 @@
|
||||
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
|
||||
@@ -37,7 +39,7 @@ abstract class SpawningCondition<T : SpawningContext> {
|
||||
|
||||
var dimensions: MutableList<Identifier>? = null
|
||||
var biomes: MutableSet<RegistryLikeCondition<Biome>>? = null
|
||||
var moonPhase: Int? = null
|
||||
var moonPhase: MoonPhaseRange? = null
|
||||
var canSeeSky: Boolean? = null
|
||||
var minX: Float? = null
|
||||
var minY: Float? = null
|
||||
@@ -76,7 +78,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 && moonPhase != ctx.moonPhase) {
|
||||
} else if (moonPhase != null && ctx.moonPhase in moonPhase!!) {
|
||||
return false
|
||||
} else if (biomes != null && biomes!!.isNotEmpty() && biomes!!.none { condition -> condition.fits(ctx.biome, ctx.biomeRegistry) }) {
|
||||
return false
|
||||
|
||||
+6
-5
@@ -8,10 +8,11 @@
|
||||
|
||||
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.util.Identifier
|
||||
import net.minecraft.fluid.Fluid
|
||||
|
||||
/**
|
||||
* Base type for a spawning condition that applies to some kind of [SubmergedSpawningContext]. This
|
||||
@@ -24,7 +25,7 @@ abstract class SubmergedTypeSpawningCondition<T : SubmergedSpawningContext> : Ar
|
||||
var minDepth: Int? = null
|
||||
var maxDepth: Int? = null
|
||||
var fluidIsSource: Boolean? = null
|
||||
var fluidBlock: Identifier? = null
|
||||
var fluid: RegistryLikeCondition<Fluid>? = null
|
||||
|
||||
override fun fits(ctx: T, detail: SpawnDetail): Boolean {
|
||||
return if (!super.fits(ctx, detail)) {
|
||||
@@ -33,9 +34,9 @@ abstract class SubmergedTypeSpawningCondition<T : SubmergedSpawningContext> : Ar
|
||||
false
|
||||
} else if (maxDepth != null && ctx.depth > maxDepth!!) {
|
||||
false
|
||||
} else if (fluidIsSource != null && ctx.fluidState.isStill != fluidIsSource!!) {
|
||||
} else if (fluidIsSource != null && ctx.fluid.isStill != fluidIsSource!!) {
|
||||
false
|
||||
} else !(fluidBlock != null && ctx.blockRegistry.getKey(ctx.fluidBlock).get().value != fluidBlock!!)
|
||||
} else !(ctx.fluid.isEmpty || (fluid != null && !fluid!!.fits(ctx.fluid.fluid, ctx.fluidRegistry)))
|
||||
}
|
||||
|
||||
override fun copyFrom(other: SpawningCondition<*>, merger: Merger) {
|
||||
@@ -44,7 +45,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)
|
||||
fluidBlock = merger.mergeSingle(fluidBlock, other.fluidBlock)
|
||||
fluid = merger.mergeSingle(fluid, other.fluid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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"
|
||||
}
|
||||
}
|
||||
+14
@@ -92,4 +92,18 @@ 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)
|
||||
+2
@@ -15,6 +15,7 @@ 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
|
||||
@@ -65,6 +66,7 @@ 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)!!
|
||||
|
||||
+1
-2
@@ -34,6 +34,5 @@ open class SubmergedSpawningContext(
|
||||
nearbyBlocks: List<BlockState>,
|
||||
slice: WorldSlice
|
||||
) : AreaSpawningContext(cause, world, position, light, canSeeSky, influences, width, height, nearbyBlocks, slice) {
|
||||
val fluidBlock = slice.getBlockState(position.x, position.y, position.z).block
|
||||
val fluidState = slice.getBlockState(position.x, position.y, position.z).fluidState
|
||||
val fluid = slice.getBlockState(position.x, position.y, position.z).fluidState
|
||||
}
|
||||
+32
-1
@@ -13,6 +13,7 @@ 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
|
||||
@@ -120,4 +121,34 @@ 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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -12,8 +12,7 @@ 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.SEASON
|
||||
import com.cobblemon.mod.common.pokemon.feature.SeasonFeature
|
||||
import com.cobblemon.mod.common.pokemon.feature.SeasonFeatureHandler
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
@@ -37,7 +36,7 @@ class PokemonSpawnAction(
|
||||
props.shiny = Cobblemon.config.shinyRate.takeIf { it >= 1 }?.let { Random.Default.nextFloat() < 1 / it }
|
||||
}
|
||||
val entity = props.createEntity(ctx.world)
|
||||
entity.pokemon.getFeature<SeasonFeature>(SEASON)?.update(entity.pokemon, ctx.world, ctx.position)
|
||||
SeasonFeatureHandler.updateSeason(entity.pokemon, ctx.world, ctx.position)
|
||||
entity.drops = detail.drops
|
||||
return entity
|
||||
}
|
||||
|
||||
@@ -8,11 +8,23 @@
|
||||
|
||||
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.
|
||||
@@ -25,7 +37,22 @@ import com.cobblemon.mod.common.api.spawning.spawner.Spawner
|
||||
* @author Hiroku
|
||||
* @since February 9th, 2022
|
||||
*/
|
||||
class SpawnPool : Iterable<SpawnDetail> {
|
||||
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()
|
||||
}
|
||||
|
||||
val details = mutableListOf<SpawnDetail>()
|
||||
var precalculation: PrecalculationResult<*> = RootPrecalculation.generate(details, emptyList())
|
||||
val precalculators = mutableListOf<SpawningPrecalculation<*>>()
|
||||
@@ -75,8 +102,8 @@ class SpawnPool : Iterable<SpawnDetail> {
|
||||
* Creates a de-referenced copy of the pool which can be modified safely without
|
||||
* this pool being changed.
|
||||
*/
|
||||
fun copy(): SpawnPool {
|
||||
val copy = SpawnPool()
|
||||
fun copy(newName: String): SpawnPool {
|
||||
val copy = SpawnPool(newName)
|
||||
copy.details.addAll(details)
|
||||
copy.precalculators.addAll(precalculators)
|
||||
copy.precalculation = precalculation
|
||||
|
||||
+3
-2
@@ -33,7 +33,8 @@ class BestSpawnerConfig {
|
||||
val replaceWithNewVersion = true
|
||||
val contextWeights = mutableMapOf(
|
||||
"grounded" to 1F,
|
||||
"submerged" to 0.7F
|
||||
"submerged" to 0.95F,
|
||||
"surface" to 0.05F
|
||||
)
|
||||
val buckets = mutableListOf(
|
||||
SpawnBucket("common", 94.4F),
|
||||
@@ -54,7 +55,7 @@ class BestSpawnerConfig {
|
||||
|
||||
fun load(): BestSpawnerConfig {
|
||||
val internal = loadInternal()
|
||||
if (Cobblemon.config.exportSpawnConfigToConfig) {
|
||||
if (Cobblemon.config.exportSpawnConfig) {
|
||||
val external = loadExternal()
|
||||
return if (external == null) {
|
||||
saveExternal()
|
||||
|
||||
+2
-94
@@ -8,39 +8,16 @@
|
||||
|
||||
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
|
||||
@@ -48,7 +25,7 @@ import net.minecraft.world.biome.Biome
|
||||
* 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 [SpawnDetailPreset.registerPresetType].
|
||||
* A subclass of this base must be registered using [SpawnDetailPresets.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.
|
||||
@@ -63,75 +40,6 @@ import net.minecraft.world.biome.Biome
|
||||
* @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
|
||||
|
||||
+5
-5
@@ -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.create()
|
||||
val rand = Random.Default
|
||||
|
||||
val center = player.pos
|
||||
|
||||
val r = nextBetween(rand, config.minimumSliceDistanceFromPlayer, config.maximumSliceDistanceFromPlayer)
|
||||
val thetatemp = atan(player.velocity.z / player.velocity.x) + nextBetween(rand, -PI/2, PI/2 )
|
||||
val r = rand.nextBetween(config.minimumSliceDistanceFromPlayer, config.maximumSliceDistanceFromPlayer)
|
||||
val thetatemp = atan(player.velocity.z / player.velocity.x) + rand.nextBetween(-PI/2, PI/2 )
|
||||
val theta = if (player.velocity.horizontalLength() < 0.1) {
|
||||
rand.nextDouble() * 2 * PI
|
||||
} else if (player.velocity.x < 0) {
|
||||
|
||||
+1
-2
@@ -46,7 +46,6 @@ abstract class TickingSpawner(
|
||||
|
||||
var active = true
|
||||
val spawnedEntities = mutableListOf<Entity>()
|
||||
var maximumSpawned = Cobblemon.config.maximumSpawnedPokemonPerPlayer
|
||||
|
||||
var lastSpawnTime = 0L
|
||||
var ticksUntilNextSpawn = 100F
|
||||
@@ -66,7 +65,7 @@ abstract class TickingSpawner(
|
||||
removalCheckTicks = 0
|
||||
}
|
||||
|
||||
if (!active || spawnedEntities.size >= maximumSpawned) {
|
||||
if (!active) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+10
-6
@@ -44,11 +44,15 @@ open class JSONStoreAdapter(
|
||||
}
|
||||
|
||||
override fun <E, T : PokemonStore<E>> load(file: File, storeClass: Class<out T>, uuid: UUID): T? {
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -38,9 +38,9 @@ open class NBTStoreAdapter(
|
||||
val nbt = NbtIo.readCompressed(file)
|
||||
store.loadFromNBT(nbt)
|
||||
store
|
||||
} catch (eof: EOFException) {
|
||||
LOGGER.error("Pokémon save file for ${storeClass.simpleName} ($uuid) was corrupted. A fresh file will be created.")
|
||||
store
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
@@ -8,6 +8,7 @@
|
||||
|
||||
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
|
||||
@@ -67,6 +68,7 @@ abstract class OneToOneFileStoreAdapter<S>(
|
||||
try {
|
||||
val tempLoaded = load(tempFile, storeClass, uuid)
|
||||
if (tempLoaded != null) {
|
||||
save(file, serialize(tempLoaded))
|
||||
return tempLoaded
|
||||
}
|
||||
} finally {
|
||||
@@ -76,6 +78,10 @@ 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,15 +8,12 @@
|
||||
|
||||
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.util.DataKeys
|
||||
import com.cobblemon.mod.common.battles.runner.GraalShowdown
|
||||
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
|
||||
@@ -113,10 +110,6 @@ 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,
|
||||
@@ -126,8 +119,8 @@ object BattleRegistry {
|
||||
battleMap[battle.battleId] = battle
|
||||
|
||||
// Build request message
|
||||
val jsonArray = JsonArray()
|
||||
jsonArray.add(">start { \"format\": ${battleFormat.toFormatJSON()} }")
|
||||
val messages = mutableListOf<String>()
|
||||
messages.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.
|
||||
@@ -167,21 +160,16 @@ object BattleRegistry {
|
||||
|
||||
// -> Add the players and team
|
||||
for (actor in battle.actors.sortedBy { it.showdownId }) {
|
||||
jsonArray.add(""">player ${actor.showdownId} {"name":"${actor.uuid}","team":"${actor.pokemonList.packTeam()}"}""")
|
||||
messages.add(""">player ${actor.showdownId} {"name":"${actor.uuid}","team":"${actor.pokemonList.packTeam()}"}""")
|
||||
}
|
||||
|
||||
// -> Set team size
|
||||
for (actor in battle.actors.sortedBy { it.showdownId }) {
|
||||
jsonArray.add(">${actor.showdownId} team ${actor.pokemonList.count()}")
|
||||
messages.add(">${actor.showdownId} team ${actor.pokemonList.count()}")
|
||||
}
|
||||
|
||||
// Compiles the request and sends it off
|
||||
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))
|
||||
|
||||
GraalShowdown.startBattle(battle, messages.toTypedArray())
|
||||
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) ?: return true
|
||||
val availableTargets = move.target.targetList(activeBattlePokemon)?.takeIf { it.isNotEmpty() } ?: 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,7 +28,6 @@ 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
|
||||
@@ -96,6 +95,7 @@ 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,18 +149,15 @@ object ShowdownInterpreter {
|
||||
else -> "HUH!?"
|
||||
}
|
||||
|
||||
fun interpretMessage(message: String) {
|
||||
fun interpretMessage(battleId: UUID, message: String) {
|
||||
// Check key map and use function if matching
|
||||
val battleId = message.split(ShowdownConnection.LINE_START)[0]
|
||||
val rawMessage = message.replace(battleId + ShowdownConnection.LINE_START, "")
|
||||
|
||||
if (rawMessage.startsWith("{\"winner\":\"")) {
|
||||
if (message.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(UUID.fromString(battleId))
|
||||
val battle = BattleRegistry.getBattle(battleId)
|
||||
|
||||
if (battle == null) {
|
||||
LOGGER.info("No battle could be found with the id: $battleId")
|
||||
@@ -169,7 +166,7 @@ object ShowdownInterpreter {
|
||||
|
||||
runOnServer {
|
||||
battle.showdownMessages.add(message)
|
||||
interpret(battle, rawMessage)
|
||||
interpret(battle, message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -904,4 +901,14 @@ 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,19 +11,17 @@ 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.Cobblemon.showdown
|
||||
import com.cobblemon.mod.common.battles.runner.JavetShowdownConnection
|
||||
import com.cobblemon.mod.common.battles.runner.GraalShowdown
|
||||
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>()
|
||||
@@ -32,7 +30,8 @@ class ShowdownThread : Thread("Cobblemon Showdown") {
|
||||
.create()
|
||||
|
||||
override fun run() {
|
||||
var showdownDir = File(".")
|
||||
var showdownDir = File("showdown")
|
||||
showdownDir.mkdir()
|
||||
|
||||
val showdownMetadata = loadShowdownMetadata()
|
||||
|
||||
@@ -63,73 +62,13 @@ class ShowdownThread : Thread("Cobblemon Showdown") {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Initialize showdown connection
|
||||
showdown = JavetShowdownConnection()
|
||||
(showdown as JavetShowdownConnection).initializeServer()
|
||||
GraalShowdown.createContext()
|
||||
GraalShowdown.boot()
|
||||
|
||||
// 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!")
|
||||
LOGGER.info("Showdown has been started!")
|
||||
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? {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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() {}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
-72
@@ -1,72 +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 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
|
||||
}
|
||||
}
|
||||
@@ -1,36 +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 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()
|
||||
}
|
||||
@@ -1,23 +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
|
||||
|
||||
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}"
|
||||
}
|
||||
}
|
||||
@@ -1,180 +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.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
-67
@@ -1,67 +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 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.clear()
|
||||
BedrockAnimationRepository.loadAnimations(resourceManager)
|
||||
PokemonModelRepository.reload(resourceManager)
|
||||
LOGGER.info("Loaded assets")
|
||||
// PokeBallModelRepository.reload(resourceManager)
|
||||
|
||||
@@ -22,6 +22,7 @@ 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>
|
||||
|
||||
+24
-10
@@ -6,39 +6,53 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.summary
|
||||
package com.cobblemon.mod.common.client.gui
|
||||
|
||||
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 * SCALE).toInt(), (HEIGHT * SCALE).toInt(), Text.literal(""), onPress) {
|
||||
): ButtonWidget(pX, pY, WIDTH.toInt(), HEIGHT.toInt(), Text.literal("Exit"), onPress) {
|
||||
|
||||
companion object {
|
||||
private const val WIDTH = 52F
|
||||
private const val HEIGHT = 26F
|
||||
private const val WIDTH = 26F
|
||||
private const val HEIGHT = 13F
|
||||
private const val SCALE = 0.5F
|
||||
private val exitButtonResource = cobblemonResource("ui/summary/summary_back_button.png")
|
||||
private val buttonResource = cobblemonResource("ui/common/back_button.png")
|
||||
private val iconResource = cobblemonResource("ui/common/back_button_icon.png")
|
||||
}
|
||||
|
||||
override fun renderButton(pMatrixStack: MatrixStack, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
|
||||
blitk(
|
||||
matrixStack = pMatrixStack,
|
||||
texture = exitButtonResource,
|
||||
x = x * 2,
|
||||
y = y * 2,
|
||||
texture = buttonResource,
|
||||
x = x,
|
||||
y = y,
|
||||
width = WIDTH,
|
||||
height = HEIGHT,
|
||||
vOffset = if (isHovered(pMouseX.toDouble(), pMouseY.toDouble())) HEIGHT else 0,
|
||||
textureHeight = HEIGHT * 2,
|
||||
textureHeight = HEIGHT * 2
|
||||
)
|
||||
|
||||
blitk(
|
||||
matrixStack = pMatrixStack,
|
||||
texture = iconResource,
|
||||
x = (x + 7) / SCALE,
|
||||
y = (y + 4) / SCALE,
|
||||
width = 21,
|
||||
height = 11,
|
||||
scale = SCALE
|
||||
)
|
||||
}
|
||||
|
||||
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in (x.toFloat()..(x.toFloat() + (WIDTH * SCALE))) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + (HEIGHT * 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))
|
||||
}
|
||||
@@ -163,7 +163,7 @@ class BattleOverlay : InGameHud(MinecraftClient.getInstance(), MinecraftClient.g
|
||||
reversed = !left,
|
||||
species = battlePokemon.species,
|
||||
level = battlePokemon.level,
|
||||
aspects = battlePokemon.properties.aspects,
|
||||
aspects = battlePokemon.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.currentHealth * battlePokemon.hpRatio).roundToInt() to (it.hp * battlePokemon.hpRatio).roundToInt() }
|
||||
trueHealth = truePokemon?.let { (it.hp * battlePokemon.hpRatio).roundToInt() to it.hp }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -8,6 +8,7 @@
|
||||
|
||||
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
|
||||
@@ -17,6 +18,8 @@ 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
|
||||
@@ -94,5 +97,9 @@ 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
-1
@@ -8,6 +8,7 @@
|
||||
|
||||
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
|
||||
@@ -26,6 +27,8 @@ 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
|
||||
@@ -156,7 +159,7 @@ class BattleMoveSelection(
|
||||
}
|
||||
moveSelection.playDownSound(MinecraftClient.getInstance().soundManager)
|
||||
val targets = move.target.targetList(moveSelection.request.activePokemon)
|
||||
if (targets == null) {
|
||||
if (targets.isNullOrEmpty()) {
|
||||
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()))
|
||||
@@ -185,4 +188,8 @@ class BattleMoveSelection(
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager) {
|
||||
soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK.get(), 1.0F))
|
||||
}
|
||||
}
|
||||
+7
@@ -8,6 +8,7 @@
|
||||
|
||||
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
|
||||
@@ -19,6 +20,8 @@ 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(
|
||||
@@ -120,5 +123,9 @@ 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
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +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.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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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,33 +8,47 @@
|
||||
|
||||
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.TexturedButtonWidget
|
||||
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
|
||||
|
||||
class NavigationButton(
|
||||
pX: Int, pY: Int,
|
||||
val pWidth: Int, val pHeight: Int,
|
||||
pXTexStart: Int, pYTexStart: Int, pYDiffText: Int,
|
||||
private val forward: Boolean,
|
||||
onPress: PressAction,
|
||||
): TexturedButtonWidget(pX, pY, pWidth, pHeight, pXTexStart, pYTexStart, pYDiffText, forwardButtonResource, onPress) {
|
||||
onPress: PressAction
|
||||
): ButtonWidget(pX, pY, (WIDTH * SCALE).toInt(), (HEIGHT * SCALE).toInt(), Text.literal("Navigation"), onPress) {
|
||||
|
||||
companion object {
|
||||
private val forwardButtonResource = cobblemonResource("ui/pc/pc_box_right.png")
|
||||
private val backwardsButtonResource = cobblemonResource("ui/pc/pc_box_left.png")
|
||||
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")
|
||||
}
|
||||
|
||||
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.25, y = y - 0.5F,
|
||||
texture = if (forward) forwardButtonResource else backwardsButtonResource,
|
||||
width = pWidth, height = pHeight
|
||||
)
|
||||
}
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
@@ -1,81 +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.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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.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
|
||||
}
|
||||
|
||||
}
|
||||
-80
@@ -1,80 +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.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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,51 +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.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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,261 +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.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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,107 +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.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()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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))
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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))
|
||||
}
|
||||
@@ -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.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))
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
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
|
||||
@@ -19,6 +20,7 @@ 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
|
||||
@@ -26,7 +28,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.change.MoveSwapScreen
|
||||
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.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
|
||||
@@ -43,7 +45,9 @@ 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")) {
|
||||
@@ -154,7 +158,12 @@ 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) }
|
||||
) {
|
||||
if (mainScreenIndex != INFO) {
|
||||
displayMainScreen(INFO)
|
||||
playSound(CobblemonSounds.GUI_CLICK.get())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
summaryTabs.add(
|
||||
@@ -162,7 +171,12 @@ 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) }
|
||||
) {
|
||||
if (mainScreenIndex != MOVES) {
|
||||
displayMainScreen(MOVES)
|
||||
playSound(CobblemonSounds.GUI_CLICK.get())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
summaryTabs.add(
|
||||
@@ -170,7 +184,12 @@ 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) }
|
||||
) {
|
||||
if (mainScreenIndex != STATS) {
|
||||
displayMainScreen(STATS)
|
||||
playSound(CobblemonSounds.GUI_CLICK.get())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
summaryTabs[mainScreenIndex].toggleTab()
|
||||
@@ -182,6 +201,7 @@ 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)
|
||||
}
|
||||
)
|
||||
@@ -512,4 +532,8 @@ 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,12 +8,14 @@
|
||||
|
||||
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
|
||||
@@ -86,9 +88,9 @@ class SummaryButton(
|
||||
return false
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager?) {
|
||||
override fun playDownSound(soundManager: SoundManager) {
|
||||
if (!this.silent) {
|
||||
super.playDownSound(soundManager)
|
||||
soundManager.play(PositionedSoundInstance.master(CobblemonSounds.GUI_CLICK.get(), 1.0F))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -8,6 +8,7 @@
|
||||
|
||||
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
|
||||
@@ -197,6 +198,7 @@ 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
|
||||
}
|
||||
}
|
||||
@@ -213,6 +215,7 @@ 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
|
||||
|
||||
+11
-1
@@ -8,6 +8,7 @@
|
||||
|
||||
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
|
||||
@@ -17,8 +18,11 @@ 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(
|
||||
@@ -46,7 +50,7 @@ class PartyWidget(
|
||||
|
||||
private val partySize = partyList.size
|
||||
private val partySlots = arrayListOf<PartySlotWidget>()
|
||||
val swapButton: SummaryButton = SummaryButton(
|
||||
private val swapButton: SummaryButton = SummaryButton(
|
||||
buttonX = x + 80F,
|
||||
buttonY = y - 9F,
|
||||
buttonWidth = 26,
|
||||
@@ -159,6 +163,7 @@ class PartyWidget(
|
||||
index = -1,
|
||||
isClientPartyMember = isParty
|
||||
)
|
||||
playSound(CobblemonSounds.PC_GRAB.get())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,6 +179,7 @@ class PartyWidget(
|
||||
}
|
||||
swapSource = null
|
||||
draggedSlot = null
|
||||
playSound(CobblemonSounds.PC_DROP.get())
|
||||
}
|
||||
}
|
||||
return super.mouseReleased(pMouseX, pMouseY, pButton)
|
||||
@@ -205,6 +211,10 @@ 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)
|
||||
}
|
||||
+6
@@ -8,12 +8,15 @@
|
||||
|
||||
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
|
||||
|
||||
@@ -47,6 +50,9 @@ class SummaryTab(
|
||||
)
|
||||
}
|
||||
|
||||
override fun playDownSound(soundManager: SoundManager) {
|
||||
}
|
||||
|
||||
fun toggleTab(state: Boolean = true) {
|
||||
isActive = state
|
||||
}
|
||||
|
||||
+4
-3
@@ -18,6 +18,7 @@ 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
|
||||
@@ -147,7 +148,7 @@ class InfoWidget(
|
||||
drawScaledText(
|
||||
matrixStack = pMatrixStack,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = pokemon.nature.displayName.bold(),
|
||||
text = pokemon.nature.displayName.asTranslated().bold(),
|
||||
x = x + 53,
|
||||
y = y + 66,
|
||||
shadow = true
|
||||
@@ -166,7 +167,7 @@ class InfoWidget(
|
||||
drawScaledText(
|
||||
matrixStack = pMatrixStack,
|
||||
font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = pokemon.ability.displayName.bold(),
|
||||
text = pokemon.ability.displayName.asTranslated().bold(),
|
||||
x = x + 53,
|
||||
y = y + 81,
|
||||
shadow = true
|
||||
@@ -177,7 +178,7 @@ class InfoWidget(
|
||||
pMatrixStack.push()
|
||||
pMatrixStack.scale(smallTextScale, smallTextScale, 1F)
|
||||
MultiLineLabelK.create(
|
||||
component = pokemon.ability.description,
|
||||
component = pokemon.ability.description.asTranslated(),
|
||||
width = 117 / smallTextScale,
|
||||
maxLines = 3
|
||||
).renderLeftAligned(
|
||||
|
||||
+3
-2
@@ -6,9 +6,10 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.change
|
||||
package com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves
|
||||
|
||||
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
|
||||
@@ -18,7 +19,6 @@ 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,6 +195,7 @@ class MoveSwapScreen(
|
||||
newMove = move
|
||||
)
|
||||
)
|
||||
pane.movesWidget.summary.playSound(CobblemonSounds.GUI_CLICK.get())
|
||||
pane.movesWidget.summary.displaySideScreen(Summary.PARTY)
|
||||
return true
|
||||
}
|
||||
+7
@@ -8,9 +8,12 @@
|
||||
|
||||
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
|
||||
|
||||
@@ -63,5 +66,9 @@ 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))
|
||||
}
|
||||
|
||||
+7
-1
@@ -8,11 +8,13 @@
|
||||
|
||||
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
|
||||
|
||||
@@ -57,5 +59,9 @@ 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))
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user