Compare commits

...
Author SHA1 Message Date
Guilherme Pereira 1986cb480b add moves tooltips 2024-12-15 22:47:47 -03:00
Polymeta c8b0f36049 Merge branch 'bugfixes-apion' into 'main'
Fixes #1182

Closes #1182

See merge request cable-mc/cobblemon!1373
2024-12-15 18:25:02 +00:00
ApionandPolymeta be2a0be806 Fixes #1182 2024-12-15 18:25:02 +00:00
Paul ba4d42ccf6 Try tweaking spawn direction handling 2024-12-15 23:04:08 +10:00
Paul 5545d3498f Include 2 more variables in clamping of rotations 2024-12-15 20:53:50 +10:00
Hiroku 641bd7ebaa Merge branch 'advancing' into 'main'
1.6 Advancement Reworks

See merge request cable-mc/cobblemon!1374
2024-12-15 09:16:28 +00:00
dhi_holoandHiroku 234c4fa0d4 1.6 Advancement Reworks 2024-12-15 09:16:28 +00:00
Hiroku 3dfda71ed5 Merge branch 'AbilityPatchCapsuleRefactor' into 'main'
AbilityPatchCapsuleRefactor

See merge request cable-mc/cobblemon!1377
2024-12-15 09:08:05 +00:00
Hiroku 65ee6de93f Merge branch 'chest-boat-interact' into 'main'
moved changes to chest boat (i'm dumb)

Closes #1214

See merge request cable-mc/cobblemon!1379
2024-12-15 09:07:48 +00:00
Paul be88b727bc Added useFixedRandom to pool party provider, added randomizePartyOrder to NPC classes and presets for randomly choosing lead battle pokemon 2024-12-15 18:04:04 +10:00
luke a69fc462ee moved changes to chest boat (i'm dumb) 2024-12-15 00:38:08 -07:00
Hiroku 7d5c30d27c Merge branch 'shift-click-interact' into 'main'
Changed distance to use player attribute

Closes #1232

See merge request cable-mc/cobblemon!1378
2024-12-15 07:32:39 +00:00
Hiroku f7c9c9d4f4 Update AbilityTypeChanger.kt 2024-12-15 07:30:12 +00:00
luke 41bc14168a Changed distance to use player attribute 2024-12-15 00:24:38 -07:00
Paul 9540c38a72 Updated readme example for NPC 2024-12-15 16:58:31 +10:00
JazzMcNade 7a96ed7cd1 Refactored queryPossible() as to not to require explicit Priority values 2024-12-15 00:45:51 -06:00
Hiroku 677adc8d2f Merge branch 'starter-selection-cry' into 'main'
Play cry when Pokémon is clicked on the starter selection screen

See merge request cable-mc/cobblemon!1362
2024-12-15 06:25:29 +00:00
lionandHiroku a99b1dfc6c Play cry when Pokémon is clicked on the starter selection screen 2024-12-15 06:25:29 +00:00
Hiroku 4a718ad0f6 Merge branch 'dex-update-event' into 'main'
Dex Data Updated Event

See merge request cable-mc/cobblemon!1353
2024-12-15 05:27:08 +00:00
Paul ddd417d458 Added player to PokedexDataChangedEvent's flow context 2024-12-15 15:19:43 +10:00
Paul 10a12a897e Merge remote-tracking branch 'origin/main' into dex-update-event 2024-12-15 15:15:48 +10:00
Paul 9fce99ed73 Improved dex event, added dex update flow, expanded dex molang and added q.player.pokedex 2024-12-15 15:15:13 +10:00
Hiroku c35f60dd5d Merge branch 'AbilitiesFixesRedux' into 'main'
AbilitiesFixesRedux

Closes #1245

See merge request cable-mc/cobblemon!1376
2024-12-15 03:11:07 +00:00
JazzMcNade 54e5a5e819 Fixed single abilities mons always evolving into the hidden ability. Fixed ability capsule / patch not being able to revert abilities. 2024-12-14 20:35:23 -06:00
Test 79ae35ea9e Merge branch 'n-p-c' into 'main'
fix npc scaling + eye height, update edit interface

See merge request cable-mc/cobblemon!1375
2024-12-15 02:17:45 +00:00
Test 58f85e6d0d fix npc scaling + eye height, update edit interface 2024-12-15 02:17:45 +00:00
Polymeta 71bfdf41f4 Merge branch 'the-else-strikes-back' into 'main'
Fix Feebas spawning

Closes #1187

See merge request cable-mc/cobblemon!1365
2024-12-15 01:13:26 +00:00
JordanandPolymeta 9976965673 Fix Feebas spawning 2024-12-15 01:13:26 +00:00
Hiroku 098df1a5a6 Merge branch 'dex-travelling-dimensions' into 'main'
Fix Pokédex screen getting stuck when changing dimensions or opening the battle GUI

See merge request cable-mc/cobblemon!1370
2024-12-15 01:09:47 +00:00
Guilherme PereiraandHiroku f49f34efa3 Fix Pokédex screen getting stuck when changing dimensions or opening the battle GUI 2024-12-15 01:09:47 +00:00
Hiroku eac19929c4 Merge branch 'fix-dex-animations-not-playing' into 'main'
Fix animations not playing on Pokédex

See merge request cable-mc/cobblemon!1355
2024-12-15 00:43:09 +00:00
Guilherme PereiraandHiroku 59cced5b2c Fix animations not playing on Pokédex 2024-12-15 00:43:08 +00:00
Apion f07707221f Merge branch 'implement_pokemon' into 'main'
Pokémon fixes and tweaks

Closes #1230 and #1137

See merge request cable-mc/cobblemon!1371
2024-12-14 23:57:34 +00:00
PolymetaandApion 89b89592ab Pokémon fixes and tweaks 2024-12-14 23:57:33 +00:00
Hiroku cdeb977769 Update DexInformationChangedEvent.kt 2024-12-14 23:55:43 +00:00
Paul a3987b433c Merge remote-tracking branch 'origin/main' 2024-12-14 22:19:51 +10:00
Paul 99de91bd7e Added optional level property to pool party providers for using molang to force a level value, closes #1244 2024-12-14 22:19:39 +10:00
Paul 4b292b1241 Less zealous on the warning logging 2024-12-14 22:16:07 +10:00
Hiroku 1e6e4d8c5f Merge branch 'moe-eats-bugs' into 'main'
Added ignore checks to prevent passive healing/revival when config values are 0

Closes #875

See merge request cable-mc/cobblemon!1372
2024-12-14 11:41:35 +00:00
MoeBoy76 9a61c3471b Added ignore checks to prevent passive healing/revival when config values are 0 2024-12-14 22:12:08 +11:00
Paul 77f7439853 Fixed brewing on neoforged 2024-12-14 14:51:01 +10:00
Paul e9718b7e8b Merge remote-tracking branch 'origin/main' 2024-12-14 14:29:28 +10:00
Hiroku 4cadd4e20f Merge branch 'fix/watchdog' into 'main'
Watchdog: Possible fix for watchdog crashing

See merge request cable-mc/cobblemon!1368
2024-12-14 04:26:24 +00:00
Hiroku 17a3d86324 Update PokemonEntity.kt 2024-12-14 04:25:19 +00:00
Hiroku 48653073f5 if ( 2024-12-14 04:21:49 +00:00
Nick 82c7c03e28 Initial tests for watchdog fix 2024-12-13 20:18:13 -08:00
Nick 2d7f3257d1 Merge branch 'hiro-fix' into 'main'
I fix bugs too

Closes #1212, #1222, and #1228

See merge request cable-mc/cobblemon!1367
2024-12-14 04:16:35 +00:00
HirokuandNick bc7b4f94c2 I fix bugs too 2024-12-14 04:16:35 +00:00
Paul 7a9e9d7ced Fixed battles being startable while pokemon are busy, closes #1212 2024-12-14 13:52:19 +10:00
Paul 0f85ece16f Fixed cry sound effects playing twice for specific cases, closes #1222 2024-12-14 13:36:53 +10:00
Paul c4656e1a4e Fixed cubone alola bias not being visible, closes #1228 2024-12-14 12:57:17 +10:00
Paul 0c48a318c5 Added /runmolangscript, updated molang version 2024-12-14 12:22:25 +10:00
Paul dab89a9f8c Merge remote-tracking branch 'origin/main' 2024-12-14 11:05:55 +10:00
Paul 53fd2dba2d Fixed flows double-registering then triple-registering then quadruple-registering then- 2024-12-14 11:05:48 +10:00
Polymeta a5b15c6127 Merge branch 'more-items-tooltips' into 'main'
Add tooltips to many items

See merge request cable-mc/cobblemon!1346
2024-12-14 00:48:11 +00:00
Guilherme PereiraandPolymeta 174fb59bbb Add tooltips to many items 2024-12-14 00:48:11 +00:00
Paul 2f8bd44938 Added q.set_query for assigning new query function values, closes #1240 2024-12-14 10:36:53 +10:00
Paul 9d58c7126e Fixed set_npc_variable not initializing NPC data properly 2024-12-14 10:12:50 +10:00
Hiroku 2afd722125 Incl default return for get_npc_variable when no data exists for the NPC yet 2024-12-14 00:03:25 +00:00
Polymeta c9cfe489d6 Merge branch 'EV-Berry-Lang-fixes' into 'main'
Fixed lang files for EV lowering berries to actually be correct

See merge request cable-mc/cobblemon!1364
2024-12-13 11:23:29 +00:00
Plastered_Crab 4ee029fa54 Fixed lang files for EV lowering berries to actually be correct 2024-12-12 09:41:17 -08:00
Polymeta 5b0a221ade Merge branch 'RaftFixes' into 'main'
Fixed reading speciesdata instead of formdata. Fixed rafts deploying on...

See merge request cable-mc/cobblemon!1359
2024-12-11 08:14:41 +00:00
JazzMcNadeandPolymeta af23b82ce5 Fixed reading speciesdata instead of formdata. Fixed rafts deploying on... 2024-12-11 08:14:41 +00:00
amo d1a8171079 resolve threads 2024-12-09 23:57:38 +00:00
amo d4b673ea38 Merge branch 'main' into dex-update-event 2024-12-09 23:54:08 +00:00
Hiroku 36e78a5af2 Merge branch 'fix-dex-quiet-cries' into 'main'
Fix quiet cries in the Pokédex

See merge request cable-mc/cobblemon!1354
2024-12-07 22:38:12 +00:00
Guilherme PereiraandHiroku 55621b01b7 Fix quiet cries in the Pokédex 2024-12-07 22:38:12 +00:00
Paul b828fe2b3a Merge remote-tracking branch 'origin/main' 2024-12-07 20:59:45 +10:00
Paul b7b10151e1 Added get_npc_variable and set_npc_variable to player functions for convenience, updated molang for flexibility with query struct nested assignments 2024-12-07 20:59:33 +10:00
amo de21b7c402 forgot me loighter 2024-12-07 04:44:27 +00:00
amo 10fe3c4362 🍋‍🟩 Chore: Add Dex data update event 2024-12-07 04:40:50 +00:00
Test cac6ed843a Merge branch 'sterr/bugfixes' into 'main'
Adjusted portrait space detection in the Pokedex to prevent overlap

Closes #1055

See merge request cable-mc/cobblemon!1348
2024-12-06 18:32:20 +00:00
SterrezoandTest 8f5f40c360 Adjusted portrait space detection in the Pokedex to prevent overlap 2024-12-06 18:32:19 +00:00
Hiroku 0e586b4f38 Merge branch 'fix/ivs' into 'main'
Rework copy on properties, just clone fields rather than serialize + deserialize JSON

See merge request cable-mc/cobblemon!1344
2024-12-06 12:38:01 +00:00
Paul 62dd487448 Removed platform independent JSON datapack loading because Fabric has fixed the bug, closes #1184 2024-12-06 20:06:50 +10:00
Nick 04ecd9189c It would seem a particular build step was forgotten 2024-12-05 21:37:34 -08:00
Nick 55be22e942 Revert previous change, update stats differently instead 2024-12-05 21:29:13 -08:00
Hiroku f2e1b3dab4 Merge branch 'pokeedit-hidden-ability-false' into 'main'
Revert Pokémon ability to normal when hiddenAbility is false

See merge request cable-mc/cobblemon!1351
2024-12-06 00:51:21 +00:00
Paul 66f98ebf12 Added v.npc for on_player_wins and on_player_loses script hooks, v.npc to on_defeat and on_victory hooks 2024-12-05 20:43:04 +10:00
Paul c1842e42b0 Fixed run_molang script hooks not running multi-expressions, closes #1224 2024-12-05 19:14:49 +10:00
Hiroku e6c2f241d4 Merge branch 'MoreDynamicRafts' into 'main'
MoreDynamicRafts

See merge request cable-mc/cobblemon!1347
2024-12-05 08:39:13 +00:00
JazzMcNade 0f9c282e1f Merge branch 'main' into MoreDynamicRafts 2024-12-04 21:23:45 -06:00
JazzMcNade 7bd5d77b60 Fixed naming conventions, removed some unnecessary variables. 2024-12-04 21:22:35 -06:00
Guilherme Pereira f7b4d4e65a add changelog of when hiddenability=false on pokespawn and pokeedit commands 2024-12-04 22:23:28 -03:00
Guilherme Pereira e78f403198 set pokemon ability to normal when property is false 2024-12-04 21:43:07 -03:00
Test 4ca91cc8d8 Merge branch 'pokeedit-nickname-completion' into 'main'
Add nickname completion suggestion for pokeedit and pokespawn commands

See merge request cable-mc/cobblemon!1350
2024-12-05 00:20:08 +00:00
Test 1518181f6b Merge branch 'sound' into 'main'
Move sounds + Pokedex volume adjustments + missing sound fixes

See merge request cable-mc/cobblemon!1341
2024-12-05 00:14:06 +00:00
Hiroku b915237c8d Merge branch 'more-npc-dakka' into 'main'
Even more npc dakka

See merge request cable-mc/cobblemon!1330
2024-12-04 21:52:38 +00:00
Guilherme Pereira bb41a72067 add nickname completion suggestion for properties argument type 2024-12-04 16:29:36 -03:00
Paul fbb8492c75 Added ScriptPartyProvider 2024-12-04 22:36:17 +10:00
Paul c07aef04b1 Merge remote-tracking branch 'origin/main' into more-npc-dakka 2024-12-04 22:05:11 +10:00
Paul 6ff4befb04 Possible fix for tick watchdog hang 2024-12-04 22:00:56 +10:00
Paul a858d4f532 Use emptyList default rather than null for party provider player list, update doco, corrected LookInDirectionTask's extra start conditions 2024-12-04 21:13:38 +10:00
Hiroku 28fc273b26 Merge branch 'gimmi_tower_typo_fix' into 'main'
Fixing something i found in the processor lists

See merge request cable-mc/cobblemon!1345
2024-12-04 10:47:14 +00:00
JazzMcNade fc89757a09 Tab spacing 2024-12-04 01:16:00 -06:00
JazzMcNade 243c9abfd1 Rafts can now can (un)deploy upon touching water or a solid surface. 2024-12-04 01:11:02 -06:00
Slippy b871645e9e Fixing something i found in the processor lists 2024-12-03 11:23:40 -06:00
Paul c79916d8a0 More handy NPC presets, add player context to dynamic party molang environments, separated applied aspects from variation aspects for API 2024-12-03 21:20:31 +10:00
Nick d503d4a1eb Rework copy on properties, just clone fields rather than serialize + deserialize JSON 2024-12-02 19:10:30 -08:00
Paul a0f75dd090 Added is_chunk_loaded_at molang to level, added deeper support to json -> component deserialization 2024-12-02 09:46:52 +10:00
Sterrezo 5eceaa849c Quick fix to Mud Sport particles 2024-12-01 16:12:53 +01:00
Sterrezo 0e0233993a Merge branch 'main' into sound 2024-12-01 15:34:51 +01:00
Sterrezo 56ed5a6412 Added a sound to Kinesis 2024-12-01 15:30:47 +01:00
Paul f2e5ecef66 Conflicts begone 2024-12-01 17:00:03 +10:00
Paul ad9e85923a Merge remote-tracking branch 'origin/main' into more-npc-dakka
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
2024-12-01 16:57:06 +10:00
Paul bf461297b2 Corrected multiple little inaccuracies around molang and flows 2024-12-01 12:09:11 +10:00
Sterrezo 5965d5a81b Psychiiiiiiic 2024-12-01 01:58:23 +01:00
Sterrezo 4e02ca310d Added sounds for Ember, fixed Ember missing an impact and corrected Psychic's timing again 2024-12-01 00:44:35 +01:00
Sterrezo 3260271d3c Adjusted Psychic 2024-11-30 23:58:20 +01:00
Sterrezo a77863adab Implemented sounds for new move particles 2024-11-30 18:07:30 +01:00
Sterrezo 455cfeb894 Missing sound fixes for particles 2024-11-30 17:29:42 +01:00
Sterrezo fd3c614c2c Pokedex sound balancing
why did mojang hardcode this
2024-11-29 14:54:29 +01:00
Paul 4f3eb7e4ba Event flows merged in and used for a few battle-related NPC presets, heaps more molang hooks 2024-11-26 21:57:55 +10:00
Paul f9a3d2dc0e Merge remote-tracking branch 'origin/event-flows' into more-npc-dakka
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/events/pokemon/PokemonCapturedEvent.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/molang/MoLangFunctions.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/data/CobblemonDataProvider.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokemon/PokemonEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/MoLangExtensions.kt
2024-11-24 21:07:50 +10:00
Paul f817c18f4f Added NPC variable editing in NPC config menu 2024-11-24 21:02:38 +10:00
Paul a7e6e638fe Added run_command molang function to general query structs and player structs 2024-05-20 09:10:04 +10:00
Paul 9ef3ea2932 Use context for flows because it's cooler, added more functions to pokemon related molang 2024-05-19 19:08:56 +10:00
Paul 38dcee3327 Merge remote-tracking branch 'origin/sega_bass_pro_fishing_2' into event-flows
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokeball/EmptyPokeBallEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/MoLangExtensions.kt
2024-05-19 11:27:18 +10:00
Paul 12b92d3f35 Added flow datapackable thing 2024-02-24 11:58:57 +10:00
Paul dacfb4f1ff Merge branch 'npcs' into event-flows
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/pokeball/EmptyPokeBallEntity.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/util/MoLangExtensions.kt
2024-02-24 10:02:26 +10:00
Paul 4086ba73d3 Some structs for things 2024-02-24 09:59:27 +10:00
346 changed files with 6628 additions and 2290 deletions
+11
View File
@@ -52,6 +52,7 @@
- Added a unique set of sounds for Ancient Poké Balls.
- Added a sound for using Exp Candy and Rare Candy items.
- Added raft platforms for non-swimming, non-flying Pokémon to stand on during battles that take place on the water's surface. (Flying Pokémon will fly over water in battle, and water breathing Pokémon will swim in water during battle.)
- Added `hiddenability=false` option to `/spawnpokemon` and `/pokemonedit` commands, allowing the Pokémon's ability to be reverted to a normal ability.
### Pokémon Added
#### Gen 1
@@ -396,6 +397,9 @@
- Fixed Alolan Exeggutor's tail not showing in the party UI. It will now also stand in front of all Pokémon in the party menu :)
- Fixed Timburr duplicating its log while fainting.
- Fixed hitbox sizes for Grotle and Torterra being set to default values.
- Fixed Hisuian Sneasel using Johtonian Sneasel's cry
- Fixed Trevenant t-posing after 8 seconds of sleep.
- Fixed Bewear t-posing for a bit if a wild one fainted.
- Fixed reviving items throwing an exception when used in battle.
- Fixed messages for Focus Sash, Confusion, Mummy, Ice Face, Own Tempo, and Revive.
- Improve error handling when loading spawn-sets to ensure invalid configurations don't crash the server.
@@ -426,6 +430,7 @@
- Prevent summary stats tab from making sounds when clicking on an already open tab.
- Fixed display case not being able to be fed items from underneath.
- Fixed Pokémon battling in water continuously sinking to the bottom.
- Fixed passive healing and wake from faint not disabling when their config values are set to 0.
### Developer
- `SpawnCause` is now an implementation of `SpawningInfluence`.
@@ -457,6 +462,7 @@
- Added HealingSource, an interface applied to all sources of healing from player actions, for easier tracking of healing sources.
- Move Change
- Shiny Chance Calculation Event (with player context)
- Dex Information Changed; this event is fired when the Pokédex is updated with new information.
- Rebuilt large swaths of the model animation code to simplify it.
- Renamed a bunch of things from %Poseable% to %Posable% because spelling.
- Renamed StatelessAnimation to PoseAnimation.
@@ -485,6 +491,10 @@
- Added support for conditional pose animations.
- Added a new universal locator called "top".
- Added `eggs_collected` and `eggs_hatched` Advancement triggers.
- Added missing `minYaw` configuration to the `q.look()` function for JSON posers.
- Some pose condition names have been changed:
- `isTouchingWaterOrRain` has been changed to `isInWaterOrRain`
- `isSubermegedInWater` has been changed to `isUnderWater`
### Localization
- Updated translations for:
@@ -1719,6 +1729,7 @@ Thank you so much to all of our community translators that bring the mod to the
- Lowered spawn rate of Tauros.
- Sableye now spawns near gem ores as well as amethyst.
- Added evolution stones and items to item tags
- Pokémon now play their cry animations when clicked on the starter selection screen.
### Fixes
- Fixed catch rates being entirely too difficult.
@@ -22,7 +22,7 @@ public abstract class MinecraftDedicatedServerMixin implements ServerInterface {
@Inject(method = "shouldDiscardEntity", at = @At(value = "HEAD"), cancellable = true)
public void cobblemon$allowPokemonSpawns(Entity entity, CallbackInfoReturnable<Boolean> callback) {
if(entity instanceof PokemonEntity) {
if (entity instanceof PokemonEntity) {
callback.setReturnValue(false);
}
}
@@ -81,6 +81,7 @@ import com.cobblemon.mod.common.config.constraint.IntConstraint
import com.cobblemon.mod.common.config.starter.StarterConfig
import com.cobblemon.mod.common.data.CobblemonDataProvider
import com.cobblemon.mod.common.events.AdvancementHandler
import com.cobblemon.mod.common.events.FlowHandler
import com.cobblemon.mod.common.events.PokedexHandler
import com.cobblemon.mod.common.events.ServerTickHandler
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
@@ -288,6 +289,8 @@ object Cobblemon {
CobblemonEvents.POKEMON_PROPERTY_INITIALISED.emit(Unit)
FlowHandler.setup()
ifDedicatedServer {
isDedicatedServer = true
}
@@ -56,6 +56,7 @@ object CobblemonCommands {
AbandonMultiTeam.register(dispatcher)
FreezePokemonCommand.register(dispatcher)
ApplyPlayerTextureCommand.register(dispatcher)
RunMolangScriptCommand.register(dispatcher)
// Possibly lock down registration if and only if under dev environment or running in an environment
// with a certain system environment variable set
@@ -0,0 +1,80 @@
/*
* Copyright (C) 2023 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
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.net.messages.client.data.FlowRegistrySyncPacket
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.endsWith
import java.util.concurrent.ExecutionException
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.ResourceManager
/**
* Holds all the flows that are loaded from the server's data packs. A flow is foldered under the event identifier,
* which can be Cobblemon's namespace but can also be minecraft or any other namespace someone has added hooks for.
* The value of the map is a list of MoLang scripts that will execute when that event occurs.
*
* @author Hiroku
* @since February 24th, 2024
*/
object CobblemonFlows : DataRegistry {
override val id = cobblemonResource("flows")
override val observable = SimpleObservable<CobblemonFlows>()
override val type = PackType.SERVER_DATA
override fun sync(player: ServerPlayer) {
player.sendPacket(FlowRegistrySyncPacket(clientFlows.entries))
}
val runtime by lazy { MoLangRuntime().setup() } // Lazy for if someone adds to generalFunctions in MoLangFunctions
val clientFlows = hashMapOf<ResourceLocation, MutableList<ExpressionLike>>()
val flows = hashMapOf<ResourceLocation, MutableList<ExpressionLike>>()
override fun reload(manager: ResourceManager) {
clientFlows.clear()
flows.clear()
val folderBeforeNameRegex = ".*\\/([^\\/]+)\\/[^\\/]+\$".toRegex()
manager.listResources("flows") { path -> path.endsWith(CobblemonScripts.MOLANG_EXTENSION) }.forEach { (identifier, resource) ->
resource.openAsReader().use { stream ->
stream.buffered().use { reader ->
try {
val expression = reader.readText().asExpressionLike()
val event = folderBeforeNameRegex.find(identifier.path)?.groupValues?.get(1)
?: throw IllegalArgumentException("Invalid flow path: $identifier. Should have a folder structure that includes the name of the event being flowed.")
if (identifier.path.startsWith("flows/client/")) {
clientFlows.getOrPut(ResourceLocation.fromNamespaceAndPath(identifier.namespace, event)) { mutableListOf() }.add(expression)
} else {
flows.getOrPut(ResourceLocation.fromNamespaceAndPath(identifier.namespace, event)) { mutableListOf() }.add(expression)
}
} catch (exception: Exception) {
throw ExecutionException("Error loading MoLang script for flow: $identifier", exception)
}
}
}
}
Cobblemon.LOGGER.info("Loaded ${CobblemonScripts.scripts.size} flows and ${CobblemonScripts.clientScripts.size} client flows")
observable.emit(this)
}
fun run(eventResourceLocation: ResourceLocation, context: Map<String, MoValue>) {
flows[eventResourceLocation]?.forEach { it.resolve(runtime, context) }
}
}
@@ -171,14 +171,6 @@ interface CobblemonImplementation {
*/
fun server(): MinecraftServer?
/**
* Handles platform independent reloading of a [JsonDataRegistry].
*
* @param registry The [JsonDataRegistry] to reload.
* @param manager The [ResourceManager] to reload from.
*/
fun <T> reloadJsonRegistry(registry: JsonDataRegistry<T>, manager: ResourceManager): HashMap<ResourceLocation, T>
/**
* Registers an item to the [ComposterBlock].
*
@@ -54,6 +54,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
override val registry: Registry<Item> = BuiltInRegistries.ITEM
override val resourceKey: ResourceKey<Registry<Item>> = Registries.ITEM
@JvmField
val NPC_EDITOR = create("npc_editor", CobblemonItem(Item.Properties().stacksTo(1)))
@JvmField
val pokeBalls = mutableListOf<PokeBallItem>()
@JvmField
@@ -54,6 +54,7 @@ import com.cobblemon.mod.common.client.net.toast.ToastPacketHandler
import com.cobblemon.mod.common.client.net.trade.*
import com.cobblemon.mod.common.net.PacketRegisterInfo
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.battle.*
import com.cobblemon.mod.common.net.messages.client.callback.OpenMoveCallbackPacket
@@ -74,6 +75,7 @@ import com.cobblemon.mod.common.net.messages.client.pasture.ClosePasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonPasturedPacket
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonUnpasturedPacket
import com.cobblemon.mod.common.net.messages.client.pokedex.ServerConfirmedRegisterPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.*
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.AddEvolutionPacket
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.ClearEvolutionsPacket
@@ -85,7 +87,6 @@ import com.cobblemon.mod.common.net.messages.client.spawn.SpawnNPCPacket
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnPokeballPacket
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnPokemonPacket
import com.cobblemon.mod.common.net.messages.client.starter.OpenStarterUIPacket
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
import com.cobblemon.mod.common.net.messages.client.storage.RemoveClientPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.SwapClientPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.party.InitializePartyPacket
@@ -94,10 +95,13 @@ import com.cobblemon.mod.common.net.messages.client.storage.party.SetPartyPokemo
import com.cobblemon.mod.common.net.messages.client.storage.party.SetPartyReferencePacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.*
import com.cobblemon.mod.common.net.messages.client.toast.ToastPacket
import com.cobblemon.mod.common.net.messages.client.trade.*
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.PokedexUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
import com.cobblemon.mod.common.net.messages.server.*
import com.cobblemon.mod.common.net.messages.server.battle.*
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonSelectedPacket
@@ -110,6 +114,7 @@ import com.cobblemon.mod.common.net.messages.server.npc.SaveNPCPacket
import com.cobblemon.mod.common.net.messages.server.pasture.PasturePokemonPacket
import com.cobblemon.mod.common.net.messages.server.pasture.UnpastureAllPokemonPacket
import com.cobblemon.mod.common.net.messages.server.pasture.UnpasturePokemonPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.FinishScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.StartScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.interact.InteractPokemonPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetNicknamePacket
@@ -125,7 +130,10 @@ import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePartyPokemonT
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.net.messages.server.storage.pc.UnlinkPlayerFromPCPacket
import com.cobblemon.mod.common.net.messages.server.trade.*
import com.cobblemon.mod.common.net.serverhandling.*
import com.cobblemon.mod.common.net.serverhandling.battle.*
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectCancelledHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectedHandler
import com.cobblemon.mod.common.net.serverhandling.callback.party.PartyPokemonSelectedHandler
@@ -139,6 +147,8 @@ import com.cobblemon.mod.common.net.serverhandling.npc.SaveNPCHandler
import com.cobblemon.mod.common.net.serverhandling.pasture.PasturePokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pasture.UnpastureAllPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pasture.UnpasturePokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.FinishScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.interact.InteractPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetNicknameHandler
import com.cobblemon.mod.common.net.serverhandling.starter.RequestStarterScreenHandler
@@ -156,21 +166,6 @@ import com.cobblemon.mod.common.net.serverhandling.storage.pc.MovePartyPokemonTo
import com.cobblemon.mod.common.net.serverhandling.storage.pc.ReleasePartyPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.pc.SwapPCPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.pc.UnlinkPlayerFromPCHandler
import com.cobblemon.mod.common.net.serverhandling.trade.AcceptTradeRequestHandler
import com.cobblemon.mod.common.net.serverhandling.trade.CancelTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.ChangeTradeAcceptanceHandler
import com.cobblemon.mod.common.net.serverhandling.trade.OfferTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.UpdateTradeOfferHandler
import com.cobblemon.mod.common.net.messages.server.battle.*
import com.cobblemon.mod.common.net.serverhandling.battle.*
import com.cobblemon.mod.common.net.messages.client.pokedex.ServerConfirmedRegisterPacket
import com.cobblemon.mod.common.net.messages.client.trade.*
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.FinishScanningPacket
import com.cobblemon.mod.common.net.messages.server.trade.*
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.FinishScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanningHandler
import com.cobblemon.mod.common.net.serverhandling.trade.*
import com.cobblemon.mod.common.util.server
import net.minecraft.server.level.ServerPlayer
@@ -308,6 +303,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(PokedexDexSyncPacket.ID, PokedexDexSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(DexEntrySyncPacket.ID, DexEntrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(FishingBaitRegistrySyncPacket.ID, FishingBaitRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(FlowRegistrySyncPacket.ID, FlowRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
// Effects
list.add(PacketRegisterInfo(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler))
@@ -260,6 +260,10 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val MOVE_QUICKATTACK_TARGET = this.create("move.quickattack.target")
@JvmField
val MOVE_PURSUIT_TARGET = this.create("move.pursuit.target")
@JvmField
val MOVE_PSYCHIC_TARGET = this.create("move.psychic.target")
@JvmField
val IMPACT_NORMAL = this.create("impact.normal")
@@ -55,6 +55,8 @@ object CobblemonCriteria : PlatformRegistry<Registry<CriterionTrigger<*>>, Resou
val CAST_POKE_ROD = this.create("cast_poke_rod", SimpleCriterionTrigger(CastPokeRodCriterionCondition.CODEC))
val REEL_IN_POKEMON = this.create("reel_in_pokemon", SimpleCriterionTrigger(ReelInPokemonCriterionCondition.CODEC))
// Advancement criteria for [grow_tumblestone.json]
val PLANT_TUMBLESTONE = this.create("plant_tumblestone", SimpleCriterionTrigger(PlantTumblestoneCriterion.CODEC))
@@ -8,26 +8,30 @@
package com.cobblemon.mod.common.advancement.criterion
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.PrimitiveCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import java.util.Optional
import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.advancements.critereon.EntityPredicate
import net.minecraft.server.level.ServerPlayer
import net.minecraft.resources.ResourceLocation
class CastPokeRodContext(val baitId: ResourceLocation)
class CastPokeRodCriterionCondition(
playerCtx: Optional<ContextAwarePredicate>,
val hasBait: Boolean
): SimpleCriterionCondition<Boolean>(playerCtx) {
playerCtx: Optional<ContextAwarePredicate>,
val baitId: String
): SimpleCriterionCondition<CastPokeRodContext>(playerCtx) {
companion object {
val CODEC: Codec<CastPokeRodCriterionCondition> = RecordCodecBuilder.create { it.group(
EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter(CastPokeRodCriterionCondition::playerCtx),
PrimitiveCodec.BOOL.fieldOf("hasBait").forGetter(CastPokeRodCriterionCondition::hasBait)
).apply(it, ::CastPokeRodCriterionCondition) }
EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter(CastPokeRodCriterionCondition::playerCtx),
Codec.STRING.optionalFieldOf("baitId", "empty_bait").forGetter(CastPokeRodCriterionCondition::baitId)
).apply(it, { playerCtx, baitId -> CastPokeRodCriterionCondition(playerCtx, baitId.ifEmpty { "empty_bait" }) }) }
}
override fun matches(player: ServerPlayer, context: Boolean): Boolean {
return hasBait == context
override fun matches(player: ServerPlayer, context: CastPokeRodContext): Boolean {
return (context.baitId == this.baitId.asIdentifierDefaultingNamespace() || this.baitId.equals("empty_bait"))
}
}
}
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2023 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.advancement.criterion
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import java.util.Optional
import net.minecraft.advancements.critereon.ContextAwarePredicate
import net.minecraft.advancements.critereon.EntityPredicate
import net.minecraft.server.level.ServerPlayer
import net.minecraft.resources.ResourceLocation
class ReelInPokemonContext(val pokemonId : ResourceLocation, val baitId: ResourceLocation)
class ReelInPokemonCriterionCondition(
playerCtx: Optional<ContextAwarePredicate>,
val pokemonId: String,
val baitId: String
) : SimpleCriterionCondition<ReelInPokemonContext>(playerCtx) {
companion object {
val CODEC: Codec<ReelInPokemonCriterionCondition> = RecordCodecBuilder.create { it.group(
EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter { it.playerCtx },
Codec.STRING.optionalFieldOf("pokemonId", "any").forGetter { it.pokemonId },
Codec.STRING.optionalFieldOf("baitId", "empty_bait").forGetter { it.baitId }
).apply(it, { playerCtx, pokemonId, baitId -> ReelInPokemonCriterionCondition(playerCtx, pokemonId, baitId.ifEmpty { "empty_bait" }) }) }
}
override fun matches(player: ServerPlayer, context: ReelInPokemonContext): Boolean {
return (context.pokemonId == this.pokemonId.asIdentifierDefaultingNamespace() || this.pokemonId == "any") && (context.baitId == this.baitId.asIdentifierDefaultingNamespace() || this.baitId == "empty_bait")
}
}
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2023 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.ai.config.task
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
import com.cobblemon.mod.common.entity.ai.LookInDirectionTask
import com.cobblemon.mod.common.util.asExpression
import net.minecraft.world.entity.LivingEntity
class LookInDirectionTaskConfig : SingleTaskConfig {
var yaw: Expression = "0".asExpression()
var pitch: Expression = "0".asExpression()
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
) = LookInDirectionTask(yaw, pitch)
}
@@ -172,4 +172,15 @@ The 'flee_attacker' type creates a task that will make the entity flee from its
The 'flee_nearest_hostile' type creates a task that will make the entity flee from the nearest hostile entity when it is hurt.
- `condition`: A MoLang expression (with `q.entity` as the entity) that determines if this entire task should exist on the entity or not. This runs at the time of entity creation, not on tick.
- `speedMultiplier`: A MoLang expression that determines the speed multiplier for fleeing from the nearest hostile. Defaults to 0.5 which is an elevated speed to usual.
- `desiredDistance`: A MoLang expression that determines the distance from the nearest hostile that the entity should try to maintain. Defaults to 9.
- `desiredDistance`: A MoLang expression that determines the distance from the nearest hostile that the entity should try to maintain. Defaults to 9.
### run_script
The 'run_script' type creates a task that will run a MoLang script every tick on this entity. This has the risk of being very laggy
if the script is doing expensive things. Remember to use `q.entity.world.game_time` and `math.mod` to do intermittent actions inside
the script to avoid lagging the game.
- `script`: A resource location identifying the script to run. This should be in the `molang` datapack folder.
### look_in_direction
The 'look_in_direction' type creates a task that will make the entity look in a specific direction at all times where they aren't being told to look elsewhere.
- `yaw`: A MoLang expression that determines the yaw angle (degrees) of the entity. Defaults to `0`.
- `pitch`: A MoLang expression that determines the pitch angle (degrees) of the entity. Defaults to `0`.
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2023 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.ai.config.task
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.ai.BrainConfigurationContext
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
/**
* Runs a registered MoLang script (from the molang datapack folder) each tick.
*
* It's up to the user to not make expensive ass scripts. Remember you can access the game time
* from q.entity.level.game_time which can be used with a modulo operator to make something intermittent.
*
* @author Hiroku
* @since December 2nd, 2024
*/
class RunScript : SingleTaskConfig {
val script = cobblemonResource("dummy")
override fun createTask(
entity: LivingEntity,
brainConfigurationContext: BrainConfigurationContext
): BehaviorControl<in LivingEntity>? = BehaviorBuilder.create {
it.group(
it.registered(MemoryModuleType.LOOK_TARGET) // I think I need to have at least something here?
).apply(it) { _ ->
Trigger { world, entity, _ ->
runtime.withQueryValue("entity", (entity as? PosableEntity)?.struct ?: entity.asMostSpecificMoLangValue())
return@Trigger CobblemonScripts.run(script, runtime) == DoubleValue.ONE
}
}
}
}
@@ -58,6 +58,8 @@ fun interface TaskConfig {
"calm_down" to CalmDownTaskConfig::class.java,
"flee_attacker" to FleeAttackerTaskConfig::class.java,
"flee_nearest_hostile" to FleeNearestHostileTaskConfig::class.java,
"run_script" to RunScript::class.java,
"look_in_direction" to LookInDirectionTaskConfig::class.java,
)
val runtime = MoLangRuntime().setup()
@@ -94,7 +94,6 @@ open class PokemonBattle(
.filterIsInstance<LastBattleCriticalHitsEvolutionProgress>()
.forEach { it.reset() }
}
actor.setupStruct()
}
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.battles.model.actor
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.ForcePassActionResponse
@@ -36,7 +37,7 @@ abstract class BattleActor(
pokemonList.forEach { it.actor = this }
}
val struct = QueryStruct(hashMapOf())
val struct = asMoLangValue()
lateinit var showdownId: String
lateinit var battle: PokemonBattle
@@ -139,10 +140,6 @@ abstract class BattleActor(
battle.writeShowdownAction(">$showdownId ${showdownMessages.joinToString()}")
}
open fun setupStruct() {
struct.addFunction("uuid") { StringValue(uuid.toString()) }
}
abstract fun getName(): MutableComponent
/**
@@ -9,8 +9,11 @@
package com.cobblemon.mod.common.api.data
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.util.endsWith
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.io.File
import java.util.concurrent.ExecutionException
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.resources.ResourceManager
@@ -42,7 +45,24 @@ interface JsonDataRegistry<T> : DataRegistry {
val resourcePath: String
override fun reload(manager: ResourceManager) {
this.reload(Cobblemon.implementation.reloadJsonRegistry(this, manager))
val data = hashMapOf<ResourceLocation, T>()
manager.listResources(resourcePath) { path -> path.endsWith(JSON_EXTENSION) }.forEach { (identifier, resource) ->
if (identifier.namespace == "pixelmon") {
return@forEach
}
resource.open().use { stream ->
stream.bufferedReader().use { reader ->
val resolvedIdentifier = ResourceLocation.fromNamespaceAndPath(identifier.namespace, File(identifier.path).nameWithoutExtension)
try {
data[resolvedIdentifier] = gson.fromJson(reader, typeToken.type)
} catch (exception: Exception) {
throw ExecutionException("Error loading JSON for data: $identifier", exception)
}
}
}
}
reload(data)
}
/**
@@ -21,13 +21,13 @@ import com.cobblemon.mod.common.util.adapters.DialogueTextAdapter
import com.cobblemon.mod.common.util.adapters.ExpressionAdapter
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.TextAdapter
import com.cobblemon.mod.common.util.adapters.TranslatedTextAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.network.chat.MutableComponent
import net.minecraft.server.level.ServerPlayer
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
/**
@@ -56,7 +56,7 @@ object Dialogues : JsonDataRegistry<Dialogue> {
.registerTypeAdapter(DialogueText::class.java, DialogueTextAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(MutableComponent::class.java, TextAdapter)
.registerTypeAdapter(MutableComponent::class.java, TranslatedTextAdapter)
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.also { gsonObservable.emit(it) }
.create()
@@ -192,6 +192,8 @@ object CobblemonEvents {
val POKEMON_GAINED = EventObservable<PokemonGainedEvent>()
@JvmField
val POKEMON_SEEN = EventObservable<PokemonSeenEvent>()
@JvmField
val POKEDEX_DATA_CHANGED = EventObservable<PokedexDataChangedEvent>()
// Fishing
@JvmField
@@ -19,8 +19,8 @@ import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
* @author Segfault Guy
* @since April 6th, 2023
*/
data class BattleFaintedEvent (
override val battle: PokemonBattle,
val killed: BattlePokemon,
val context: BattleContext
data class BattleFaintedEvent(
override val battle: PokemonBattle,
val killed: BattlePokemon,
val context: BattleContext
) : BattleEvent
@@ -18,8 +18,6 @@ import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
* @since March 25th 2023
*/
data class BattleFledEvent (
override val battle: PokemonBattle,
val player: PlayerBattleActor
) : BattleEvent
@@ -8,8 +8,14 @@
package com.cobblemon.mod.common.api.events.battles
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.util.asArrayValue
/**
* Event fired when a battle is won by some number of [BattleActor]s.
@@ -22,4 +28,20 @@ data class BattleVictoryEvent (
val winners : List<BattleActor>,
val losers : List<BattleActor>,
val wasWildCapture : Boolean
) : BattleEvent
) : BattleEvent {
val context = mutableMapOf<String, MoValue>(
"battle" to battle.struct,
"winners" to winners.asArrayValue { it.struct },
"losers" to losers.asArrayValue { it.struct },
"was_wild_capture" to DoubleValue(wasWildCapture),
"player_winners" to winners.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
"player_losers" to losers.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
"npc_winners" to winners.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
"npc_losers" to losers.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
"wild_pokemon_winners" to winners.filter { it.type == ActorType.WILD }.asArrayValue { it.struct },
"wild_pokemon_losers" to losers.filter { it.type == ActorType.WILD }.asArrayValue { it.struct },
"players" to battle.actors.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
"npcs" to battle.actors.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
"wild_pokemon" to battle.actors.filter { it.type == ActorType.WILD }.asArrayValue { it.struct }
)
}
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2023 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.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.pokedex.FormDexRecord
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.getPlayer
import com.mojang.datafixers.util.Either
import java.util.UUID
/**
* Event that fires when a Pokémon's information is gained or updated in the Pokédex.
*/
data class PokedexDataChangedEvent(
val dataSource: Either<PokedexEntityData, Pokemon>,
val knowledge: PokedexEntryProgress,
val playerUUID: UUID,
val record: FormDexRecord
) {
val pokedexManager: AbstractPokedexManager
get() = record.speciesDexRecord.pokedexManager
val context = mutableMapOf<String, MoValue>(
"player" to (playerUUID.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO),
"pokemon" to dataSource.map({ DoubleValue.ZERO }, { it.struct }),
"data" to dataSource.map({ it.struct }, { DoubleValue.ZERO }),
"knowledge" to StringValue(knowledge.name.lowercase()),
"pokedex" to pokedexManager.struct
)
}
@@ -8,12 +8,23 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.core.registries.Registries
import net.minecraft.server.level.ServerPlayer
data class PokemonCapturedEvent (
val pokemon: Pokemon,
val player: ServerPlayer,
val pokeBallEntity: EmptyPokeBallEntity
)
) {
val context = mutableMapOf<String, MoValue>(
"pokemon" to pokemon.struct,
"player" to player.asMoLangValue(),
"poke_ball" to pokeBallEntity.struct,
"item" to player.level().itemRegistry.wrapAsHolder(pokeBallEntity.pokeBall.item).asMoLangValue(Registries.ITEM)
)
}
@@ -14,5 +14,4 @@ import java.util.UUID
data class PokemonGainedEvent(
val playerId: UUID,
val pokemon: Pokemon
) {
}
)
@@ -9,11 +9,9 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.server.level.ServerPlayer
import java.util.UUID
data class PokemonSeenEvent(
val playerId: UUID,
val pokemon: Pokemon
) {
}
)
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.gui
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import net.minecraft.client.gui.components.AbstractWidget
import net.minecraft.client.gui.components.Renderable
import net.minecraft.client.gui.components.events.GuiEventListener
@@ -22,9 +23,9 @@ abstract class ParentWidget(
pX: Int, pY: Int,
pWidth: Int, pHeight: Int,
component: Component
): Renderable, AbstractWidget(pX, pY, pWidth, pHeight, component) {
): CobblemonRenderable, AbstractWidget(pX, pY, pWidth, pHeight, component) {
private val children: MutableList<GuiEventListener> = mutableListOf()
val children: MutableList<GuiEventListener> = mutableListOf()
/**
* Adds Widget to the children list
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.item.ability
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.abilities.*
import com.cobblemon.mod.common.item.interactive.ability.AbilityTypeChanger
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -33,7 +34,7 @@ interface AbilityChanger<T : PotentialAbility> {
* @param pokemon The [Pokemon] being queried.
* @return A set containing all their legal abilities associated with [type], can be empty.
*/
fun queryPossible(pokemon: Pokemon): Set<AbilityTemplate>
fun queryPossible(pokemon: Pokemon): Set<Pair<AbilityTemplate, Priority>>
/**
* Attempts to update the ability of the given [pokemon].
@@ -22,11 +22,20 @@ import com.cobblemon.mod.common.CobblemonActivities
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.dialogue.PlayerDialogueFaceProvider
import com.cobblemon.mod.common.api.dialogue.ReferenceDialogueFaceProvider
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.NPCProvider
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
import com.cobblemon.mod.common.api.pokedex.CaughtCount
import com.cobblemon.mod.common.api.pokedex.CaughtPercent
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.PokedexManager
import com.cobblemon.mod.common.api.pokedex.SeenCount
import com.cobblemon.mod.common.api.pokedex.SeenPercent
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
@@ -37,17 +46,24 @@ import com.cobblemon.mod.common.api.storage.pc.PCPosition
import com.cobblemon.mod.common.api.storage.pc.PCStore
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunctions
import com.cobblemon.mod.common.entity.PosableEntity
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.PokemonMoveControl
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.util.*
import com.mojang.datafixers.util.Either
import java.util.UUID
import kotlin.math.sqrt
import net.minecraft.commands.arguments.EntityAnchorArgument
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
@@ -59,14 +75,23 @@ import net.minecraft.nbt.StringTag
import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.tags.TagKey
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.damagesource.DamageTypes
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.LightningBolt
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.Level
import net.minecraft.world.level.Level.ExplosionInteraction
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.dimension.DimensionType
@@ -81,6 +106,31 @@ import net.minecraft.world.phys.Vec3
*/
object MoLangFunctions {
val generalFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>(
"print" to java.util.function.Function { params ->
val message = params.get<MoValue>(0).asString()
Cobblemon.LOGGER.info(message)
},
"set_query" to java.util.function.Function { params ->
val variable = params.getString(0)
val value = params.get<MoValue>(1)
params.environment.query.addFunction(variable) { value }
return@Function value
},
"replace" to java.util.function.Function { params ->
val text = params.getString(0)
val search = params.getString(1)
val replace = params.getString(2)
return@Function StringValue(text.replace(search, replace))
},
"is_blank" to java.util.function.Function { params ->
val arg = params.get<MoValue>(0)
return@Function DoubleValue((arg is StringValue && (arg.value.isBlank() || arg.value.toDoubleOrNull() == 0.0)) || (arg is DoubleValue && arg.value == 0.0))
},
"run_command" to java.util.function.Function { params ->
val command = params.getString(0)
val server = server() ?: return@Function DoubleValue.ZERO
server.commands.performPrefixedCommand(server.createCommandSourceStack(), command)
},
"is_int" to java.util.function.Function { params -> DoubleValue(params.get<MoValue>(0).asString().isInt()) },
"is_number" to java.util.function.Function { params -> DoubleValue(params.get<MoValue>(0).asString().toDoubleOrNull() != null) },
"to_number" to java.util.function.Function { params -> DoubleValue(params.get<MoValue>(0).asString().toDoubleOrNull() ?: 0.0) },
@@ -115,19 +165,93 @@ object MoLangFunctions {
runtime.environment.variable = params.environment.variable
runtime.environment.context = params.environment.context
val script = params.getString(0).asIdentifierDefaultingNamespace()
CobblemonScripts.run(script, runtime) ?: DoubleValue(0)
CobblemonScripts.run(script, runtime) ?: DoubleValue.ZERO
},
"run_command" to java.util.function.Function { params ->
val command = params.getString(0)
server()?.let {
return@Function DoubleValue(it.commands.dispatcher.execute(command, it.createCommandSourceStack()))
} ?: return@Function DoubleValue.ZERO
"run_molang" to java.util.function.Function { params ->
val runtime = MoLangRuntime()
runtime.environment.query = params.environment.query
runtime.environment.variable = params.environment.variable
runtime.environment.context = params.environment.context
val expression = params.getString(0).asExpressionLike()
runtime.resolve(expression)
},
)
val biomeFunctions = mutableListOf<(Holder<Biome>) -> HashMap<String, java.util.function.Function<MoParams, Any>>>()
val worldFunctions = mutableListOf<(Holder<Level>) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ worldHolder ->
val world = worldHolder.value()
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("game_time") { _ -> DoubleValue(world.gameTime.toDouble()) }
map.put("time_of_day") {
val time = world.dayTime % 24000
return@put DoubleValue(time.toDouble())
}
map.put("server") { _ -> server()?.asMoLangValue() ?: DoubleValue.ZERO }
map.put("is_raining_at") { params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
return@put DoubleValue(world.isRainingAt(BlockPos(x, y, z)))
}
map.put("is_chunk_loaded_at") { params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
return@put DoubleValue(world.isLoaded(BlockPos(x, y, z)))
}
map.put("is_thundering") { _ -> DoubleValue(world.isThundering) }
map.put("is_raining") { _ -> DoubleValue(world.isRaining) }
map.put("set_block") { params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
val block = world.blockRegistry.get(params.getString(3).asIdentifierDefaultingNamespace())
?: run {
Cobblemon.LOGGER.error("Unknown block: ${params.getString(3)}")
return@put DoubleValue.ZERO
}
world.setBlock(BlockPos(x, y, z), block.defaultBlockState(), Block.UPDATE_ALL)
}
map.put("get_block") { params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
val block = world.getBlockState(BlockPos(x, y, z)).block
return@put world.blockRegistry.wrapAsHolder(block).asMoLangValue(Registries.BLOCK)
}
map.put("spawn_explosion") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val range = params.getDouble(3).toFloat()
world.explode(null, x, y, z, range, ExplosionInteraction.valueOf(params.getStringOrNull(4)?.uppercase() ?: ExplosionInteraction.TNT.name))
}
map.put("spawn_lightning") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val lightning = LightningBolt(EntityType.LIGHTNING_BOLT, world)
lightning.setPos(x, y, z)
world.addFreshEntity(lightning)
return@put DoubleValue.ONE
}
map.put("get_entities_around") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val range = params.getDouble(3) * 2
val entities = world.getEntities(null, AABB.ofSize(Vec3(x, y, z), range, range, range))
return@put entities
.filterIsInstance<LivingEntity>()
.map { it.asMostSpecificMoLangValue() }
.asArrayValue()
}
return@mutableListOf map
}
)
val biomeFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
val worldFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
val dimensionTypeFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
val blockFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
val dimensionTypeFunctions = mutableListOf<(Holder<DimensionType>) -> HashMap<String, java.util.function.Function<MoParams, Any>>>()
val blockFunctions = mutableListOf<(Holder<Block>) -> HashMap<String, java.util.function.Function<MoParams, Any>>>()
val playerFunctions = mutableListOf<(Player) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ player ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
@@ -141,7 +265,7 @@ object MoLangFunctions {
map.put("saturation_level") { _ -> DoubleValue(player.foodData.saturationLevel) }
map.put("tell") { params ->
val message = params.getString(0).text()
val overlay = params.getBooleanOrNull(1) ?: false
val overlay = params.getBooleanOrNull(1) == true
player.displayClientMessage(message, overlay)
}
map.put("teleport") { params ->
@@ -160,7 +284,12 @@ object MoLangFunctions {
environment.query = player.asMoLangValue()
environment
}
map.put("is_player") { DoubleValue.ONE }
if (player is ServerPlayer) {
map.put("run_command") { params ->
val command = params.getString(0)
player.server.commands.performPrefixedCommand(player.createCommandSourceStack(), command)
}
map.put("is_party_at_full_health") { _ ->
DoubleValue(player.party().none(Pokemon::canBeHealed)) }
map.put("can_heal_at_healer") { params ->
@@ -188,11 +317,46 @@ object MoLangFunctions {
map.put("save_data") { _ -> Cobblemon.molangData.save(player.uuid) }
map.put("in_battle") { DoubleValue(player.isInBattle()) }
map.put("battle") { player.getBattleState()?.first?.struct ?: DoubleValue.ZERO }
map.put("get_npc_data") { params ->
val npcId = (params.get<MoValue>(0) as? ObjectValue<NPCEntity>)?.obj?.stringUUID ?: params.getString(0)
val data = Cobblemon.molangData.load(player.uuid)
if (data.map.containsKey(npcId)) {
return@put data.map[npcId]!!
} else {
val vars = VariableStruct()
data.map[npcId] = vars
return@put vars
}
}
map.put("get_npc_variable") { params ->
val npcId = (params.get<MoValue>(0) as? ObjectValue<NPCEntity>)?.obj?.stringUUID ?: params.getString(0)
val variable = params.getString(1)
val data = Cobblemon.molangData.load(player.uuid)
if (data.map.containsKey(npcId)) {
return@put (data.map[npcId] as VariableStruct).map[variable] ?: DoubleValue.ZERO
} else {
return@put DoubleValue.ZERO
}
}
map.put("set_npc_variable") { params ->
val npcId = (params.get<MoValue>(0) as? ObjectValue<NPCEntity>)?.obj?.stringUUID ?: params.getString(0)
val variable = params.getString(1)
val value = params.get<MoValue>(2)
val saveAfterwards = params.getBooleanOrNull(3) != false
val data = Cobblemon.molangData.load(player.uuid)
val npcData = data.map.getOrPut(npcId) { VariableStruct() } as VariableStruct
npcData.map[variable] = value
if (saveAfterwards) {
Cobblemon.molangData.save(player.uuid)
}
return@put DoubleValue.ONE
}
map.put("pokedex") { player.pokedex().struct }
}
map
}
)
val entityFunctions = mutableListOf<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
val entityFunctions: MutableList<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ entity ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("uuid") { _ -> StringValue(entity.uuid.toString()) }
@@ -201,6 +365,28 @@ object MoLangFunctions {
val source = DamageSource(entity.level().registryAccess().registryOrThrow(Registries.DAMAGE_TYPE).getHolder(DamageTypes.GENERIC).get())
entity.hurt(source, amount.toFloat())
}
map.put("walk_to") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val speedMultiplier = params.getDoubleOrNull(3) ?: 0.35
if (entity is PathfinderMob) {
if (entity.brain.checkMemory(MemoryModuleType.WALK_TARGET, MemoryStatus.REGISTERED)) {
entity.brain.setMemory(MemoryModuleType.WALK_TARGET, WalkTarget(Vec3(x, y, z), speedMultiplier.toFloat(), 1))
entity.brain.setMemory(MemoryModuleType.LOOK_TARGET, BlockPosTracker(Vec3(x, y + entity.eyeHeight, z)))
} else {
entity.navigation.moveTo(x, y, z, speedMultiplier)
entity.lookControl.setLookAt(Vec3(x, y + entity.eyeHeight, z))
}
}
}
map.put("has_walk_target") { _ ->
if (entity is PathfinderMob) {
DoubleValue(entity.brain.getMemory(MemoryModuleType.WALK_TARGET).isPresent || entity.isPathFinding)
} else {
DoubleValue.ZERO
}
}
map.put("is_sneaking") { _ -> DoubleValue(entity.isShiftKeyDown) }
map.put("is_sprinting") { _ -> DoubleValue(entity.isSprinting) }
map.put("is_flying") { _ -> DoubleValue(entity.isFallFlying) }
@@ -212,8 +398,14 @@ object MoLangFunctions {
map.put("is_on_fire") { _ -> DoubleValue(entity.isOnFire) }
map.put("is_invisible") { _ -> DoubleValue(entity.isInvisible) }
map.put("is_sleeping") { _ -> DoubleValue(entity.isSleeping) }
map.put("is_riding") { _ -> DoubleValue(entity.isPassenger()) }
map.put("is_riding") { _ -> DoubleValue(entity.isPassenger) }
map.put("health") { _ -> DoubleValue(entity.health) }
map.put("distance_to_pos") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
return@put DoubleValue(sqrt(entity.distanceToSqr(Vec3(x, y, z))))
}
map.put("max_health") { _ -> DoubleValue(entity.maxHealth) }
map.put("name") { _ -> StringValue(entity.effectiveName().string) }
map.put("yaw") { _ -> DoubleValue(entity.yRot.toDouble()) }
@@ -255,6 +447,21 @@ object MoLangFunctions {
return@put DoubleValue.ZERO
}
}
map.put("get_nearby_entities") { params ->
val distance = params.getDouble(0)
val entities = entity.level().getEntities(entity, AABB.ofSize(entity.position(), distance, distance, distance))
return@put entities
.filterIsInstance<LivingEntity>()
.map { it.asMostSpecificMoLangValue() }
.asArrayValue()
}
if (entity is PosableEntity) {
map.put("play_animation") { params ->
val animation = params.getString(0)
val packet = PlayPosableAnimationPacket(entity.id, setOf(animation), emptyList())
packet.sendToPlayersAround(entity.x, entity.y, entity.z, 64.0, entity.level().dimension())
}
}
map
}
)
@@ -315,9 +522,9 @@ object MoLangFunctions {
}
}
return@put DoubleValue(1)
return@put DoubleValue.ONE
}
return@put DoubleValue(0)
return@put DoubleValue.ZERO
}
map.put("put_pokemon_in_healer") { params ->
val healer = npc.level().getBlockEntity(params.get<ArrayStruct>(0).asBlockPos(), CobblemonBlockEntities.HEALING_MACHINE).orElse(null) ?: return@put DoubleValue.ZERO
@@ -336,6 +543,8 @@ object MoLangFunctions {
map.put("environment") { _ -> npc.runtime.environment }
map.put("party") { npc.party?.struct ?: DoubleValue.ZERO }
map.put("has_party") { DoubleValue(npc.party != null) }
map.put("is_npc") { DoubleValue.ONE }
map.put("can_battle") { DoubleValue(npc.party?.any { it.currentHealth > 0 } == true || npc.npc.party?.isStatic == false) }
map
}
)
@@ -355,10 +564,26 @@ object MoLangFunctions {
return@put actor.struct
}
map.put("stop") { _ -> battle.stop() }
map.put("actors") { battle.actors.toList().asArrayValue { it.struct } }
map
}
)
val battleActorFunctions = mutableListOf<(BattleActor) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ battleActor ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("is_npc") { DoubleValue(battleActor.type == ActorType.NPC) }
map.put("is_player") { DoubleValue(battleActor.type == ActorType.PLAYER) }
map.put("is_wild") { DoubleValue(battleActor.type == ActorType.WILD) }
if (battleActor is NPCBattleActor) {
map.put("npc") { battleActor.entity.struct }
} else if (battleActor is PlayerBattleActor) {
map.put("player") { battleActor.entity?.asMoLangValue() ?: DoubleValue.ZERO }
}
return@mutableListOf map
}
)
val pokemonFunctions = mutableListOf<(Pokemon) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ pokemon ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
@@ -383,6 +608,7 @@ object MoLangFunctions {
}
map.put("is_wild") { DoubleValue(pokemon.entity?.let { it.ownerUUID == null } == true) }
map.put("is_shiny") { DoubleValue(pokemon.shiny) }
map.put("species") { pokemon.species.struct }
map.put("form") { StringValue(pokemon.form.name) }
map.put("weight") { DoubleValue(pokemon.species.weight.toDouble()) }
map.put("matches") { params -> DoubleValue(params.getString(0).toProperties().matches(pokemon)) }
@@ -397,6 +623,7 @@ object MoLangFunctions {
map.put("is_moving") { DoubleValue((pokemonEntity.moveControl as? PokemonMoveControl)?.hasWanted() == true) }
map.put("is_flying") { DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.FLYING)) }
map.put("has_aspect") { DoubleValue(it.getString(0) in pokemonEntity.aspects) }
map.put("is_pokemon") { DoubleValue.ONE }
map
}
)
@@ -535,10 +762,140 @@ object MoLangFunctions {
}
)
fun Holder<Biome>.asBiomeMoLangValue() = asMoLangValue(Registries.BIOME).addFunctions(biomeFunctions)
fun Holder<Level>.asWorldMoLangValue() = asMoLangValue(Registries.DIMENSION).addFunctions(worldFunctions)
fun Holder<Block>.asBlockMoLangValue() = asMoLangValue(Registries.BLOCK).addFunctions(blockFunctions)
fun Holder<DimensionType>.asDimensionTypeMoLangValue() = asMoLangValue(Registries.DIMENSION_TYPE).addFunctions(dimensionTypeFunctions)
val serverFunctions: MutableList<(MinecraftServer) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ server ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("get_world") { params ->
val world = server.getLevel(
ResourceKey.create(
Registries.DIMENSION,
params.getString(0).asIdentifierDefaultingNamespace(namespace = "minecraft")
)
) ?: return@put DoubleValue.ZERO
return@put world
.registryAccess()
.registryOrThrow(Registries.DIMENSION)
.wrapAsHolder(world)
.asWorldMoLangValue()
}
map.put("broadcast") { params ->
val message = params.getString(0)
server.playerList.broadcastSystemMessage(message.text(), params.getBooleanOrNull(1) == true)
return@put DoubleValue.ONE
}
map.put("get_player_by_uuid") { params ->
val uuid = UUID.fromString(params.getString(0))
val player = server.playerList.getPlayer(uuid) ?: return@put DoubleValue.ZERO
return@put player.asMoLangValue()
}
map.put("get_player_by_username") { params ->
val username = params.getString(0)
val player = server.playerList.getPlayerByName(username) ?: return@put DoubleValue.ZERO
return@put player.asMoLangValue()
}
map.put("data") { params ->
val data = Cobblemon.molangData.load(UUID(0L, 0L))
return@put data
}
map.put("save_data") {
Cobblemon.molangData.save(UUID(0L, 0L))
return@put DoubleValue.ONE
}
// Maybe later...
// map.put("stop") { params ->
// Cobblemon.LOGGER.warn("Server is being stopped from a MoLang script.")
// server.stopServer()
// }
return@mutableListOf map
}
)
val pokedexFunctions: MutableList<(AbstractPokedexManager) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ pokedex ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("get_species_record") { params ->
val speciesId = params.getString(0).asIdentifierDefaultingNamespace()
pokedex.speciesRecords[speciesId]?.struct ?: QueryStruct(hashMapOf())
}
map.put("has_seen") { params ->
val speciesId = params.getString(0).asIdentifierDefaultingNamespace()
val formName = params.getStringOrNull(1)
if (formName == null) {
return@put DoubleValue(pokedex.getHighestKnowledgeForSpecies(speciesId).ordinal >= PokedexEntryProgress.ENCOUNTERED.ordinal)
} else {
return@put DoubleValue((pokedex.getSpeciesRecord(speciesId)?.getFormRecord(formName)?.knowledge?.ordinal ?: 0) >= PokedexEntryProgress.ENCOUNTERED.ordinal)
}
}
map.put("has_caught") { params ->
val speciesId = params.getString(0).asIdentifierDefaultingNamespace()
val formName = params.getStringOrNull(1)
if (formName == null) {
return@put DoubleValue(pokedex.getHighestKnowledgeForSpecies(speciesId) == PokedexEntryProgress.CAUGHT)
} else {
return@put DoubleValue(pokedex.getSpeciesRecord(speciesId)?.getFormRecord(formName)?.knowledge == PokedexEntryProgress.CAUGHT)
}
}
map.put("caught_count") { DoubleValue(pokedex.getGlobalCalculatedValue(CaughtCount)) }
map.put("seen_count") { DoubleValue(pokedex.getGlobalCalculatedValue(SeenCount)) }
map.put("caught_percent") { DoubleValue(pokedex.getGlobalCalculatedValue(CaughtPercent)) }
map.put("seen_percent") { DoubleValue(pokedex.getGlobalCalculatedValue(SeenPercent)) }
if (pokedex is PokedexManager) {
map.put("player_id") { StringValue(pokedex.uuid.toString()) }
map.put("see") { params ->
val pokemon = params.get<ObjectValue<Pokemon>>(0).obj
pokedex.encounter(pokemon)
return@put DoubleValue.ONE
}
map.put("catch") { params ->
val pokemon = params.get<ObjectValue<Pokemon>>(0).obj
pokedex.catch(pokemon)
return@put DoubleValue.ONE
}
}
map
}
)
val speciesFunctions: MutableList<(Species) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ species ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("identifier") { StringValue(species.resourceIdentifier.toString()) }
map.put("name") { StringValue(species.name) }
map.put("primary_type") { StringValue(species.primaryType.name) }
map.put("secondary_type") { StringValue(species.secondaryType?.name ?: "null") }
map.put("experience_group") { StringValue(species.experienceGroup.name) }
map.put("height") { DoubleValue(species.height) }
map.put("weight") { DoubleValue(species.weight) }
map.put("base_scale") { DoubleValue(species.baseScale) }
map.put("hitbox_width") { DoubleValue(species.hitbox.width) }
map.put("hitbox_height") { DoubleValue(species.hitbox.height) }
map.put("hitbox_fixed") { DoubleValue(species.hitbox.fixed) }
map.put("catch_rate") { DoubleValue(species.catchRate) }
map
}
)
fun Holder<Biome>.asBiomeMoLangValue() = asMoLangValue(Registries.BIOME).addFunctions(biomeFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
fun Holder<Level>.asWorldMoLangValue() = asMoLangValue(Registries.DIMENSION).addFunctions(worldFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
fun Holder<Block>.asBlockMoLangValue() = asMoLangValue(Registries.BLOCK).addFunctions(blockFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
fun MinecraftServer.asMoLangValue() = ObjectValue(this).addStandardFunctions().addFunctions(serverFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
fun Holder<DimensionType>.asDimensionTypeMoLangValue() = asMoLangValue(Registries.DIMENSION_TYPE).addFunctions(dimensionTypeFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
fun Player.asMoLangValue(): ObjectValue<Player> {
val value = ObjectValue(
obj = this,
@@ -586,6 +943,17 @@ object MoLangFunctions {
return value
}
fun PokemonEntity.asMoLangValue(): ObjectValue<PokemonEntity> {
val value = ObjectValue(
obj = this,
stringify = { it.pokemon.uuid.toString() }
)
value.addFunctions(entityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
value.addFunctions(pokemonFunctions.flatMap { it(this.pokemon).entries.map { it.key to it.value } }.toMap()) // Convenience
value.addFunctions(pokemonEntityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
return value
}
fun PokemonBattle.asMoLangValue(): ObjectValue<PokemonBattle> {
val value = ObjectValue(
obj = this,
@@ -595,6 +963,15 @@ object MoLangFunctions {
return value
}
fun BattleActor.asMoLangValue(): ObjectValue<BattleActor> {
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
value.addFunctions(battleActorFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
return value
}
fun SpawningContext.asMoLangValue(): ObjectValue<SpawningContext> {
val value = ObjectValue(
obj = this,
@@ -604,6 +981,18 @@ object MoLangFunctions {
return value
}
/**
* Different functions exist depending on
*/
fun LivingEntity.asMostSpecificMoLangValue(): ObjectValue<out LivingEntity> {
return when (this) {
is Player -> asMoLangValue()
is PokemonEntity -> struct
is NPCEntity -> struct
else -> ObjectValue(this).also { it.addStandardFunctions().addFunctions(entityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap()) }
}
}
fun <T> Holder<T>.asMoLangValue(key: ResourceKey<Registry<T>>): ObjectValue<Holder<T>> {
val value = ObjectValue(
obj = this,
@@ -633,6 +1022,22 @@ object MoLangFunctions {
return this
}
fun QueryStruct.addPokedexFunctions(pokedexManager: AbstractPokedexManager): QueryStruct {
val addedFunctions = pokedexFunctions
.flatMap { it.invoke(pokedexManager).entries }
.associate { it.key to it.value }
functions.putAll(addedFunctions)
return this
}
fun QueryStruct.addSpeciesFunctions(species: Species): QueryStruct {
val addedFunctions = speciesFunctions
.flatMap { it.invoke(species).entries }
.associate { it.key to it.value }
functions.putAll(addedFunctions)
return this
}
fun QueryStruct.addPokemonFunctions(pokemon: Pokemon): QueryStruct {
val addedFunctions = pokemonFunctions
.flatMap { it.invoke(pokemon).entries }
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2023 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.moves
enum class MoveEffectiveness(val showdownId: Int, val multiplier: Double) {
NORMAL(0, 1.0),
WEAKNESS(1, 2.0),
RESISTANCE(2, 0.5),
IMMUNITY(3, 0.0);
companion object {
fun of(showdownId: Int): MoveEffectiveness {
return entries.find { it.showdownId == showdownId }
?: throw IllegalArgumentException("Unknown MoveEffectiveness: $showdownId")
}
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 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.moves
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.runner.ShowdownService
object TypeChart {
private val movesEffectiveness = mutableMapOf<ElementalType, MutableMap<ElementalType, MoveEffectiveness>>()
fun load() {
movesEffectiveness.clear()
val typeChartJson = ShowdownService.service.getTypeChart()
typeChartJson.keySet().forEach { type ->
val defenderType = ElementalTypes.get(type) ?: return@forEach
val moveTypes = typeChartJson.get(type).asJsonObject.get("damageTaken").asJsonObject
moveTypes.keySet().forEach innerLoop@ {
val moveType = ElementalTypes.get(it) ?: return@innerLoop
val moveEffectiveness = moveTypes.get(it).asInt
val defenderMap = movesEffectiveness.getOrPut(moveType) { mutableMapOf() }
defenderMap[defenderType] = MoveEffectiveness.of(moveEffectiveness)
}
}
}
fun getMoveEffectiveness(moveType: ElementalType, defenderTypes: List<ElementalType?>): Double {
var multiplier = 1.0
defenderTypes.filterNotNull().forEach {
multiplier *= getMoveEffectiveness(moveType, it).multiplier
}
return multiplier
}
private fun getMoveEffectiveness(moveType: ElementalType, defenderType: ElementalType): MoveEffectiveness {
return movesEffectiveness[moveType]?.get(defenderType) ?: throw IllegalArgumentException("Unknown effectiveness type: $moveType $defenderType")
}
}
@@ -38,7 +38,8 @@ class NPCClass {
var resourceIdentifier: ResourceLocation = cobblemonResource("dummy")
var names: MutableList<Component> = mutableListOf()
var aspects: MutableSet<String> = mutableSetOf() // These only make sense when applied via presets
var hitbox = EntityDimensions.scalable(0.6F, 1.8F)
var hitbox = EntityDimensions.scalable(0.6F, 1.8F).withEyeHeight(1.62F)
var modelScale: Float = 0.94F
var battleConfiguration = NPCBattleConfiguration()
var interaction: NPCInteractConfiguration? = null
var canDespawn = true
@@ -48,6 +49,7 @@ class NPCClass {
var party: NPCPartyProvider? = null
var skill: Int = 0
var autoHealParty: Boolean = true
var randomizePartyOrder: Boolean = false
var battleTheme: ResourceLocation? = null
var ai: MutableList<BrainConfig> = mutableListOf()
var isMovable: Boolean = true
@@ -83,6 +85,7 @@ class NPCClass {
}
buffer.writeInt(skill)
buffer.writeBoolean(autoHealParty)
buffer.writeBoolean(randomizePartyOrder)
buffer.writeMapK(size = IntSize.U_BYTE, map = variables) { (key, value) ->
buffer.writeString(key)
buffer.writeString(value.asString())
@@ -127,6 +130,7 @@ class NPCClass {
}.toMutableList()
skill = buffer.readInt()
autoHealParty = buffer.readBoolean()
randomizePartyOrder = buffer.readBoolean()
buffer.readMapK(size = IntSize.U_BYTE, map = variables) {
val key = buffer.readString()
val value = buffer.readString()
@@ -70,7 +70,7 @@ object NPCClasses : JsonDataRegistry<NPCClass> {
.registerTypeAdapter(MoValue::class.java, MoValueAdapter)
.registerTypeAdapter(NPCClass::class.java, NPCClassAdapter)
.registerTypeAdapter(Activity::class.java, ActivityAdapter)
.registerTypeAdapter(Component::class.java, TextAdapter)
.registerTypeAdapter(Component::class.java, TranslatedTextAdapter)
.registerTypeAdapter(WeightedAspect::class.java, WeightedAspectAdapter)
.registerTypeAdapter(BrainConfig::class.java, BrainConfigAdapter)
.registerTypeAdapter(TaskConfig::class.java, TaskConfigAdapter)
@@ -9,10 +9,12 @@
package com.cobblemon.mod.common.api.npc
import com.cobblemon.mod.common.api.npc.partyproviders.PoolPartyProvider
import com.cobblemon.mod.common.api.npc.partyproviders.ScriptPartyProvider
import com.cobblemon.mod.common.api.npc.partyproviders.SimplePartyProvider
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.google.gson.JsonElement
import net.minecraft.server.level.ServerPlayer
/**
* A provider of a party for battling the NPC. Completely custom party providers will only display
@@ -25,13 +27,14 @@ interface NPCPartyProvider {
companion object {
val types = mutableMapOf<String, (String) -> NPCPartyProvider>(
SimplePartyProvider.TYPE to { SimplePartyProvider() },
PoolPartyProvider.TYPE to { PoolPartyProvider() }
PoolPartyProvider.TYPE to { PoolPartyProvider() },
ScriptPartyProvider.TYPE to { ScriptPartyProvider() }
)
}
val type: String
val isStatic: Boolean
fun provide(npc: NPCEntity, level: Int): NPCPartyStore
fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer> = emptyList()): NPCPartyStore
// Why did I opt for manual JSON loading??? I must have had a reason but I can't remember. Maybe for S2C? Use a codec doofus
fun loadFromJSON(json: JsonElement)
}
@@ -30,6 +30,7 @@ class NPCPreset {
var ai: List<BrainConfig>? = null
var skill: Int? = null
var autoHealParty: Boolean? = null
var randomizePartyOrder: Boolean? = null
var battleTheme: ResourceLocation? = null
var isMovable: Boolean? = null
var isInvulnerable: Boolean? = null
@@ -53,6 +54,7 @@ class NPCPreset {
hitbox?.let { npcClass.hitbox = it }
skill?.let { npcClass.skill = it }
autoHealParty?.let { npcClass.autoHealParty = it }
randomizePartyOrder?.let { npcClass.randomizePartyOrder = it }
battleTheme?.let { npcClass.battleTheme = it }
ai?.let { npcClass.ai.addAll(it) }
isMovable?.let { npcClass.isMovable = it }
@@ -67,7 +67,7 @@ object NPCPresets : JsonDataRegistry<NPCPreset> {
.registerTypeAdapter(NPCVariationProvider::class.java, NPCVariationProviderAdapter)
.registerTypeAdapter(Activity::class.java, ActivityAdapter)
.registerTypeAdapter(MoValue::class.java, MoValueAdapter)
.registerTypeAdapter(Component::class.java, TextAdapter)
.registerTypeAdapter(Component::class.java, TranslatedTextAdapter)
.registerTypeAdapter(BrainConfig::class.java, BrainConfigAdapter)
.registerTypeAdapter(TaskConfig::class.java, TaskConfigAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
@@ -57,8 +57,8 @@ any number of presets and the properties of those presets will be merged into th
"variableName": "challenge_distance",
"displayName": "cobblemon.npc.challenge_distance",
"description": "cobblemon.npc.challenge_distance.description",
"type": "number",
"default": "5"
"type": "NUMBER",
"defaultValue": "5"
}
],
"names": [
@@ -77,6 +77,7 @@ any number of presets and the properties of those presets will be merged into th
"canDespawn": false,
"skill": 5,
"autoHealParty": true,
"randomizePartyOrder": true,
"party": {
"type": "simple",
"pokemon": [
@@ -178,19 +179,19 @@ An example situation of using this is if there is an NPC class preset that defin
challenging a player when they come within a certain range. Inside that class preset, the AI script may
need to know at what distance the player will be engaged. This can be defined in the NPC class preset as
a config variable, and therefore any NPC class that relies on this preset will have that config variable
and present it to users when they setup an NPC of that class.
and present it to users when they set up an NPC of that class.
"config": [
{
"variableName": "challenge_distance",
"displayName": "cobblemon.npc.challenge_distance",
"description": "cobblemon.npc.challenge_distance.description",
"type": "number",
"default": "5"
"displayName": "cobblemon.npc.challenge_distance.name",
"description": "cobblemon.npc.challenge_distance.desc",
"type": "NUMBER",
"defaultValue": "5"
}
]
The "type" of variable is one of: `string`, `number`, or `boolean`. The type is used to preset a more
The "type" of variable is one of: `STRING`, `NUMBER`, or `BOOLEAN`. The type is used to preset a more
appropriate input field for the user when they are setting up the NPC.
### names
@@ -282,15 +283,20 @@ is not mentioned in the Pokémon properties will be set to the seed level.
The "pool" party type is a way to define a party of Pokémon using a more complicated format that involves some
randomization and weighting to different Pokémon options.
A pool party provider can be static or dynamic depending on the value of `isStatic`. It specifies the minimum
and maximum number of Pokémon that can be in the party using `minPokemon` and `maxPokemon`. The `pool` property dictates all of the possible Pokémon, with
each being an object with several properties.
A pool party provider can be static or dynamic depending on the value of `isStatic`. If `useFixedRandom` is true, the Pokémon it selects
from the pool will be constant for a single spawned NPC entity. This is slightly different to a static party because
the level of the Pokémon can still be modified dynamically using MoLang values inside each entry's `level` field.
The pool specifies the minimum and maximum number of Pokémon that can be in the party using `minPokemon` and `maxPokemon`.
The `pool` property dictates all the possible Pokémon with each being an object with several properties.
- The `pokemon` property is the Pokémon properties as used in commands and spawn files that will generate the Pokémon.
- The `weight` property is a decimal number that represents how likely that Pokémon is to be chosen. If the weight is -1, it will be guaranteed to be selected. Defaults to 1. The larger the value, the more likely it will be chosen.
- The `selectableTimes` property is the maximum number of times that the entry can be selected in the party. Defaults to 1.
- The `npcLevels` property is a range of seed levels that the Pokémon can be generated at. For example, if the NPC is spawned with seed level 10, and the range is 5-9, the Pokémon will not be selected. The actual level of the Pokémon will be the seed level. Defaults to "1-100".
- The `levelVariation` property is the amount of variation the Pokémon's level can have from the NPC seed level. For example, a value of 2 will mean that for an NPC generated with seed level 10, the Pokémon will be generated somewhere between level 8 and 12 inclusive. Defaults to zero.
- The `level` property can be used instead of `levelVariation` to force a specific level. This is a MoLang expression.
For `minPokemon`, `maxPokemon`, `weight`, `selectableTimes`, and `levelVariation`, the values can be a simple number or be full MoLang expressions. At the time
of seeding an NPC, the cosmetic variations have already been applied. Therefore, it's possible to make these properties
@@ -298,11 +304,16 @@ depend on visual aspects of the NPC. For example, if the NPC has a variation for
certain Pokémon to be `"weight": "q.npc.has_aspect('dirty') ? -1 : 0"` which would make it a guaranteed party Pokémon for "dirty" NPCs
and impossible for all other variations of the NPC.
The MoLang environment used for the pool party provider contains q.level for the seed level, q.npc for the NPC, as well as q.players for
an array of all players that are challenging the NPC. The players list is only present for dynamic parties. If it is specifically
a single battle challenge and this is a dynamic party, there will also be a `q.player`, for convenience.
"party": {
"type": "pool",
"minPokemon": "3",
"maxPokemon": "6",
"isStatic": true,
"isStatic": false,
"useFixedRandom": true,
"pool": [
{
"pokemon": "weedle",
@@ -339,11 +350,40 @@ and impossible for all other variations of the NPC.
"pokemon": "butterfree",
"weight": "5",
"npcLevels": "10-15",
"selectableTimes": "1"
"selectableTimes": "1",
"level": "q.player.party.highest_level"
}
]
}
#### script
The "script" party type is a way to define a party of Pokémon using a MoLang script. The script can modify a q.party struct,
primarily using function calls such as `q.party.add_by_properties('pikachu shiny)`.
There is an `isStatic` property to control whether the party is static or dynamic. There is a `script` property which is a
resource location pointing to the script that should be located in the `molang` datapack folder.
The MoLang environment used for the script party provider contains q.level for the seed level, q.npc for the NPC, as well as q.players for
an array of all players that are challenging the NPC. The players list is only present for dynamic parties. If it is specifically
a single battle challenge and this is a dynamic party, there will also be a `q.player`, for convenience.
"party": {
"type": "script",
"isStatic": true,
"script": "cobblemon:npc_party"
}
An example script might be:
t.use_butterfree = math.random_integer(1, 2) == 1;
t.use_butterfree ? {
q.party.add_by_properties('butterfree level=' + (q.level + 3));
} : {
q.party.add_by_properties('beedrill level=' + (q.level + 3));
};
q.party.add_by_properties('pidgey');
### autoHealParty
Whether or not the NPC's party will be healed between battles. If the NPC has a dynamic party, this option will
do nothing. If the NPC has a static party and this option is false, their Pokémon will not be healed and the
@@ -364,6 +404,11 @@ the NPC and move them using leads.
Controls whether the NPC can be hit by projectiles. Disabling this option prevents unwanted
interactions, such as players moving the NPC with fishing rods or other projectiles.
### randomizePartyOrder
Whether the NPC's party order will be randomized between battles. When true, this NPC's party (regardless of
whether it was dynamic or static) will choose its initial send-out Pokémon randomly instead of always choosing the first
Pokémon in the party.
### ai
The AI property is an array of brain configurations. This is used to configure the behaviours of the NPC. See
the documentation for brain configurations at [AI Configuration](../ai/config/README.md).
@@ -13,6 +13,11 @@ import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.readText
import com.cobblemon.mod.common.util.writeString
import com.cobblemon.mod.common.util.writeText
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.Component
/**
@@ -30,10 +35,38 @@ class NPCConfigVariable(
val type: NPCVariableType = NPCVariableType.NUMBER,
val defaultValue: String = "0",
) {
companion object {
fun decode(buffer: RegistryFriendlyByteBuf): NPCConfigVariable {
return NPCConfigVariable(
buffer.readString(),
buffer.readText(),
buffer.readText(),
buffer.readEnum(NPCVariableType::class.java),
buffer.readString()
)
}
}
enum class NPCVariableType {
NUMBER,
STRING,
BOOLEAN
TEXT,
BOOLEAN;
fun toMoValue(value: String): MoValue {
return when (this) {
TEXT -> StringValue(value)
BOOLEAN -> DoubleValue(value.let { it == "1" || it.toBooleanStrictOrNull() == true })
else -> DoubleValue(value.toDouble())
}
}
}
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeString(variableName)
buffer.writeText(displayName)
buffer.writeText(description)
buffer.writeEnum(type)
buffer.writeString(defaultValue)
}
fun apply(npc: NPCEntity, value: MoValue) {
@@ -41,11 +74,6 @@ class NPCConfigVariable(
}
fun applyDefault(npc: NPCEntity) {
val value = when (type) {
NPCVariableType.STRING -> StringValue(defaultValue)
NPCVariableType.BOOLEAN -> DoubleValue(defaultValue.toBoolean())
else -> DoubleValue(defaultValue.toDouble())
}
npc.config.set(variableName.split(".").iterator(), value)
npc.config.setDirectly(variableName, type.toMoValue(defaultValue))
}
}
@@ -10,11 +10,14 @@ package com.cobblemon.mod.common.api.npc.partyproviders
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.npc.NPCPartyProvider
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asArrayValue
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.resolveInt
@@ -23,6 +26,8 @@ import com.cobblemon.mod.common.util.withQueryValue
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import kotlin.random.Random
import net.minecraft.server.level.ServerPlayer
/**
* A provider of a party for battling the NPC. It generates a party using a more complex process
@@ -46,6 +51,7 @@ class PoolPartyProvider : NPCPartyProvider {
override val type = TYPE
override var isStatic: Boolean = false
var useFixedRandom: Boolean = false
var minPokemon: Expression = "1".asExpression()
var maxPokemon: Expression = "6".asExpression()
var pool = mutableListOf<DynamicPokemon>()
@@ -59,6 +65,7 @@ class PoolPartyProvider : NPCPartyProvider {
return@map DynamicPokemon(
PokemonProperties.parse(it.asString),
"0".asExpression(),
null,
1..100,
"1".asExpression(),
"1".asExpression()
@@ -67,19 +74,22 @@ class PoolPartyProvider : NPCPartyProvider {
it as JsonObject
DynamicPokemon(
PokemonProperties.parse(it.getAsJsonPrimitive("pokemon").asString),
it.getAsJsonPrimitive("levelVariation")?.asString?.asExpression() ?: "0".asExpression(),
it.getAsJsonPrimitive("npcLevels")?.asString?.split("-")?.let { it[0].toInt()..it[1].toInt() } ?: 1..100,
it.getAsJsonPrimitive("selectableTimes")?.asString?.asExpression() ?: "1".asExpression(),
it.getAsJsonPrimitive("weight")?.asString?.asExpression() ?: "1".asExpression()
PokemonProperties.parse(it.get("pokemon").asString),
it.get("levelVariation")?.asString?.asExpression() ?: "0".asExpression(),
it.get("level")?.asString?.asExpression(),
it.get("npcLevels")?.asString?.split("-")?.let { it[0].toInt()..it[1].toInt() } ?: 1..100,
it.get("selectableTimes")?.asString?.asExpression() ?: "1".asExpression(),
it.get("weight")?.asString?.asExpression() ?: "1".asExpression()
)
}.toMutableList()
isStatic = json.getAsJsonPrimitive("isStatic").asBoolean
isStatic = json.get("isStatic").asBoolean
json.get("useFixedRandom")?.asBoolean?.let { useFixedRandom = it }
}
class DynamicPokemon(
val pokemon: PokemonProperties,
val levelVariation: Expression,
val level: Expression? = null,
val npcLevels: IntRange,
val selectableTimes: Expression,
val weight: Expression = "1".asExpression()
@@ -93,18 +103,25 @@ class PoolPartyProvider : NPCPartyProvider {
}
}
fun formulateParty(npc: NPCEntity, level: Int, party: NPCPartyStore) {
fun formulateParty(npc: NPCEntity, level: Int, players: List<ServerPlayer>, party: NPCPartyStore) {
val runtime = MoLangRuntime().setup().withQueryValue("npc", npc.struct)
val random = if (useFixedRandom) Random(npc.uuid.hashCode()) else Random.Default
runtime.withQueryValue("level", DoubleValue(level))
runtime.withQueryValue("players", players.asArrayValue { it.asMoLangValue() })
if (players.size == 1) {
// This is for the convenience, most cases will be pvn one player
runtime.withQueryValue("player", players.first().asMoLangValue())
}
val minPokemon = runtime.resolveInt(this.minPokemon)
val maxPokemon = runtime.resolveInt(this.maxPokemon)
var desiredPokemonCount = (minPokemon..maxPokemon).random()
var desiredPokemonCount = (minPokemon..maxPokemon).random(random)
val workingPool = pool.filter { level in it.npcLevels }.toMutableList()
val workingPool = pool.filter { level in it.npcLevels && runtime.resolveInt(it.selectableTimes) > 0 }.toMutableList()
val useCounts = mutableMapOf<DynamicPokemon, Int>()
while (desiredPokemonCount > 0 && workingPool.filter { it.hasWeight(runtime) }.isNotEmpty()) {
val selected = workingPool.filter { it.getWeight(runtime) == -1F }.randomOrNull()
?: workingPool.weightedSelection { it.getWeight(runtime) }
while (desiredPokemonCount > 0 && workingPool.any { it.hasWeight(runtime) }) {
val selected = workingPool.filter { it.getWeight(runtime) == -1F }.randomOrNull(random)
?: workingPool.weightedSelection(random = random) { it.getWeight(runtime) }
?: break
useCounts[selected] = useCounts.getOrDefault(selected, 0) + 1
val allowedSelections = runtime.resolveInt(selected.selectableTimes)
@@ -114,16 +131,17 @@ class PoolPartyProvider : NPCPartyProvider {
desiredPokemonCount--
// If the Pokémon's props specifies a level then use that, otherwise choose a random level within the range
val levelVariation = (0..runtime.resolveInt(selected.levelVariation)).random()
val levelVariation = (0..runtime.resolveInt(selected.levelVariation)).random(random)
val randomLevel = level + levelVariation
val instance = selected.pokemon.copy().also { it.level = it.level ?: randomLevel }.create()
val dictatedLevel = selected.level?.let { runtime.resolveInt(it) }
val instance = selected.pokemon.copy().also { it.level = it.level ?: dictatedLevel ?: randomLevel }.create()
party.add(instance)
}
}
override fun provide(npc: NPCEntity, level: Int): NPCPartyStore {
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
val party = NPCPartyStore(npc)
formulateParty(npc, level, party)
formulateParty(npc, level, players, party)
return party
}
}
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2023 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.npc.partyproviders
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.npc.NPCPartyProvider
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asArrayValue
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.toProperties
import com.cobblemon.mod.common.util.withQueryValue
import com.google.gson.JsonElement
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
class ScriptPartyProvider : NPCPartyProvider {
companion object {
const val TYPE = "script"
}
override val type = TYPE
override var isStatic: Boolean = true
var script: ResourceLocation = cobblemonResource("dummy")
override fun loadFromJSON(json: JsonElement) {
isStatic = json.asJsonObject.get("isStatic")?.asBoolean != false
script = json.asJsonObject.get("script").asString.asIdentifierDefaultingNamespace()
}
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
val runtime = MoLangRuntime().setup().withQueryValue("npc", npc.struct)
runtime.withQueryValue("level", DoubleValue(level))
runtime.withQueryValue("players", players.asArrayValue { it.asMoLangValue() })
if (players.size == 1) {
// This is for the convenience, most cases will be pvn one player
runtime.withQueryValue("player", players.first().asMoLangValue())
}
val npcParty = NPCPartyStore(npc)
runtime.withQueryValue("party", npcParty.struct)
if (!CobblemonScripts.scripts.containsKey(script)) {
Cobblemon.LOGGER.error("Script $script not found in script registry.")
npcParty.add("magikarp".toProperties().create())
return npcParty
}
CobblemonScripts.run(identifier = script, runtime = runtime)
return npcParty
}
}
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.toProperties
import com.google.gson.JsonElement
import net.minecraft.server.level.ServerPlayer
/**
* A basic party provider that just produces a [NPCPartyStore] based on a simple list of [PokemonProperties].
@@ -52,7 +53,7 @@ class SimplePartyProvider : NPCPartyProvider {
}
}
override fun provide(npc: NPCEntity, level: Int): NPCPartyStore {
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
val pokemon = pokemon.map { it.copy().also { it.level = it.level ?: level }.create() }
val party = NPCPartyStore(npc)
pokemon.forEach(party::add)
@@ -32,5 +32,5 @@ class WeightedNPCVariationProvider : NPCVariationProvider {
override val aspects: Set<String>
get() = options.flatMap(WeightedAspect::aspects).toSet()
override fun provideAspects(npcEntity: NPCEntity) = options.weightedSelection(WeightedAspect::weight)?.aspects ?: emptySet()
override fun provideAspects(npcEntity: NPCEntity) = options.weightedSelection { it.weight }?.aspects ?: emptySet()
}
@@ -82,6 +82,8 @@ object CobblemonPermissions {
val ABANDON_MULTITEAM = this.create("${COMMAND_PREFIX}abandonmultiteam", PermissionLevel.NONE)
val RUN_MOLANG_SCRIPT = this.create("${COMMAND_PREFIX}runmolangscript", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
fun all(): Iterable<Permission> = this.permissions
private fun create(node: String, level: PermissionLevel): Permission {
@@ -8,13 +8,13 @@
package com.cobblemon.mod.common.api.pokedex
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addPokedexFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import net.minecraft.resources.ResourceLocation
abstract class AbstractPokedexManager {
@@ -23,11 +23,9 @@ abstract class AbstractPokedexManager {
private val globalCalculatedValues = mutableMapOf<GlobalPokedexValueCalculator<*>, Any>()
@Transient
val struct = QueryStruct(hashMapOf()).addStandardFunctions()
.addFunction("get_species_record") { params ->
val speciesId = params.getString(0).asIdentifierDefaultingNamespace()
speciesRecords[speciesId]?.struct ?: QueryStruct(hashMapOf())
}
val struct = ObjectValue<AbstractPokedexManager>(this)
.addStandardFunctions()
.addPokedexFunctions(this)
fun deleteSpeciesRecord(speciesId: ResourceLocation) {
speciesRecords.remove(speciesId)
@@ -11,6 +11,8 @@ package com.cobblemon.mod.common.api.pokedex
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.struct.VariableStruct
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokemon.PokedexDataChangedEvent
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Pokemon
@@ -19,6 +21,7 @@ import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeEnumConstant
import com.cobblemon.mod.common.util.writeString
import com.google.common.collect.Sets
import com.mojang.datafixers.util.Either
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.ListCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
@@ -123,6 +126,10 @@ class FormDexRecord {
}
speciesDexRecord.addInformation(pokemon, knowledge)
speciesDexRecord.onFormRecordUpdated(this)
(speciesDexRecord.pokedexManager as? PokedexManager)?.let { pokedexManager ->
CobblemonEvents.POKEDEX_DATA_CHANGED.post(PokedexDataChangedEvent(Either.right(pokemon), knowledge, pokedexManager.uuid, this))
}
}
private fun addInformation(pokedexEntityData: PokedexEntityData, knowledge: PokedexEntryProgress) {
@@ -133,6 +140,10 @@ class FormDexRecord {
}
speciesDexRecord.addInformation(pokedexEntityData, knowledge)
speciesDexRecord.onFormRecordUpdated(this)
(speciesDexRecord.pokedexManager as? PokedexManager)?.let { pokedexManager ->
CobblemonEvents.POKEDEX_DATA_CHANGED.post(PokedexDataChangedEvent(Either.left(pokedexEntityData), knowledge, pokedexManager.uuid, this))
}
}
/** Returns whether the given [Pokemon] and [knowledge] would add new information to the Pokédex.*/
@@ -36,7 +36,7 @@ import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.isInt
import com.cobblemon.mod.common.util.isDouble
import com.cobblemon.mod.common.util.isUuid
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.util.simplify
@@ -59,7 +59,6 @@ import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
@@ -187,10 +186,10 @@ open class PokemonProperties {
private fun parseIntProperty(keyPairs: MutableList<Pair<String, String?>>, labels: Iterable<String>): Int? {
val matchingKeyPair = getMatchedKeyPair(keyPairs, labels) ?: return null
val value = matchingKeyPair.second
return if (value == null || !value.isInt()) {
return if (value == null || !value.isDouble()) {
null
} else {
value.toInt()
value.toDouble().toInt()
}
}
@@ -790,7 +789,7 @@ open class PokemonProperties {
}
fun copy(): PokemonProperties {
return PokemonProperties().loadFromJSON(saveToJSON())
return PokemonProperties().loadFromJSON(this.saveToJSON())
}
// If the config value is at least 1, then do 1/x and use that as the property chance
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.api.drop.ItemDropMethod
import com.cobblemon.mod.common.api.entity.EntityDimensionsAdapter
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.TypeChart
import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffect
import com.cobblemon.mod.common.api.pokemon.effect.adapter.ShoulderEffectAdapter
@@ -125,6 +126,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
it.indicateSpeciesInitialized()
// Reload this with the mod
CobblemonHeldItemManager.load()
TypeChart.load()
Cobblemon.LOGGER.info("Loaded {} Pokémon species", this.speciesByIdentifier.size)
this.observable.emit(this)
}
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.ScriptRegistrySyncPacket
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.contextOrEmpty
import com.cobblemon.mod.common.util.endsWith
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
@@ -64,6 +65,6 @@ object CobblemonScripts : DataRegistry {
}
fun run(identifier: ResourceLocation, runtime: MoLangRuntime): MoValue? {
return scripts[identifier]?.resolve(runtime)
return scripts[identifier]?.resolve(runtime, runtime.contextOrEmpty)
}
}
@@ -12,6 +12,8 @@ import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.world.item.Item
import net.minecraft.world.item.enchantment.EnchantmentHelper
import net.minecraft.world.item.enchantment.Enchantments
import net.minecraft.world.level.block.Block
/**
@@ -33,9 +35,43 @@ class FishingSpawningCondition: SpawningCondition<FishingSpawningContext>() {
return false
} else if (neededNearbyBlocks != null && neededNearbyBlocks!!.none { cond -> ctx.nearbyBlockTypes.any { cond.fits(it, ctx.blockRegistry) } }) {
return false
} else {
return true
}
if (minLureLevel != null) { // check for the lureLevel of the rod
val pokerodStack = ctx.rodStack
val lureLevel = EnchantmentHelper.getItemEnchantmentLevel(
ctx.enchantmentRegistry.getHolder(Enchantments.LURE).get(),
pokerodStack
)
if (lureLevel < minLureLevel!!) {
return false
} else if (maxLureLevel != null && lureLevel > maxLureLevel!!) {
return false
}
}
if (bait != null) { // check for the bait on the bobber
val pokerodBait = ctx.rodBait?.item
if (pokerodBait != bait) {
return false
}
}
if (rodType != null) { // check for the type of pokerod being used
val pokerodItem = ctx.rodItem
if (pokerodItem?.pokeRodId != rodType) {
return false
}
}
/*if (ctx is FishingSpawningContext && (ctx as FishingSpawningContext).rodItem != null) { // check if the bait attracts certain EV yields
val pokerodItem = (ctx as FishingSpawningContext).rodItem
// todo check if the EV yield of the berry matches the bait EV attract maybe?
if (// todo if bait EV yield != EV yield of pokemon consideration //Registries.ITEM.getId(pokerodItem?.bait?.item).path == )
return false
}*/
return true
}
companion object {
@@ -116,24 +116,6 @@ abstract class SpawningCondition<T : SpawningContext> {
}
) {
return false
} else if (minLureLevel != null && ctx is FishingSpawningContext) { // check for the lureLevel of the rod
val pokerodStack = (ctx as FishingSpawningContext).rodStack
val lureLevel = EnchantmentHelper.getItemEnchantmentLevel(ctx.enchantmentRegistry.getHolder(Enchantments.LURE).get(), pokerodStack)
if (lureLevel < minLureLevel!!) {
return false
} else if (maxLureLevel != null && lureLevel > maxLureLevel!!) {
return false
}
} else if (bait != null && ctx is FishingSpawningContext) { // check for the bait on the bobber
val pokerodBait = (ctx as FishingSpawningContext).rodBait?.item
if (pokerodBait != bait) {
return false
}
} else if (rodType != null && ctx is FishingSpawningContext) { // check for the type of pokerod being used
val pokerodItem = (ctx as FishingSpawningContext).rodItem
if (pokerodItem?.pokeRodId != rodType) {
return false
}
} else if (isSlimeChunk != null && isSlimeChunk != false) {
val isSlimeChunk = WorldgenRandom.seedSlimeChunk(ctx.position.x shr 4, ctx.position.z shr 4, ctx.world.seed, 987234911L).nextInt(10) == 0
@@ -152,15 +134,6 @@ abstract class SpawningCondition<T : SpawningContext> {
return random.nextInt(10) == 0*/
}
/*else if (ctx is FishingSpawningContext && (ctx as FishingSpawningContext).rodItem != null) { // check if the bait attracts certain EV yields
val pokerodItem = (ctx as FishingSpawningContext).rodItem
// todo check if the EV yield of the berry matches the bait EV attract maybe?
if (// todo if bait EV yield != EV yield of pokemon consideration //Registries.ITEM.getId(pokerodItem?.bait?.item).path == )
return false
}*/
return true
}
@@ -16,7 +16,6 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.feature.SeasonFeatureHandler
import com.cobblemon.mod.common.util.weightedSelection
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Player
/**
* A [SpawnAction] that will spawn a single [PokemonEntity].
@@ -269,7 +269,7 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
BattlePokemon.playerOwned(it)
}.also { if (healPokemon) it.effectedPokemon.heal() }
}.toMutableList()
if(leadingPokemon != null) {
if (leadingPokemon != null) {
Collections.rotate(result, result.size - this.indexOfFirst { it.uuid == leadingPokemon })
}
return result
@@ -102,20 +102,32 @@ open class PlayerPartyStore(
for (pokemon in this) {
// Awake from fainted
if (pokemon.isFainted()) {
pokemon.faintedTimer -= 1
if (pokemon.faintedTimer <= -1) {
val php = ceil(pokemon.maxHealth * Cobblemon.config.faintAwakenHealthPercent)
pokemon.currentHealth = php.toInt()
player.sendSystemMessage(Component.translatable("cobblemon.party.faintRecover", pokemon.getDisplayName()))
//Skip awaken logic check if config value is 0
if (Cobblemon.config.faintAwakenHealthPercent > 0) {
pokemon.faintedTimer -= 1
if (pokemon.faintedTimer <= -1) {
val php = ceil(pokemon.maxHealth * Cobblemon.config.faintAwakenHealthPercent)
pokemon.currentHealth = php.toInt()
player.sendSystemMessage(
Component.translatable(
"cobblemon.party.faintRecover",
pokemon.getDisplayName()
)
)
}
}
}
// Passive healing while less than full health
else if (pokemon.currentHealth < pokemon.maxHealth) {
pokemon.healTimer--
if (pokemon.healTimer <= -1) {
pokemon.healTimer = Cobblemon.config.healTimer
val healAmount = 1.0.coerceAtLeast(pokemon.maxHealth.toDouble() * Cobblemon.config.healPercent)
pokemon.currentHealth = pokemon.currentHealth + round(healAmount).toInt()
//Skip passive healing logic check if config value is 0
if (Cobblemon.config.healPercent > 0) {
pokemon.healTimer--
if (pokemon.healTimer <= -1) {
pokemon.healTimer = Cobblemon.config.healTimer
val healAmount =
1.0.coerceAtLeast(pokemon.maxHealth.toDouble() * Cobblemon.config.healPercent)
pokemon.currentHealth = pokemon.currentHealth + round(healAmount).toInt()
}
}
}
@@ -204,4 +216,4 @@ open class PlayerPartyStore(
super.set(position, pokemon)
pokemon.getOwnerPlayer()?.let { CobblemonCriteria.PARTY_CHECK.trigger(it, this) }
}
}
}
@@ -100,6 +100,10 @@ object BattleBuilder {
)
}
if (actor.pokemonList.any { it.entity?.isBusy == true }) {
errors.participantErrors[actor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
if (BattleRegistry.getBattleByParticipatingPlayer(player) != null) {
errors.participantErrors[actor] += BattleStartError.alreadyInBattle(player)
}
@@ -178,6 +182,10 @@ object BattleBuilder {
)
}
if (actor.pokemonList.any { it.entity?.isBusy == true }) {
errors.participantErrors[actor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
if (BattleRegistry.getBattleByParticipatingPlayer(player) != null) {
errors.participantErrors[actor] += BattleStartError.alreadyInBattle(player)
}
@@ -248,7 +256,7 @@ object BattleBuilder {
val wildActor = PokemonBattleActor(pokemonEntity.pokemon.uuid, BattlePokemon(pokemonEntity.pokemon), fleeDistance)
val errors = ErroredBattleStart()
if(playerTeam.isNotEmpty() && playerTeam[0].health <= 0){
if (playerTeam.isNotEmpty() && playerTeam[0].health <= 0){
errors.participantErrors[playerActor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
@@ -264,6 +272,10 @@ object BattleBuilder {
)
}
if (playerActor.pokemonList.any { it.entity?.isBusy == true }) {
errors.participantErrors[playerActor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
if (BattleRegistry.getBattleByParticipatingPlayer(player) != null) {
errors.participantErrors[playerActor] += BattleStartError.alreadyInBattle(playerActor)
}
@@ -315,7 +327,7 @@ object BattleBuilder {
): BattleStartResult {
val playerTeam = party.toBattleTeam(clone = cloneParties, healPokemon = healFirst, leadingPokemon = leadingPokemon)
val playerActor = PlayerBattleActor(player.uuid, playerTeam)
val npcParty = npcEntity.getPartyForChallenge(player)
val npcParty = npcEntity.getPartyForChallenge(listOf(player))
val errors = ErroredBattleStart()
if (playerActor.pokemonList.size < battleFormat.battleType.slotsPerActor) {
@@ -348,6 +360,10 @@ object BattleBuilder {
)
}
if (playerActor.pokemonList.any { it.entity?.isBusy == true }) {
errors.participantErrors[playerActor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
playerActor.battleTheme = npcEntity.getBattleTheme()
return if (errors.isEmpty) {
@@ -394,7 +410,7 @@ interface BattleStartError {
fun alreadyInBattle(pokemonEntity: PokemonEntity) = AlreadyInBattleError(pokemonEntity.uuid, pokemonEntity.effectiveName())
fun alreadyInBattle(actor: BattleActor) = AlreadyInBattleError(actor.uuid, actor.getName())
fun noParty(npcEntity: NPCEntity) = NoPartyError(npcEntity)
fun targetIsBusy(targetName: MutableComponent) = BusyError(targetName)
fun targetIsBusy(targetName: Component) = BusyError(targetName)
fun insufficientPokemon(
actorEntity: Entity,
requiredCount: Int,
@@ -474,7 +490,7 @@ class AlreadyInBattleError(
}
}
class BusyError(
val targetName: MutableComponent
val targetName: Component
): BattleStartError {
override fun getMessageFor(entity: Entity) = battleLang("errors.busy", targetName)
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.battles.runner
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.runner.graal.GraalShowdownService
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import java.util.UUID
/**
@@ -30,6 +31,7 @@ interface ShowdownService {
fun getAbilityIds(): JsonArray
fun getMoves(): JsonArray
fun getItemIds(): JsonArray
fun getTypeChart(): JsonObject
fun registerSpecies()
fun registerBagItems()
fun indicateSpeciesInitialized() {}
@@ -38,4 +40,4 @@ interface ShowdownService {
companion object {
val service: ShowdownService by lazy { GraalShowdownService() }
}
}
}
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import java.io.File
import java.io.IOException
import java.net.URI
@@ -152,6 +153,12 @@ class GraalShowdownService : ShowdownService {
return gson.fromJson(arrayResult, JsonArray::class.java)
}
override fun getTypeChart(): JsonObject {
val getCobbledItemIdsFn = context.getBindings("js").getMember("getTypeChart")
val arrayResult = getCobbledItemIdsFn.execute().asString()
return gson.fromJson(arrayResult, JsonObject::class.java)
}
override fun registerSpecies() {
val receiveSpeciesDataFn = this.context.getBindings("js").getMember("receiveSpeciesData")
val jsArray = this.context.eval("js", "new Array();")
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.OutputStreamWriter
@@ -123,6 +124,13 @@ class SocketShowdownService(val host: String = "localhost", val port: Int = 1846
return gson.fromJson(response, JsonArray::class.java)
}
override fun getTypeChart(): JsonObject {
writer.write(">getTypeChart")
writer.flush()
val response = readMessage()
return gson.fromJson(response, JsonObject::class.java)
}
private fun sendSpeciesData(species: Species, form: FormData?) {
writer.write(">receiveSpeciesData ${gson.toJson(PokemonSpecies.ShowdownSpecies(species, form))}\n")
acknowledge()
@@ -157,7 +157,7 @@ class GildedChestBlock(settings: Properties, val type: Type = Type.RED) : BaseEn
// The yaw based on the block's facing direction
val yaw = facingToYaw[state.getValue(HorizontalDirectionalBlock.FACING)] ?: 0.0F
entity.entityData.set(PokemonEntity.SPAWN_DIRECTION, facingToYaw[state.getValue(HorizontalDirectionalBlock.FACING)])
entity.entityData.set(PokemonEntity.SPAWN_DIRECTION, yaw)
val offsetDir = state.getValue(HorizontalDirectionalBlock.FACING)
val vec = pos.toVec3d().add(offsetDir.stepX * 0.1 + 0.5, 0.0, offsetDir.stepZ * 0.1 + 0.5)
entity.moveTo(vec.x, vec.y, vec.z, yaw, entity.xRot)
@@ -125,6 +125,7 @@ object ClientMoLangFunctions {
val maxPitch = params.getDoubleOrNull(3) ?: 70F
val minPitch = params.getDoubleOrNull(4) ?: -45F
val maxYaw = params.getDoubleOrNull(5) ?: 45F
val minYaw = params.getDoubleOrNull(6) ?: -45F
ObjectValue(
SingleBoneLookAnimation(
bone = model.getPart(boneName),
@@ -132,7 +133,8 @@ object ClientMoLangFunctions {
yawMultiplier = yawMultiplier.toFloat(),
maxPitch = maxPitch.toFloat(),
minPitch = minPitch.toFloat(),
maxYaw = maxYaw.toFloat()
maxYaw = maxYaw.toFloat(),
minYaw = minYaw.toFloat()
)
)
}
@@ -0,0 +1,13 @@
/*
* Copyright (C) 2023 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
import net.minecraft.client.gui.components.Renderable
interface CobblemonRenderable : Renderable
@@ -18,7 +18,7 @@ import net.minecraft.network.chat.Component
class ExitButton(
pX: Int, pY: Int,
onPress: OnPress
): Button(pX, pY, WIDTH.toInt(), HEIGHT.toInt(), Component.literal("Exit"), onPress, DEFAULT_NARRATION) {
): Button(pX, pY, WIDTH.toInt(), HEIGHT.toInt(), Component.literal("Exit"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
private const val WIDTH = 26F
@@ -31,7 +31,7 @@ abstract class ScrollingWidget<T : ObjectSelectionList.Entry<T>>(
height, // Height
top, // Top
slotHeight // Slot Height
) {
), CobblemonRenderable {
override fun renderListBackground(guiGraphics: GuiGraphics) {}
override fun renderSelection(context: GuiGraphics, y: Int, entryWidth: Int, entryHeight: Int, borderColor: Int, fillColor: Int) {}
override fun renderDecorations(context: GuiGraphics, mouseX: Int, mouseY: Int) {}
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.battles.ShowdownActionResponse
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.battle.ClientBattleActor
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleActionSelection
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleBackButton
import com.cobblemon.mod.common.client.gui.battle.subscreen.BattleGeneralActionSelection
@@ -29,7 +30,7 @@ import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.Screen
class BattleGUI : Screen(battleLang("gui.title")) {
class BattleGUI : Screen(battleLang("gui.title")), CobblemonRenderable {
companion object {
const val OPTION_VERTICAL_SPACING = 3
const val OPTION_HORIZONTAL_SPACING = 3
@@ -9,18 +9,19 @@
package com.cobblemon.mod.common.client.gui.battle.subscreen
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.gui.GuiGraphics
class BattleBackButton(val x: Float, val y: Float) {
class BattleBackButton(val x: Float, val y: Float) : CobblemonRenderable {
companion object {
const val WIDTH = 58
const val HEIGHT = 34
const val SCALE = 0.5F
}
fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
blitk(
matrixStack = context.pose(),
texture = cobblemonResource("textures/gui/battle/battle_back.png"),
@@ -11,12 +11,11 @@ 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
import com.cobblemon.mod.common.api.text.gold
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.api.moves.TypeChart
import com.cobblemon.mod.common.api.text.*
import com.cobblemon.mod.common.battles.*
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.ActiveClientBattlePokemon
import com.cobblemon.mod.common.client.battle.SingleActionRequest
import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
import com.cobblemon.mod.common.client.gui.TypeIcon
@@ -24,12 +23,16 @@ import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.toRGB
import com.google.common.collect.Lists
import net.fabricmc.loader.impl.util.StringUtil
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.util.Mth.floor
class BattleMoveSelection(
@@ -91,16 +94,16 @@ class BattleMoveSelection(
open val response: MoveActionResponse get() = MoveActionResponse(move.id, targetPnx)
open val selectable: Boolean get() = !move.disabled
val targetPnx: String? get() = targetList?.let { targets ->
val target: Targetable? get() = targetList?.let { targets ->
return@let when {
targets.isEmpty() -> null
targets.size == 1 -> targets[0].getPNX()
targets.size == 1 -> targets[0]
else -> null // TODO: multi-battles
}
}
val targetPnx: String? get() = target?.getPNX()
fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val selectConditionOpacity = moveSelection.opacity * if (!selectable) 0.5F else 1F
blitk(
@@ -173,6 +176,8 @@ class BattleMoveSelection(
opacity = moveSelection.opacity,
centered = true
)
drawTooltip(context, mouseX, mouseY)
}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX >= x && mouseX <= x + MOVE_WIDTH && mouseY >= y && mouseY <= y + MOVE_HEIGHT
@@ -182,6 +187,41 @@ class BattleMoveSelection(
moveSelection.playDownSound(Minecraft.getInstance().soundManager)
moveSelection.battleGUI.selectAction(moveSelection.request, response)
}
private fun drawTooltip(context: GuiGraphics, mouseX: Int, mouseY: Int) {
if (!isHovered(mouseX.toDouble(), mouseY.toDouble())) return
val textTooltip: MutableList<MutableComponent> = mutableListOf()
for (descriptionLine in StringUtil.wrapLines(moveTemplate.description.string, 80).split("\n")) {
textTooltip.add(Component.literal(descriptionLine).gray())
}
textTooltip.add(Component.literal(""))
if (moveTemplate.power > 0) textTooltip.add(lang("ui.power").append(String.format(": %d", moveTemplate.power.toInt())))
if (moveTemplate.accuracy > 0) textTooltip.add(lang("ui.accuracy").append(String.format(": %d%%", moveTemplate.accuracy.toInt())))
if (moveTemplate.power > 0 && moveTemplate.target != MoveTarget.self && target is ActiveClientBattlePokemon) {
val species = (target as ActiveClientBattlePokemon).battlePokemon?.species
if (species != null) {
val moveEffectiveness: Double = TypeChart.getMoveEffectiveness(elementalType, listOf(species.primaryType, species.secondaryType))
textTooltip.add(when {
moveEffectiveness == 0.0 -> lang("battle.ui.effectiveness.immunity", moveEffectiveness).gray()
moveEffectiveness < 1.0 -> lang("battle.ui.effectiveness.resistance", moveEffectiveness).gray()
moveEffectiveness > 1.0 -> lang("battle.ui.effectiveness.super_effective", moveEffectiveness).yellow()
else -> lang("battle.ui.effectiveness.normal", moveEffectiveness).white()
})
}
}
context.renderTooltip(
Minecraft.getInstance().font,
Lists.transform(textTooltip, Component::getVisualOrderText),
mouseX,
mouseY
)
}
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.gui.battle.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.battle.ClientBattleMessageQueue
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
@@ -32,7 +33,7 @@ class BattleMessagePane(
FRAME_HEIGHT, // height
1, // top
LINE_HEIGHT
) {
), CobblemonRenderable {
var opacity = 1F
private var scrolling = false
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.client.gui.battle.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.battle.BattleGUI
import com.cobblemon.mod.common.client.render.drawScaledText
import net.minecraft.client.gui.GuiGraphics
@@ -28,7 +29,7 @@ class BattleOptionTile(
val resource: ResourceLocation,
val text: MutableComponent,
val onClick: () -> Unit
) : Renderable, GuiEventListener, NarratableEntry {
) : CobblemonRenderable, GuiEventListener, NarratableEntry {
companion object {
const val OPTION_WIDTH = 90
const val OPTION_HEIGHT = 26
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueBox
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialoguePortraitWidget
import com.cobblemon.mod.common.client.gui.dialogue.widgets.DialogueNameWidget
@@ -36,7 +37,7 @@ import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.Screen
class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTranslated()) {
class DialogueScreen(var dialogueDTO: DialogueDTO) : Screen("gui.dialogue".asTranslated()), CobblemonRenderable {
val speakers = dialogueDTO.speakers?.mapNotNull { (key, value) ->
val name = value.name
when (val face = value.face) {
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.client.gui.dialogue.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
@@ -22,7 +23,7 @@ class DialogueNameWidget(
val width: Int,
val height: Int,
val text: MutableComponent?
) : Renderable, GuiEventListener {
) : CobblemonRenderable, GuiEventListener {
companion object {
val nameResource = cobblemonResource("textures/gui/dialogue/dialogue_name.png")
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.client.gui.dialogue.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.dialogue.DialogueScreen
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
@@ -22,7 +23,7 @@ class DialoguePortraitWidget(
val y: Int,
val width: Int,
val height: Int
) : Renderable, GuiEventListener {
) : CobblemonRenderable, GuiEventListener {
companion object {
val DIALOGUE_ARROW_HEIGHT = 11
val DIALOGUE_ARROW_WIDTH = 6
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.gui.dialogue.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.gui.drawCenteredText
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.dialogue.DialogueScreen
import com.cobblemon.mod.common.net.messages.client.dialogue.dto.DialogueInputDTO
import com.cobblemon.mod.common.net.messages.server.dialogue.InputToDialoguePacket
@@ -36,7 +37,7 @@ class DialogueTextInputWidget(
width,
height,
"gui_dialogue_text_input".text()
) {
), CobblemonRenderable {
init {
setMaxLength(maxLength)
isFocused = true
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.client.gui.dialogue.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.dialogue.DialogueScreen
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
@@ -22,7 +23,7 @@ class DialogueTimerWidget(
val y: Int,
val width: Int,
val height: Int,
) : Renderable, GuiEventListener {
) : CobblemonRenderable, GuiEventListener {
companion object {
val timerResource = cobblemonResource("textures/gui/dialogue/dialogue_timer_bar.png")
}
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.battle.ClientBattleChallenge
import com.cobblemon.mod.common.client.battle.ClientTeamRequest
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
import com.cobblemon.mod.common.net.messages.server.*
@@ -35,7 +36,7 @@ class BattleConfigureGUI(
private val packet: PlayerInteractOptionsPacket,
private val activeRequest: ClientBattleChallenge? = null,
private val activeTeamRequest: ClientTeamRequest? = null
) : Screen(lang("ui.challenge.challenge_title")) {
) : Screen(lang("ui.challenge.challenge_title")), CobblemonRenderable {
companion object {
const val SIZE = 113
private val backgroundResource = cobblemonResource("textures/gui/interact/request/battle_request.png")
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.client.gui.interact.battleRequest
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
@@ -25,7 +26,7 @@ class BattleRequestNavigationButton(
private val forwardIcon: ResourceLocation = forwardButtonResource,
private val backwardIcon: ResourceLocation = backwardsButtonResource,
onPress: OnPress
): Button(pX.toInt(), pY.toInt(), (WIDTH * SCALE).toInt(), (clickHeight * SCALE).toInt(), Component.literal("Navigation"), onPress, DEFAULT_NARRATION) {
): Button(pX.toInt(), pY.toInt(), (WIDTH * SCALE).toInt(), (clickHeight * SCALE).toInt(), Component.literal("Navigation"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
const val HEIGHT = 16
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.client.gui.interact.battleRequest
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
@@ -21,7 +22,7 @@ class BattleResponseButton(
pX: Int, pY: Int,
private val accept: Boolean,
onPress: OnPress
): Button(pX, pY, (WIDTH * SCALE).toInt(), (HEIGHT * SCALE).toInt(), Component.literal("Navigation"), onPress, DEFAULT_NARRATION) {
): Button(pX, pY, (WIDTH * SCALE).toInt(), (HEIGHT * SCALE).toInt(), Component.literal("Navigation"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
private const val WIDTH = 34F
@@ -12,6 +12,7 @@ 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.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
@@ -24,7 +25,7 @@ class BattleRequestButton(
pX: Int, pY: Int,
var text: MutableComponent,
onPress: OnPress
): Button(pX, pY, (WIDTH * SCALE).toInt(), (HEIGHT * SCALE).toInt(), text, onPress, DEFAULT_NARRATION) {
): Button(pX, pY, (WIDTH * SCALE).toInt(), (HEIGHT * SCALE).toInt(), text, onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
private const val WIDTH = 69F
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.callback.MoveSelectDTO
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
@@ -35,7 +36,7 @@ class MoveSelectConfiguration(
class MoveSelectGUI(
val config: MoveSelectConfiguration
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")) {
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable {
companion object {
const val WIDTH = 122
const val HEIGHT = 133
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.api.text.gold
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.MoveCategoryIcon
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.render.drawScaledText
@@ -35,7 +36,7 @@ class MoveSlotButton(
val ppMax: Int,
val enabled: Boolean = true,
onPress: OnPress
) : Button(x, y, WIDTH, HEIGHT, Component.literal("Move"), onPress, CreateNarration { "".text() }) {
) : Button(x, y, WIDTH, HEIGHT, Component.literal("Move"), onPress, CreateNarration { "".text() }), CobblemonRenderable {
companion object {
private val moveResource = cobblemonResource("textures/gui/summary/summary_move.png")
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.callback.PartySelectPokemonDTO
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.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonSelectedPacket
@@ -37,7 +38,7 @@ class PartySelectConfiguration(
)
class PartySelectGUI(
val config: PartySelectConfiguration
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")) {
) : Screen(Component.translatable("cobblemon.ui.interact.moveselect")), CobblemonRenderable {
companion object {
const val WIDTH = 163
const val HEIGHT = 132
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.pokemon.PokemonProperties
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.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.summary.widgets.PartySlotWidget
import com.cobblemon.mod.common.client.render.drawScaledText
@@ -42,7 +43,7 @@ class PartySlotButton(
val enabled: Boolean = true,
val parent: PartySelectGUI,
onPress: OnPress
) : Button(x, y, WIDTH, HEIGHT, Component.literal("Pokemon"), onPress, CreateNarration { "".text() }) {
) : Button(x, y, WIDTH, HEIGHT, Component.literal("Pokemon"), onPress, CreateNarration { "".text() }), CobblemonRenderable {
companion object {
private val slotResource = cobblemonResource("textures/gui/interact/party_select_slot.png")
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.client.gui.interact.wheel
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
@@ -29,7 +30,7 @@ class InteractWheelButton(
private val colour: () -> Vector3f?,
onPress: OnPress,
private val canHover: (Double, Double) -> Boolean
) : Button(x, y, BUTTON_SIZE, BUTTON_SIZE, Component.literal("Interact"), onPress, DEFAULT_NARRATION) {
) : Button(x, y, BUTTON_SIZE, BUTTON_SIZE, Component.literal("Interact"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
const val BUTTON_SIZE = 69
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.client.gui.interact.wheel
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.startselection.widgets.preview.ArrowButton
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.common.collect.Multimap
@@ -20,7 +21,7 @@ import net.minecraft.client.gui.screens.Screen
import net.minecraft.network.chat.Component
import kotlin.math.max
class InteractWheelGUI(private val options: Multimap<Orientation, InteractWheelOption>, title: Component) : Screen(title) {
class InteractWheelGUI(private val options: Multimap<Orientation, InteractWheelOption>, title: Component) : Screen(title), CobblemonRenderable {
companion object {
const val SIZE = 140
const val OPTION_SIZE = 71
@@ -0,0 +1,109 @@
/*
* Copyright (C) 2023 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.npc
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.render.drawScaledText
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.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.MutableComponent
class NPCEditorButton(
var buttonX: Float,
var buttonY: Float,
var label: MutableComponent,
var cycleButtonState: Boolean? = null,
var buttonWidth: Int = Minecraft.getInstance().font.width(label) + (BUTTON_PADDING * 2),
val silent: Boolean = false,
val alignRight: Boolean = false,
val clickAction: OnPress
): Button(if (alignRight) (buttonX - buttonWidth).toInt() else buttonX.toInt(), buttonY.toInt(), buttonWidth, HEIGHT, "".text(), clickAction, DEFAULT_NARRATION) {
companion object {
val HEIGHT = 16
val BORDER_WIDTH = 2
val BUTTON_PADDING = 6
val buttonResource = cobblemonResource("textures/gui/npc/button_base.png")
val buttonBorderResource = cobblemonResource("textures/gui/npc/button_border.png")
}
var isWidgetActive = false
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
val matrices = context.pose()
// Left border
blitk(
matrixStack = matrices,
texture = buttonBorderResource,
x = x,
y = y,
width = BORDER_WIDTH,
height = HEIGHT,
vOffset = if (isMouseOver(mouseX.toDouble(), mouseY.toDouble()) || isWidgetActive) HEIGHT else 0,
textureWidth = 3,
textureHeight = HEIGHT * 2,
)
// Base
blitk(
matrixStack = matrices,
texture = buttonResource,
x = x + BORDER_WIDTH,
y = y,
width = buttonWidth - (BORDER_WIDTH * 2),
height = HEIGHT,
vOffset = if (isMouseOver(mouseX.toDouble(), mouseY.toDouble()) || isWidgetActive) HEIGHT else 0,
textureHeight = HEIGHT * 2,
)
// Right border
blitk(
matrixStack = matrices,
texture = buttonBorderResource,
x = x + (buttonWidth - BORDER_WIDTH),
y = y,
width = BORDER_WIDTH,
height = HEIGHT,
uOffset = 1,
vOffset = if (isMouseOver(mouseX.toDouble(), mouseY.toDouble()) || isWidgetActive) HEIGHT else 0,
textureWidth = 3,
textureHeight = HEIGHT * 2,
)
var buttonLabel = if (cycleButtonState != null) "options.${if (cycleButtonState as Boolean) "on" else "off"}.composed".asTranslated(label) else label
drawScaledText(
context = context,
text = buttonLabel,
x = x + (buttonWidth / 2),
y = y + 4,
centered = true,
shadow = true
)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (active && isMouseOver(mouseX, mouseY)) {
super.mouseClicked(mouseX, mouseY, button)
}
return false
}
override fun playDownSound(soundManager: SoundManager) {
if (active && !this.silent) {
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
}
}
@@ -9,26 +9,35 @@
package com.cobblemon.mod.common.client.gui.npc
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.gui.drawText
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.npc.widgets.ConfigVariableList
import com.cobblemon.mod.common.client.gui.npc.widgets.NPCRenderWidget
import com.cobblemon.mod.common.client.gui.npc.widgets.NPCRenderWidget.Companion.HEIGHT
import com.cobblemon.mod.common.client.gui.npc.widgets.NPCRenderWidget.Companion.WIDTH
import com.cobblemon.mod.common.client.gui.npc.widgets.SimpleNPCTextInputWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
import com.cobblemon.mod.common.net.messages.client.npc.dto.NPCConfigurationDTO
import com.cobblemon.mod.common.net.messages.server.npc.SaveNPCPacket
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.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.screens.Screen
import java.util.UUID
class NPCEditorScreen(
val npcId: Int,
val dto: NPCConfigurationDTO
) : Screen("gui.npc_editor.title".asTranslated()) {
) : Screen("gui.npc_editor.title".asTranslated()), CobblemonRenderable {
companion object {
const val BASE_WIDTH = 331
const val BASE_HEIGHT = 161
const val BASE_WIDTH = 360
const val BASE_HEIGHT = 220
private val baseResource = cobblemonResource("textures/gui/npc/editor_base.png")
val baseResource = cobblemonResource("textures/gui/npc/base.png")
}
val middleX: Int
@@ -43,42 +52,52 @@ class NPCEditorScreen(
override fun init() {
super.init()
addRenderableOnly(NPCRenderWidget(leftX + 6, topY + 32, dto.npcClass, dto.aspects))
addRenderableOnly(NPCRenderWidget(leftX + 12, topY + 35, dto.npcClass, dto.aspects))
addRenderableWidget(SimpleNPCTextInputWidget(
texture = cobblemonResource("textures/gui/npc/basic_text_input.png"),
getter = { dto.npcName.string },
setter = { dto.npcName = it.text() },
x = leftX + 79,
y = topY + 14,
width = 130,
height = 22
posX = leftX + 12,
posY = topY + 9,
width = 192,
height = 22,
maxLength = 32
))
addRenderableWidget(SimpleNPCTextInputWidget(
texture = cobblemonResource("textures/gui/npc/aspects_text_input.png"),
getter = { dto.aspects.joinToString() },
setter = {
dto.aspects.clear()
dto.aspects.addAll(it.split(",").map { it.trim() })
},
x = leftX + 79,
y = topY + 36,
width = 130,
posX = leftX + 12,
posY = topY + 155,
width = 116,
height = 40,
maxLength = 75,
maxLength = 72,
wrap = true
))
addRenderableWidget(
Button
.builder("Save".text()) {
SaveNPCPacket(npcId, dto).sendToServer()
this.minecraft!!.setScreen(null)
}
.pos(leftX + BASE_WIDTH - 42, topY + BASE_HEIGHT - 18)
.size(40, 16)
.build()
NPCEditorButton(
buttonX = leftX + 348F,
buttonY = topY + 201F,
label = lang("ui.generic.save"),
alignRight = true
) {
SaveNPCPacket(npcId, dto).sendToServer()
this.minecraft!!.setScreen(null)
}
)
addRenderableWidget(ConfigVariableList(leftX + 134, topY + 35, this))
}
override fun renderBlurredBackground(delta: Float) {}
override fun renderMenuBackground(context: GuiGraphics) {}
override fun isPauseScreen() = false
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
blitk(
matrixStack = context.pose(),
@@ -88,6 +107,18 @@ class NPCEditorScreen(
height = BASE_HEIGHT,
width = BASE_WIDTH
)
var extendedId = npcId.toString()
while (extendedId.length < 12) extendedId = "0$extendedId"
drawScaledTextJustifiedRight(
context = context,
text = extendedId.text(),
x = leftX + 348,
y = topY + 9,
shadow = true
)
super.render(context, mouseX, mouseY, delta)
}
}
@@ -0,0 +1,217 @@
/*
* Copyright (C) 2023 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.npc.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.npc.NPCClasses
import com.cobblemon.mod.common.api.npc.configuration.NPCConfigVariable
import com.cobblemon.mod.common.api.npc.configuration.NPCConfigVariable.NPCVariableType
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.npc.NPCEditorButton
import com.cobblemon.mod.common.client.gui.npc.NPCEditorScreen
import com.cobblemon.mod.common.client.gui.npc.widgets.ConfigVariableList.ConfigVariable
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.ContainerObjectSelectionList
import net.minecraft.client.gui.components.EditBox
import net.minecraft.client.gui.components.Tooltip
import net.minecraft.client.gui.components.events.GuiEventListener
import net.minecraft.client.gui.narration.NarratableEntry
class ConfigVariableList(
val listX: Int,
var listY: Int,
val parent: NPCEditorScreen
) : ContainerObjectSelectionList<ConfigVariable>(
Minecraft.getInstance(),
WIDTH, // width
HEIGHT, // height
0, // top
SLOT_HEIGHT + SLOT_SPACING
), CobblemonRenderable {
companion object {
const val WIDTH = 211
const val HEIGHT = 160
const val SLOT_WIDTH = 196
const val SLOT_HEIGHT = 33
const val SLOT_SPACING = 0
val textInputBackground = cobblemonResource("textures/gui/npc/text_input_base.png")
}
private var scrolling = false
override fun getRowWidth() = SLOT_WIDTH
init {
listY += 4
this.x = listX
this.y = listY
correctSize()
setRenderHeader(false, 0)
val npcClass = NPCClasses.getByIdentifier(parent.dto.npcClass)
if (npcClass != null) {
npcClass.config.forEach { variable ->
val value = parent.dto.variables[variable.variableName] ?: variable.defaultValue
addEntry(ConfigVariable(variable, value, this))
}
}
}
override fun getScrollbarPosition() = x + width - 3
public override fun addEntry(entry: ConfigVariable) = super.addEntry(entry)
public override fun removeEntry(entry: ConfigVariable) = super.removeEntry(entry)
override fun renderListSeparators(guiGraphics: GuiGraphics) {}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
correctSize()
super.renderWidget(context, mouseX, mouseY, partialTicks)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
updateScrollingState(mouseX, mouseY)
if (scrolling) {
focused = getEntryAtPosition(mouseX, mouseY)
isDragging = true
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
if (scrolling) {
if (mouseY < this.listY) {
scrollAmount = 0.0
} else if (mouseY > bottom) {
scrollAmount = maxScroll.toDouble()
} else {
scrollAmount += deltaY
}
}
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
}
private fun updateScrollingState(mouseX: Double, mouseY: Double) {
scrolling = mouseX >= this.scrollbarPosition.toDouble()
&& mouseX < (this.scrollbarPosition + 3).toDouble()
&& mouseY >= listY
&& mouseY < bottom
}
override fun renderListBackground(context: GuiGraphics) {}
private fun correctSize() {
setRectangle(WIDTH, HEIGHT, listX, (listY - 4))
}
fun isHovered(mouseX: Double, mouseY: Double) = mouseX.toFloat() in (x.toFloat()..(x.toFloat() + WIDTH)) && mouseY.toFloat() in (y.toFloat()..(y.toFloat() + HEIGHT))
class ConfigVariable(val variable: NPCConfigVariable, val value: String, private val parent: ConfigVariableList) : Entry<ConfigVariable>() {
val client: Minecraft = Minecraft.getInstance()
var _focused = false
var children = mutableListOf<GuiEventListener>()
var textValue = value
var booleanValue = value.let { it.toDoubleOrNull() == 1.0 || it.toBooleanStrictOrNull() == true }
val editBox = EditBox(
client.font,
parent.listX,
parent.listY + 8,
SLOT_WIDTH - 8,
SLOT_HEIGHT - 8,
variable.displayName,
).also {
it.isBordered = false
it.tooltip = Tooltip.create(variable.description)
it.height = SLOT_HEIGHT - 16
it.setMaxLength(250)
it.value = value
it.setResponder {
textValue = it
parent.parent.dto.variables[variable.variableName] = it
}
if (variable.type == NPCVariableType.NUMBER) {
it.setFilter { value -> value.toDoubleOrNull() != null || value.isBlank() || value == "." || value == "-" }
}
}
val toggleButton = NPCEditorButton(
parent.listX.toFloat(),
parent.listY + 6F,
variable.displayName.copy(),
booleanValue,
SLOT_WIDTH
) {
booleanValue = !booleanValue
parent.parent.dto.variables[variable.variableName] = if (booleanValue) "1" else "0"
(it as NPCEditorButton).cycleButtonState = booleanValue
}.also {
it.tooltip = Tooltip.create(variable.description)
}
init {
if (variable.type == NPCVariableType.BOOLEAN) {
children.add(toggleButton) // children.add(cycleButton)
} else {
children.add(editBox)
}
}
override fun isFocused() = _focused
override fun setFocused(focused: Boolean) {
this._focused = focused
}
override fun children() = children
override fun narratables(): List<NarratableEntry> {
return children.filterIsInstance<NarratableEntry>()
}
override fun render(
context: GuiGraphics,
index: Int,
rowTop: Int,
rowLeft: Int,
rowWidth: Int,
rowHeight: Int,
mouseX: Int,
mouseY: Int,
isHovered: Boolean,
partialTicks: Float
) {
val x = rowLeft - 4
val y = rowTop
if (variable.type == NPCVariableType.BOOLEAN) {
toggleButton.x = x
toggleButton.y = y + 9
toggleButton.render(context, mouseX, mouseY, partialTicks)
} else {
drawScaledText(
context = context,
text = variable.displayName.visualOrderText,
x = x,
y = y + 4,
shadow = true
)
blitk(matrixStack = context.pose(), texture = textInputBackground, x = x, y = y + 16, height = 12, width = 1)
blitk(matrixStack = context.pose(), texture = textInputBackground, x = x + 1, y = y + 15, height = 14, width = SLOT_WIDTH - 2)
blitk(matrixStack = context.pose(), texture = textInputBackground, x = x + SLOT_WIDTH - 1, y = y + 16, height = 12, width = 1)
editBox.x = x + 4
editBox.y = y + 18
editBox.render(context, mouseX, mouseY, partialTicks)
}
}
}
}
@@ -8,11 +8,10 @@
package com.cobblemon.mod.common.client.gui.npc.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.gui.drawProfile
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.repository.NPCModelRepository
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Renderable
import net.minecraft.client.gui.components.events.GuiEventListener
@@ -23,41 +22,40 @@ class NPCRenderWidget(
val y: Int,
var identifier: ResourceLocation,
val aspects: MutableSet<String>
) : Renderable, GuiEventListener {
) : CobblemonRenderable, GuiEventListener {
val state = FloatingState().also {
it.currentAspects = aspects
}
companion object {
val profileBackgroundResource = cobblemonResource("textures/gui/npc/profile_background.png")
const val WIDTH = 66
const val HEIGHT = 66
const val WIDTH = 116
const val HEIGHT = 112
}
override fun isFocused() = false
override fun setFocused(focused: Boolean) {}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
blitk(
matrixStack = context.pose(),
texture = profileBackgroundResource,
x = x,
y = y,
width = WIDTH,
height = HEIGHT
)
context.enableScissor(
x,
y,
x + WIDTH,
y + HEIGHT
)
context.pose().pushPose()
context.pose().translate(x + WIDTH / 2F, y + HEIGHT / 2F, 0F)
// Decrease on Z-axis to prevent model from rendering above other components like tooltips
context.pose().translate(x + (WIDTH / 2F), y + HEIGHT + (HEIGHT / 4F), -500F)
drawProfile(
repository = NPCModelRepository,
resourceIdentifier = identifier,
matrixStack = context.pose(),
partialTicks = delta,
scale = 30F,
scale = 120F,
state = state
)
context.pose().popPose()
context.disableScissor()
}
}
@@ -9,89 +9,142 @@
package com.cobblemon.mod.common.client.gui.npc.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.gui.drawCenteredText
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.render.drawScaledText
import net.minecraft.Util
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.EditBox
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.chat.contents.PlainTextContents.LiteralContents
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.renderer.RenderType
import net.minecraft.network.chat.Style
import net.minecraft.resources.ResourceLocation
import kotlin.math.floor
class SimpleNPCTextInputWidget(
getter: () -> String,
val texture: ResourceLocation,
val texture: ResourceLocation? = null,
private val setter: (String) -> Unit,
x: Int,
y: Int,
posX: Int,
posY: Int,
width: Int,
height: Int,
maxLength: Int = 100,
val shadow: Boolean = true,
val wrap: Boolean = false
) : EditBox(
Minecraft.getInstance().font,
x,
y,
posX + TEXT_PADDING,
posY,
width,
height,
"input".text()
) {
), CobblemonRenderable {
companion object {
const val TEXT_PADDING = 4
}
var focusedTime: Long = 0
init {
setMaxLength(maxLength)
isFocused = true
focusedTime = Util.getMillis()
value = getter()
this.setResponder { setter(it) }
setResponder { setter(it) }
setFocused(false)
isBordered = false
}
override fun setFocused(focused: Boolean) {
if (focused) focusedTime = Util.getMillis()
super.setFocused(focused)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
return if (mouseX.toInt() in x..(x + width) && mouseY.toInt() in y..(y + height)) {
isFocused = true
true
} else {
false
val clickedState = super.mouseClicked(mouseX, mouseY, button)
if (wrap) {
val splitLines = Minecraft.getInstance().font.splitter.splitLines(value, width - 8, Style.EMPTY)
val clickedLineIndex = floor((mouseY - y)/11).toInt()
if (clickedLineIndex >= 0 && splitLines.size > clickedLineIndex) {
val lineAtIndex = splitLines.get(clickedLineIndex)
var lineSubstring = ""
for (char in lineAtIndex.string) {
lineSubstring += char
val startToLineWidth = Minecraft.getInstance().font.width(lineSubstring.text())
if ((mouseX - x) <= startToLineWidth) break
}
var unwrappedLine = ""
splitLines.forEachIndexed { index, line ->
if (index == clickedLineIndex) {
unwrappedLine += lineSubstring
} else if (index < clickedLineIndex) {
unwrappedLine += line.string
}
}
this.moveCursorTo(maxOf(0, unwrappedLine.length - 1), Screen.hasShiftDown())
}
}
return clickedState
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
if (cursorPosition != value.length) {
moveCursorToEnd(false)
if (texture != null) {
blitk(
matrixStack = context.pose(),
x = x,
y = y,
width = width,
height = height,
texture = texture
)
}
blitk(
matrixStack = context.pose(),
x = x,
y = y,
width = width,
height = height,
texture = texture
)
context.pose().pushPose()
val scale = 0.8F
context.pose().scale(scale, scale, 1F)
if (wrap) {
val wrappedLines = Minecraft.getInstance().font.split(MutableComponent.create(LiteralContents(value)), ((width - 8) / scale).toInt())
val showCursor = isFocused && ((Util.getMillis() - this.focusedTime) / 300L % 2L == 0L)
val input = (if (isFocused) "${value}${if ((cursorPosition == value.length) && showCursor) "_" else ""}" else value)
var cursorLine = ""
var cursorLineIndex = -1
var cursorLinePos = 0
var accumlatedLength = 0
val wrappedLines = Minecraft.getInstance().font.splitter.splitLines(input, width - (TEXT_PADDING * 2), Style.EMPTY)
for ((index, line) in wrappedLines.withIndex()) {
context.drawString(
Minecraft.getInstance().font,
line,
((x + 4) / scale).toInt(),
((y + index * 10 + 5) / scale).toInt(),
0xFFFFFF,
false
accumlatedLength += line.string.length
if (cursorLineIndex < 0 && accumlatedLength > cursorPosition) {
cursorLineIndex = index
cursorLine = line.string
cursorLinePos = if (cursorLineIndex > 0) cursorPosition - (accumlatedLength - line.string.length) else cursorPosition
}
drawScaledText(
context = context,
text = line.string.text(),
x = x,
y = y + index * 10 + 1,
colour = 0xDDDDDD,
shadow = true
)
}
if (showCursor && !value.isEmpty() && cursorPosition != value.length) {
val startToCursorWidth = Minecraft.getInstance().font.width((cursorLine.substring(0, cursorLinePos).text()))
context.fill(
RenderType.guiTextHighlight(),
x + startToCursorWidth - 1,
y + cursorLineIndex * 10 + 1,
x + startToCursorWidth,
y + (cursorLineIndex * 10 + 1) + 9,
-3092272
)
}
} else {
drawCenteredText(
context = context,
text = value.text(),
x = (x + width / 2F) / scale,
y = (y + height / 2 - 4) / scale,
shadow = true,
colour = 0xFFFFFF
)
super.renderWidget(context, mouseX, mouseY, delta)
}
context.pose().popPose()
}
}
@@ -13,6 +13,7 @@ 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.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pc.StorageSlot
import com.cobblemon.mod.common.client.gui.summary.widgets.PartySlotWidget
@@ -40,7 +41,7 @@ class PasturePokemonScrollList(
HEIGHT, // height
0, // top
SLOT_HEIGHT + SLOT_SPACING
) {
), CobblemonRenderable {
companion object {
const val WIDTH = 70
const val HEIGHT = 120
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.client.gui.pasture
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
@@ -18,7 +19,7 @@ import net.minecraft.network.chat.Component
class PastureSlotIconButton(
var xPos: Int, var yPos: Int,
onPress: OnPress
) : Button(xPos, yPos, (SIZE * SCALE).toInt(), (SIZE * SCALE).toInt(), Component.literal("Pasture Move"), onPress, DEFAULT_NARRATION) {
) : Button(xPos, yPos, (SIZE * SCALE).toInt(), (SIZE * SCALE).toInt(), Component.literal("Pasture Move"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
const val SIZE = 14
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.gui.pasture
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.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
@@ -22,7 +23,7 @@ import net.minecraft.network.chat.Component
class RecallButton(
x: Int, y: Int,
onPress: OnPress
) : Button(x, y, WIDTH, HEIGHT, Component.literal("Retrieve"), onPress, DEFAULT_NARRATION) {
) : Button(x, y, WIDTH, HEIGHT, Component.literal("Retrieve"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
private const val WIDTH = 70
@@ -10,6 +10,7 @@ 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.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
@@ -21,7 +22,7 @@ class NavigationButton(
pX: Int, pY: Int,
private val forward: Boolean,
onPress: OnPress
): Button(pX, pY, (WIDTH * SCALE).toInt(), (HEIGHT * SCALE).toInt(), Component.literal("Navigation"), onPress, DEFAULT_NARRATION) {
): Button(pX, pY, (WIDTH * SCALE).toInt(), (HEIGHT * SCALE).toInt(), Component.literal("Navigation"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
private const val WIDTH = 8F
@@ -13,6 +13,7 @@ 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.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.Summary
@@ -40,7 +41,7 @@ class PCGUI(
val pc: ClientPC,
val party: ClientParty,
val configuration: PCGUIConfiguration
) : Screen(Component.translatable("cobblemon.ui.pc.title")) {
) : Screen(Component.translatable("cobblemon.ui.pc.title")), CobblemonRenderable {
companion object {
const val BASE_WIDTH = 349
@@ -11,6 +11,7 @@ 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.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
@@ -23,7 +24,7 @@ class ReleaseButton(
x: Int, y: Int,
private val parent: StorageWidget,
onPress: OnPress
) : Button(x, y, WIDTH, HEIGHT, Component.literal("Release"), onPress, DEFAULT_NARRATION) {
) : Button(x, y, WIDTH, HEIGHT, Component.literal("Release"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
private const val WIDTH = 58
@@ -11,6 +11,7 @@ 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.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
@@ -24,7 +25,7 @@ class ReleaseConfirmButton(
private val parent: StorageWidget,
private val subKey: String,
onPress: OnPress
) : Button(x, y, WIDTH, HEIGHT, Component.literal("ReleaseConfirm"), onPress, DEFAULT_NARRATION) {
) : Button(x, y, WIDTH, HEIGHT, Component.literal("ReleaseConfirm"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
private const val WIDTH = 30
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.client.gui.pc
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.pasture.PasturePCGUIConfiguration
import com.cobblemon.mod.common.client.render.drawScaledText
@@ -31,7 +32,7 @@ open class StorageSlot(
x: Int, y: Int,
private val parent: StorageWidget,
onPress: OnPress
) : Button(x, y, SIZE, SIZE, Component.literal("StorageSlot"), onPress, DEFAULT_NARRATION) {
) : Button(x, y, SIZE, SIZE, Component.literal("StorageSlot"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
val state = FloatingState()
companion object {
@@ -29,6 +29,7 @@ import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.BASE_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HEADER_BAR_HEIGHT
@@ -75,7 +76,7 @@ class PokedexGUI private constructor(
val type: PokedexType,
val initSpecies: ResourceLocation?,
val blockPos: BlockPos?
): Screen(Component.translatable("cobblemon.ui.pokedex.title")) {
): Screen(Component.translatable("cobblemon.ui.pokedex.title")), CobblemonRenderable {
companion object {
private val screenBackground = cobblemonResource("textures/gui/pokedex/pokedex_screen.png")
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.gui.pokedex
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.narration.NarrationElementOutput
@@ -27,7 +28,7 @@ class ScaledButton(
val scale: Float = 0.5F,
val silent: Boolean = false,
val clickAction: Button.OnPress
): Button(buttonX.toInt(), buttonY.toInt(), buttonWidth.toInt(), buttonHeight.toInt(), "".text(), clickAction, DEFAULT_NARRATION) {
): Button(buttonX.toInt(), buttonY.toInt(), buttonWidth.toInt(), buttonHeight.toInt(), "".text(), clickAction, DEFAULT_NARRATION), CobblemonRenderable {
var isWidgetActive = false
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.client.gui.pokedex.widgets
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.gui.ParentWidget
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
@@ -688,6 +689,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
fun isWithinPortraitSpace(mouseX: Double, mouseY: Double): Boolean =
mouseX.toInt() in pX + 15..(pX + 15 + PORTRAIT_POKE_BALL_WIDTH)
&& mouseY.toInt() in pY + 25..(pY + 25 + PORTRAIT_POKE_BALL_HEIGHT)
&& !children.any { it.isMouseOver(mouseX, mouseY) && it is ScaledButton }
private fun isSelectedPokemonOwned(): Boolean {
return currentEntry?.let { CobblemonClient.clientPokedexData.getKnowledgeForSpecies(it.speciesId) } == PokedexEntryProgress.CAUGHT

Some files were not shown because too many files have changed in this diff Show More