Compare commits

...
141 Commits
Author SHA1 Message Date
Hiroku 16007d3c1b Merge branch 'main' into '1.2.0'
Update Changelog

See merge request cable-mc/cobblemon!246
2023-01-01 04:20:56 +00:00
TestandHiroku ccee0065c2 Update Changelog [skip-ci] 2023-01-01 04:20:55 +00:00
Paul 30137a9308 When referring to a discretely counted thing, 'less' is actually meant to be 'fewer'. [skip-ci] 2023-01-01 14:04:26 +10:00
Paul c853644c16 Changelog for 1.2.0 [skip-ci] 2023-01-01 13:59:52 +10:00
Paul 794d1b391d Non-snapshot 1.2.0 2023-01-01 13:44:51 +10:00
Paul 3ac2687f1e Merge remote-tracking branch 'origin/main' 2023-01-01 11:19:29 +10:00
Paul 511fd00652 Add last saved Cobblemon version to Pokemon data so we can do data migrations 2023-01-01 11:19:18 +10:00
Test 405d3fd774 fix framing for Beedrill/Butterfree line 2022-12-31 15:20:55 +00:00
Paul 7a26d613fd Fixed zero PP turning into max-maxPP when entering battle 2022-12-31 20:16:34 +10:00
Paul 0a02edf6f3 Fixed hitboxes not changing when a Pokémon evolves 2022-12-31 19:22:30 +10:00
Paul e8c7b31144 Added explicit dependency on Architectury so we get fewer crash reports 2022-12-31 18:59:35 +10:00
Paul d6f6742658 Fixed Eevee positioning on shoulders 2022-12-31 13:16:35 +10:00
Paul c76dfe9e96 Doubled default charge rate of healers 2022-12-31 13:16:06 +10:00
Paul 3ba88265eb Fixed variations not showing in battle tiles, start battles with choice menu open 2022-12-31 13:04:57 +10:00
Hiroku dcc8975757 Fixed pyu size 2022-12-31 02:22:44 +00:00
Hiroku e824234057 Merge branch 'hit-count-interpret' into 'main'
Add support for '-hitcount' to ShowdownInterpreter

See merge request cable-mc/cobblemon!244
2022-12-30 23:32:41 +00:00
Licious 0cc8bd6e0f Remove unused import 2022-12-30 18:22:52 +00:00
Licious17 1e45ff34f0 Added support for '-hitcount' to ShowdownInterpreter 2022-12-30 17:20:38 +00:00
Hiroku 2532511d53 Merge branch 'fix-capture-effects' into 'main'
Fix capture effects

See merge request cable-mc/cobblemon!243
2022-12-30 12:39:12 +00:00
Licious17 14f7c5ecc3 Fixed CaptureEffect not triggering after a successful capture 2022-12-30 12:14:09 +00:00
Hiroku 1ec6af6d34 Merge branch 'pokemon-cry-fixes' into 'main'
Fix connection to server with cries

See merge request cable-mc/cobblemon!242
2022-12-29 21:04:19 +00:00
Licious17 e472ee5fbd Fixed S2C crash due to missing cry sound events, made the sound event identifier adapt to the namespace of the registered species for better expected datapack functionality, added the ability to create sounds for every form. 2022-12-29 20:50:11 +00:00
Test 7b4f43fd12 update labels 2022-12-28 21:45:36 +00:00
Hiroku 8a49b88c7a Merge branch 'spawn-pos-change' into 'main'
Fix ModifiableWorld#spawnEntity and Entity#setPosition inconsistencies

See merge request cable-mc/cobblemon!241
2022-12-28 13:02:15 +00:00
Licious17 2138022f6d Fixed usages of ModifiableWorld#spawnEntity before Entity#setPosition 2022-12-28 12:38:31 +00:00
Paul fd3bc667a9 Changelog for cry change [skip-ci] 2022-12-28 17:46:21 +10:00
Paul 4db5e54d88 Added blank sounds for Pokémon ambient cries so they can be set via RP 2022-12-28 17:41:07 +10:00
Paul 62748437de Corrected evo displays I think 2022-12-28 14:17:27 +10:00
Paul 163b2df7f6 Fixed many bugged forms that didn't have aspect conditions 2022-12-28 14:17:04 +10:00
Paul 386bad2c1c More missed stuff from last commit (illegal abilities) 2022-12-28 14:15:32 +10:00
Paul d304028389 Missed stuff from last commit (illegal abilities) 2022-12-28 14:14:53 +10:00
Paul 964894f376 Fixed abilities being illegal when they shouldn't be illegal 2022-12-28 14:14:33 +10:00
Paul ec8dfd746d Fixed moves that only work in double battles not being selectable at all 2022-12-28 13:47:09 +10:00
Hiroku 732547213a Merge branch 'mod-menu-compat' into 'main'
Added necessary data

See merge request cable-mc/cobblemon!239
2022-12-27 22:03:01 +00:00
Hiroku df888a7f36 Merge branch 'graal-relocation' into 'main'
Relocate IBM package, add pipes for Graal logging

See merge request cable-mc/cobblemon!240
2022-12-27 22:02:29 +00:00
Nick bd01b9d682 Relocate IBM package, add pipes for Graal logging 2022-12-27 13:10:38 -08:00
Licious17 ab390703da Updated mods.toml and fabric.mod.json with our new website. 2022-12-27 17:38:09 +00:00
Licious17 28cd8f0265 Added necessary data 2022-12-27 17:33:46 +00:00
Paul ca47636ffe Merge remote-tracking branch 'origin/main' into snowstorm-particles 2022-12-27 21:14:31 +10:00
Paul a852b8e535 Fixed multiple variant JSON issues 2022-12-27 21:14:13 +10:00
Test fa6f52d305 update click.ogg 2022-12-26 22:27:20 +00:00
Paul 3e8d8daa9b Merge remote-tracking branch 'origin/main' into snowstorm-particles 2022-12-26 21:32:21 +10:00
Paul 8196a686df Fixed arbok variants, other variants need restoring ): 2022-12-26 21:31:59 +10:00
Veraxiel da157a1b28 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon 2022-12-26 02:17:14 -05:00
Veraxiel 167191a135 Added spawn files for Natu, Murkrow, and Wailord families. Updated spawn files of the Gen 1 fossil Pokémon. 2022-12-26 02:16:31 -05:00
Paul e6f881df6e Merge remote-tracking branch 'origin/main' into snowstorm-particles
# Conflicts:
#	CHANGELOG.md
2022-12-26 14:35:35 +10:00
Paul 53f82b1971 Fixed form and alolan stuff 2022-12-26 14:25:18 +10:00
Hiroku 9f775f2c92 Merge branch 'model-fix-balls-healmach' into 'main'
Fixes In Hand Pokeball and Healing Machine Display Settings

See merge request cable-mc/cobblemon!238
2022-12-25 08:57:15 +00:00
dhi_holo 07f89dc691 Update CHANGELOG.md 2022-12-25 18:33:19 +10:00
dhi_holo 0c70a5cbac Fixes In Hand Pokeball and Healing Machine Display Settings
The rendering of the pokeball and healing machine items, mainly when dropped but in other places, were not matching to how other things in Minecraft work, so were brought slightly more inline. Slight model tweak to the Pokeball In Hand model to make the visuals cleaner, but every change directly links to the display settings.
2022-12-25 18:26:15 +10:00
Paul 3ceda765c9 Updated framing for starters, pichu line, zubat 2022-12-25 17:16:51 +10:00
Paul e3360e2d18 Updated various models and textures, fixed Rattata + Nidoqueen texture, updated Sableye animations 2022-12-25 14:33:19 +10:00
Paul b4e2ecc8f1 Added Wailmer, Wailord, Murkrow, Honchkrow, and fixed various framings 2022-12-24 22:04:45 +10:00
Paul bdd6ea48a0 Added scissoring of pokemon renders in PC GUI, update click sound to be a bit quieter 2022-12-24 11:44:26 +10:00
Paul 84244c0376 Added Natu and Xatu 2022-12-23 19:03:38 +10:00
Paul 6fec1417b1 Don't despawn a pokemon that has a vehicle 2022-12-23 16:13:04 +10:00
Paul 142c47de1c Changelog for pokemon look change during battle 2022-12-23 13:13:35 +10:00
Paul e796883660 Merge remote-tracking branch 'origin/main' into pc 2022-12-23 13:13:03 +10:00
Paul c6779385a7 Added emissive texture support to Pokémon render layers 2022-12-23 13:12:44 +10:00
Hiroku 1a0ea5db72 Merge branch 'pc' into 'main'
PC

See merge request cable-mc/cobblemon!237
2022-12-23 03:07:36 +00:00
TestandHiroku 342a1ceb6d PC 2022-12-23 03:07:36 +00:00
Hiroku 0e7d3438a5 Merge branch 'main' into 'main'
Pokémon look at eachother in battle & halt all other goals.

Closes #131

See merge request cable-mc/cobblemon!233
2022-12-23 02:55:25 +00:00
amo cadcdab39b fix bug 2022-12-23 02:52:08 +00:00
Paul 0bc7614809 Merge remote-tracking branch 'origin/pc' into pc 2022-12-23 12:42:56 +10:00
Paul ecbde40aa6 Fixed production issue on Forge caused by Graal 2022-12-23 12:42:46 +10:00
Village 672ed74309 rm unused imports 2022-12-22 21:25:12 -05:00
Village 469e9e9065 set preview on hover, remove debounce for grab 2022-12-22 21:20:50 -05:00
Paul 2dba103c14 Fixed text glitching on ability and nature 2022-12-23 10:37:15 +10:00
Village be7c0220d4 update sounds 2022-12-22 15:01:23 -05:00
Village 14f9640556 Merge branch 'main' of https://gitlab.com/cable-mc/pokemon-cobbled into pc 2022-12-22 02:59:05 -05:00
Village 3b2940ac59 rm unused imports 2022-12-22 02:58:46 -05:00
Village 036d9c5e6b PC 2022-12-22 02:34:25 -05:00
Paul 76cdecbb94 Fixed Mewtwo always being Mega Mewtwo X actually 2022-12-21 22:15:02 +10:00
Paul 1d6317b5d6 Fixed an instance of using CheckedRandom unsafely 2022-12-21 22:13:07 +10:00
Paul 26f963f9b9 Improved initializing logic so we don't get nasty invis pokemon again 2022-12-21 22:06:20 +10:00
Paul 9ee90508ed Changelog update [skip-ci] 2022-12-21 17:44:03 +10:00
Hiroku 9b6da631ee Merge branch 'stat-calculation-fix' into 'main'
Apply level multiplier in correct location, fixes invalid stats outside of HP calculation

See merge request cable-mc/cobblemon!236
2022-12-21 07:38:00 +00:00
Hiroku c26edc55f5 Merge branch 'spawn-validator' into 'main'
Fix Magmortar spawn definitions and add validator for future default configurations

See merge request cable-mc/cobblemon!235
2022-12-21 07:37:50 +00:00
Nick f1c306051c Apply level multiplier in correct location, fixes invalid stats outside of HP calculation 2022-12-20 19:18:32 -08:00
Nick dacb16044d License 2022-12-20 18:04:51 -08:00
Nick 68478f84d8 Fix Magmortar spawn definitions and add validator for future default configurations 2022-12-20 18:01:59 -08:00
Hiroku 6272bfe2d6 Merge branch 'battle-engine' into 'main'
Replaced Javet with Graal

See merge request cable-mc/cobblemon!234
2022-12-20 19:52:14 +00:00
Paul e561bfd148 Fixed bad directory reference in PokemonSpecies showdown export 2022-12-20 07:06:59 +10:00
amo 2f9cc2b41c pokemon look at eachother in battle & dont move 2022-12-19 16:08:12 +00:00
Paul 8d39e2536b Fixed graal issue in production jars 2022-12-19 22:08:45 +10:00
Paul 17fa80e752 Upped compile-time RAM requisition to 4gb 2022-12-19 21:59:10 +10:00
Paul 4d2641308a licenses 2022-12-19 21:02:34 +10:00
Paul 27e02963eb Implemented Showdown messaging using Graal 2022-12-19 21:01:12 +10:00
Paul d71c03e48a Merge remote-tracking branch 'origin/main' into battle-engine 2022-12-19 18:28:09 +10:00
Paul 43b69ba860 Merge remote-tracking branch 'origin/main' 2022-12-17 10:08:36 +10:00
Paul 64bfb72c53 Documentation correction 2022-12-17 10:08:25 +10:00
Paul 6810232183 Updated licenses 2022-12-17 10:08:03 +10:00
Licious17 6e25c2591e Changed the default Apricorn seed chance config value from 6% to 10% 2022-12-16 12:42:22 +00:00
Paul 0be43ec28c Added waning and waxing moonphase ranges 2022-12-16 21:08:20 +10:00
Paul 78f0f572b2 Added range support to moon phase conditions and allowed single-value interpretation for time range 2022-12-16 21:05:10 +10:00
Paul 6d99c7e643 Fixed showdown shutting down after logging out of singleplayer 2022-12-16 12:56:03 +10:00
Paul c6f4fc8992 Prevent all motion on dying pokemon 2022-12-16 12:37:40 +10:00
Paul 320c471b4e Changelog for chunk load fix 2022-12-16 12:33:10 +10:00
Paul 0ea8d8932e Merge branch '1.1.1-log-world-gen' of gitlab.com:cable-mc/pokemon-cobbled
# Conflicts:
#	gradle.properties
2022-12-16 12:32:27 +10:00
Hiroku 7325f4b6ac Merge branch 'surface-spawning' into 'main'
Added surface spawning context

See merge request cable-mc/cobblemon!232
2022-12-16 02:21:57 +00:00
Paul 4fd035aa16 Merge branch 'main' of gitlab.com:cable-mc/pokemon-cobbled into surface-spawning 2022-12-16 00:21:15 +10:00
Licious 5f1038d7a4 Merge branch 'animations-in-resource-packs' into 'main'
Animations in resource packs

See merge request cable-mc/cobblemon!230
2022-12-15 13:36:17 +00:00
HirokuandLicious 6449cb7295 Animations in resource packs 2022-12-15 13:36:17 +00:00
Paul 1fa0e81b8e Added surface spawning context 2022-12-15 22:34:09 +10:00
Hiroku b243a0a098 Merge branch 'starter-config-maintainability' into 'main'
Starter config maintainability

See merge request cable-mc/cobblemon!229
2022-12-14 21:58:09 +00:00
Paul 139253454c Merge branch 'main' of gitlab.com:cable-mc/pokemon-cobbled into starter-config-maintainability
# Conflicts:
#	CHANGELOG.md
2022-12-15 07:42:26 +10:00
Paul 7224ac123f Better protect save file loading using the .temps 2022-12-14 22:29:28 +10:00
Paul c53018942b Changelog [skip-ci] 2022-12-13 19:50:34 +10:00
Licious17 6bf09dbd2a Merge remote-tracking branch 'origin/main' 2022-12-10 13:16:00 +00:00
Licious17 010f9b16f9 Fix Rockruff and Lycanroc data for gimmick form 2022-12-10 13:15:51 +00:00
Paul 45eaf6fb3b Likely fix for world chunk gen hanging 2022-12-10 17:00:26 +10:00
Paul 8e4141ccc1 Merge branch 'main' of gitlab.com:cable-mc/pokemon-cobbled into starter-config-maintainability 2022-12-10 15:23:58 +10:00
Paul 41c6accc16 Improved starter config handling so it is opt-in for it to export to a file 2022-12-10 15:23:50 +10:00
Hiroku 470f0d37a4 Merge branch 'spawns-as-data' into 'main'
Moved spawn sets and spawn detail presets to data

See merge request cable-mc/cobblemon!228
2022-12-09 08:01:39 +00:00
Paul f7bd3af13b Licensing fix 2022-12-09 17:46:05 +10:00
Paul f4f7c0f19a Moved spawn sets and spawn detail presets to data 2022-12-09 17:30:34 +10:00
Paul 7ff0a66aae Removed faulty spawn cap 2022-12-09 00:00:23 +10:00
Hiroku 0487d431e1 Merge branch 'healer-advancements-redux' into 'main'
Updated Healing Machine Advancements

See merge request cable-mc/cobblemon!227
2022-12-08 09:42:37 +00:00
Hiroku 5429f70a26 Merge branch 'pkm-properties' into 'main'
Added pokeball and nature pokemon property

See merge request cable-mc/cobblemon!226
2022-12-08 09:05:55 +00:00
Licious17 aa8d0d80f1 Added pokeball and nature pokemon property 2022-12-06 12:49:12 +00:00
dhi_holo 9547be150b New Healing Machine Advancement
Removed full_heal and max_heal (dumb names, sorry), and made a (*slightly* jankily written, but fully functional) advancement which checks for you interacting with a healer with atleast one pokeball in it (any type is detected due to the stuff that makes it mildly janky looking).
A more optimized option advancement might be doable eventually, but this is the only thing I could detect to show the healer successfully being used.
Is possible to cheese it by interacting with a healer someone else is using, but shouldn't be an issue in the long run.

Updated en_us lang with new advancement, removing the prior two (the name from full_heal is kept, with a brand new description)
2022-12-06 20:43:00 +10:00
dhi_holo 346e3a3047 Update en_us.json
Fixed Calcuim (Calcium) misspelling
2022-12-06 16:28:11 +10:00
Licious 594bf59496 Merge branch 'aspect-json-rework' into 'main'
Aspect json rework

See merge request cable-mc/cobblemon!225
2022-12-05 15:11:12 +00:00
Paul 7f6fa2f009 Removed asset resolver converter code that is no longer needed 2022-12-05 19:51:11 +10:00
Paul b7720248ad Refactored asset resolver JSONs to be resource pack friendly 2022-12-05 19:49:35 +10:00
Hiroku 60593c7cdf Merge branch 'fix-aw-generation' into 'main'
Fix access widener generations on compile, remove cached AW

See merge request cable-mc/cobblemon!224
2022-12-05 02:50:58 +00:00
Nick 61e545ace3 Fix access widener generations on compile, remove cached AW 2022-12-04 15:28:19 -08:00
Hiroku 045398d204 Merge branch 'model-loading-fix' into 'main'
Model loading fix

See merge request cable-mc/cobblemon!223
2022-12-04 11:23:25 +00:00
Paul c4e318cb1c Updated aspect JSONs to use new model paths 2022-12-04 11:30:01 +10:00
Paul 048bae7585 Merge branch 'main' of gitlab.com:cable-mc/pokemon-cobbled into model-loading-fix 2022-12-03 18:48:46 +10:00
Hiroku fee858e5dc Merge branch 'better-aspects' into 'main'
Reworked species features, complete with new data pack stuff

See merge request cable-mc/cobblemon!222
2022-12-03 00:09:54 +00:00
Paul f938e99036 Merge branch 'main' of gitlab.com:cable-mc/pokemon-cobbled into better-aspects
# Conflicts:
#	CHANGELOG.md
2022-12-02 23:15:13 +10:00
Paul 02ecc87608 Fixed max hp display not being correct for allied pokemon 2022-12-02 23:14:34 +10:00
Paul 5a0d46410a Merge branch 'main' of gitlab.com:cable-mc/pokemon-cobbled into better-aspects
# Conflicts:
#	CHANGELOG.md
2022-12-02 23:05:19 +10:00
Paul e516b6dea2 Reworked species features, complete with new data pack stuff 2022-12-02 23:03:50 +10:00
MoeBoy76 3e3a2a7ec0 Fixed model loading for ResourcePacks, updated Abra aspect to match changes 2022-12-02 21:14:19 +11:00
Hiroku bd1f7c59c3 Merge branch 'entity-attributes-fix' into 'main'
Entity attribute registration fix

See merge request cable-mc/cobblemon!221
2022-11-29 10:54:14 +00:00
LiciousandHiroku 1fdf928b7b Entity attribute registration fix 2022-11-29 10:54:13 +00:00
Paul ee863a4e04 Dated the changelog [skip-ci] 2022-11-28 18:34:35 +10:00
Paul 5848e7b21c Added more stuff 2022-11-13 13:57:45 +10:00
Paul 2a2c0da200 Starting some global type definitions for engine 2022-11-11 19:50:41 +10:00
2292 changed files with 17617 additions and 10139 deletions
+5 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+1 -7
View File
@@ -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")
}
@@ -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()
}
}
}
@@ -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
}
@@ -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() }
}
}
@@ -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()
}
}
}
@@ -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")
}
@@ -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)
}
}
@@ -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
@@ -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()
}
@@ -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()
}
@@ -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?
}
@@ -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)
}
}
@@ -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
}
@@ -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"
}
@@ -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()
@@ -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)
}
@@ -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
@@ -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)
}
}
}
@@ -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"
}
}
@@ -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)
@@ -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)!!
@@ -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
}
@@ -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)
)
}
}
@@ -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
@@ -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()
@@ -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
@@ -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) {
@@ -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
}
@@ -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
}
}
}
@@ -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
}
}
}
@@ -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)
}
}
@@ -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()
}
}
}
@@ -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>
@@ -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 }
)
}
@@ -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,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))
}
}
@@ -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
}
}
@@ -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))
}
}
@@ -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
@@ -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)
}
@@ -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
}
@@ -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(
@@ -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
}
@@ -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))
}
@@ -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