Compare commits

..
Author SHA1 Message Date
amo c7767e68bc Merge branch 'main' into 'observationally-overboard'
Adding an implementation of Observable Collections & utilities:

See merge request cable-mc/cobblemon!1109
2024-09-07 12:54:52 +00:00
amo 444a70e1fa e 2024-09-07 13:52:44 +01:00
Eight 8a58142b62 Implemented ObservableSubList.kt, tests, & updated iterators:
1. ObservableSubList:
    - Switched from returning kotlin sublist to an observable implementation that can still be subscribed to.
    - Split MutableObservableSubList functions to match the other observable collections.
    - Implemented iterators.

2. Tests:
    - Implemented tests for the current functionality to help with future iterations & reliability.
    - AllCollectionTests.kt
    - ConstructionTests.kt
    - EmittingReferenceTypeTests.kt
    - EmittingTests.kt
    - IteratorTests.kt

3. Updated iterators:
    - Removed redundant construction of kotlin iterators with observable collection iterators where possible.
    - Fixed a possible out of range exception when attempting to get an element from an empty sublist iterator & an observable list with no element below the fromIndex.
2024-08-05 17:37:48 -07:00
Eight a108119420 Merge branch 'master' into 'main'
Observable collections & utilities:

See merge request SomeGuyEight/cobblemon!1
2024-08-05 06:19:52 +00:00
Eight 6572478ee1 Adding an implementation of Observable Collections & utilities:
These are collection implementations that extend the observable interface & api. The collections can manage all observables within the stored elements & entries. Each will emit with any mutation to the elements contained or a mutation of the collection like addition, removal, set, or swap. Each collection implements a subscription object that holds the subscriptions of all possible observables for the associated collection.

    Each collection behaves as the kotlin implementation is expected to aside from the sublist. The sublist behaves as the kotlin equivalent until the backing list is mutated. When the backing list of the kotlin sublist is mutated, accessing the sublist will throw an exception, but the MutableObservableSubList.kt will continue to stay synced. It will continue to emit & return values from the backing list for the index window it is constructed with.

1. Observable Collections implemented:
    A) ObservableCollection.kt & MutableObservableCollection.kt interfaces
        - ObservableSet.kt & MutableObservableSet.kt implementations
        - ObservableList.kt & MutableObservableList.kt implementations
        - MutableObservableSubList.kt implementation
    B) ObservableMap.kt & MutableObservableMap.kt

2. Observable Subscriptions:
    A) CollectionSubscription.kt interface
        - SetSubscription.kt
        - ListSubscription.kt
        - IndexedListSubscription
    B) MapSubscription.kt

3. Iterators:
    A) ObservableSetIterator.kt & MutableObservableListIterator.kt
    B) ObservableListIterator.kt & MutableObservableListIterator.kt
    C) MutableObservableSubListIterator.kt
    D) ObservableMapIterator.kt & MutableObservableMapIterator.kt

4. Nbt serialization utils:
    A) ObservableSetSerialization.kt
    B) ObservableListSerialization.kt
    C) ObservableMapSerialization.kt
2024-08-04 22:56:22 -07:00
986 changed files with 101128 additions and 111487 deletions

No files matched your search

+1 -1
View File
@@ -35,7 +35,7 @@ publish:
- neoforge/build/libs/*.jar
- fabric/build/libs/*.jar
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
- if: '$CI_COMMIT_BRANCH == "sega_bass_pro_fishing_2"'
script:
- export GRADLE_OPTS="-Xmx4G -Xms4G"
- chmod +x ./gradlew
-6
View File
@@ -1,6 +0,0 @@
### Additions
- Added `moves` option to PokemonProperties, allowing you to set the moves of a Pokémon in commands and spawn files.
- Added /spawnnpc and /spawnnpcat commands.
### Fixes
- Fixed sync issues with recently learned moves where their PP would not go down until you logout and in.
+150 -168
View File
@@ -1,36 +1,75 @@
# Changelog
## [1.6.0 - The Bass Pro Fishing Update (Month Xth, 2024)](#1-6-0)
#### "Now that there's a fishing mechanic, the mod is actually good!"
### Fixes
- Fixed trading Pokémon setting their friendsip to 0 instead of the base value.
- Fixed a scenario where a Pokémon that rolled to spawn with a special Tera type sometimes has a Tera type that is already a part of their natural typing.
- Fixed PokemonProperty argument suggestions for ``tera`` and ``tera_type`` not suggesting ``stellar``.
- Fixed being able to stack Relic Coin Pouches on top of each other.
- Bag Items used during a turn will now be refunded if the battle ends before the next turn begins.
### Additions
- Added Pokémon (and item) fishing using modified fishing rods - Poké Rods! You'll need a Poké Rod smithing template, a fishing rod, and some type of Poké Ball. Each Poké Ball makes a differently themed rod. Why not?
- Added Lure Ball functionality, increasing the catch rate of Pokémon that were caught on a fishing rod.
- Added Repeat Ball functionality, increasing the catch rate of Pokémon that are already registered as caught in a player's Pokédex.
- Added property chaining support for duplicate CustomPokemonPropertyType elements.
- Added `aspect` and `unaspect` PokemonProperty arguments (which also includes commands such as `/pokemonedit`, `/spawnpokemon`, and `/givepokemon`) to allow forcing or un-forcing an aspect on a Pokémon.
- Added `type` alternatively `elemental_type` PokemonProperty argument, this is only used for filtering and is not applied to Pokémon. Example `type=fire` would be true for Charmander but false for Squirtle.
- Added ``aspect`` and ``unaspect`` PokemonProperty arguments (which also includes commands such as /pokemonedit, /spawnpokemon, and /givepokemon) to allow forcing or un-forcing an aspect on a Pokémon.
- Added ``type`` alternatively ``elemental_type`` PokemonProperty argument, this is only used for filtering and is not applied to Pokémon. Example ``type=fire`` would be true for Charmander but false for Squirtle.
- Added support for Double Battles, Triple Battles, and Multi-battles.
- CriticalCaptures and Pokédex progress capture multiplier now work with the Pokédex.
- Added extra visuals to early berry growth stages.
- Adjusted Pokémon sendout positions for single battles.
- Wild Pokémon interaction range increased to 12 blocks (from 10 blocks)
- Player trade range increased to 12 blocks (from 10 blocks)
- PvP battle range increased to 32 blocks (from 10 blocks)
- Spectate range increased to 64 blocks (from 10 blocks)
### Developer
- SpawnCause is now an implementation of SpawningInfluence.
- Added "forcedAspects" to Pokemon to make it easier to easily add basic aspects to a Pokémon in a way that persists. We already know that everyone is going to overuse this.
- Made the Pokemon.aspects setter private. This could technically break sidemods but if you are affected by this then you were using it wrong! Use Pokemon.forcedAspects to fix it.
- Many types related to ``Pokemon`` including itself now have dedicated ``Codec``, please migrate to them from the NBT/JSON/PacketBuffer write/read methods.
- ``TeraTypes`` now implements ``Iterable``.
## [1.5.3](#1-5-3)
### Fixes
- Scaled down Amaura's fetus model to avoid clipping through the tank while animating
- Fixed Cubone's cry not having a sound.
- Fixed the Seafloor spawning context not being a usable context.
- Fixed Pokemon spawning in non-full blocks like slabs.
- Fixed Gilded Chests not dropping the chest itself when broken, only the contents.
- Fixed Pokémon losing their Hidden Ability through evolution if the middle stage did not have a Hidden Ability.
- Hidden Power no longer plays the water type action effect. (It now plays the normal type action effect.)
- Fixed Chimchar and Monferno T posing whenever they sleep.
- Fixed the Magby line not having any placeholder walk animations.
- Fixed Duskull and Dusclops using skylight levels for their nether spawn data. There is no sun in the Nether!
- Fixed Hisuian Zoroark using base Zoroark stats.
- Fixed Bellossom clipping into the player head when shoulder mounted.
- Fixed Shroomish's look range to prevent it from looking higher than it should.
- Fixed Crumbling Arch not blending in with the world.
- Fixed Energy Root applying being usable on a fainted Pokemon.
- Fixed Pokémon entity not spawning when starting a battle while recalling said Pokémon
- Fixed species comparison not using namespace for both sides in PokemonProperties.isSubsetOf.
- Fixed PokemonProperties#asString prefixing nicknames with a '$'
- Fixed PC not saving when Pokemon was changed outside.
- Fixed shearble Pokemon not dropping correct wool colors.
- Fixed pasture spawning Pokemon inside solid blocks when closest spawning position is blocked off.
- Fixed Tumbling Down advancement not being granted by tumblestone variants.
- Improve error handling when loading spawn-sets to ensure invalid configurations don't crash the server
- Fixed empty JsonPlayerData files resulting in players being unable to join server/world
### Additions
- Added extra visuals to early berry growth stages
- Hidden Power now displays its effective typing.
- Moves impacted by the abilities Pixelate, Refrigerate, Aerilate, Galvanize, and Normalize now display as their altered typing.
- Added Polished Tumblestone and Tumblestone Brick block sets from Tumblestone, Black Tumblestone, and Sky Tumblestone.
- Added Fire, Water, Thunder, Leaf, Ice, Sun, Moon, Shiny, Dawn, and Dusk Stone storage blocks.
- Added Eject Pack, Metronome, Protective Pads, Punching Glove, Room Service, Scope Lens, Shed Shell, Terrain Extender, Throat Spray, Utility Umbrella, Wide Lens, and Zoom Lens held items
- Added Fire, Water, Thunder, Leaf, Ice, Sun, Moon, Shiny, Dawn, and Dusk Stone Storage Blocks.
- Added Metronome, Protective Pads, Punching Glove, Room Service, Scope Lens, Shed Shell, Terrain Extender, Throat Spray, Utility Umbrella, Wide Lens, and Zoom Lens held items
- Moves can now be benched without specifying a replacement move, allowing for empty move slots.
- Moves learned via Sketch now persist after battle.
- Added `stonjourner_henge_ruins`, `luna_henge_ruins`, and `sol_henge_ruins` structures.
- Added a `battleInvulnerability` gamerule to make players invulnerable to any damage during a battle.
- Added a `mobTargetInBattle` gamerule to exclude players from being targeted by mobs during a battle.
- Added battle log messages for switching out Pokémon.
- Added Evolution particles and sounds for Pokémon that are sent out when evolution is started.
- Added a Nurse profession that can be unlocked by having villagers claim a healing machine block.
- Added pokecenters to all 5 village types.
- Added a `natural` block state property for the healing machine block; when property is set to true, the block will have a different texture and drop an iron ingot instead of itself.
- New config setting `displayEntityNameLabel` and `displayEntityLabelsWhenCrouchingOnly` to control what and when is displayed for the pokemon label
- `/freezepokemon` command to pause a Pokémon's animation at a specific point in time.
- Added `no_ai` and `freeze_frame` options to the `/spawnpokemon` command.
- Added shiny Pokémon particles with sound effects.
- Added ston_henge_ruins, luna_henge_ruins, and sol_henge_ruins structures.
- Added Eject Pack, Metronome, Protective Pads, Punching Glove, Room Service, Scope Lens, Shed Shell, Terrain Extender, Throat Spray, Utility Umbrella, Wide Lens, and Zoom Lens held items
- Added a `battleInvulnerability` gamerule to make players invulnerable to any damage during a battle
- Added a `mobTargetInBattle` gamerule to exclude players from being targeted by mobs during a battle
- Added battle log messages for switching out Pokémon
- Added Evolution particles and sounds for Pokemon that are sent out when evolution is started.
### Pokémon Added
#### Gen 2
@@ -44,9 +83,6 @@
#### Gen 3
- Wynaut
#### Gen 8
- Cramorant
#### Gen 9
- Farigiraf
@@ -55,125 +91,71 @@
- Golett, Golurk
### Changes
- Buffed Dusk Ball catch multipliers: Increased multiplier to 3.5 (from 3.0) in light level 0. Increased multiplier to 3.0 (from 1.5) in light levels 1-7.
- Altered the item model for Medicinal Leeks and Roasted Leeks to be held like sticks and other rod items.
- Adjusted some berry balance values like yield and growth times.
- Adjusted some berry balance values like yield and growth times
- Adjusted volumes of sounds made by Display Cases, Berry Bushes, Energy Root, Medicinal Leek, Vivichoke, Mints, Revival Herbs and Gilded Chests.
- Changes to mulch buffs/durations.
- Dreepy is now shoulder mountable.
- Updated potion sprites.
- Weedle, Caterpie, Spearow, Smoochum, Mew, Murkrow, Larvitar, Taillow, Plusle, Minun, Beldum, Starly, Buneary, Combee, Pachirisu, Pidove, Petilil, Hisui Bias Petilil, Zorua, Hisuian Zorua, Elgyem, Fletchling, Skrelp, Klefki, Fomantis, Morelull, Dreepy, Shroodle, Tatsugiri, Glimmet, and Roaming Gimmighoul are now shoulder mountable.
- Link Cable recipe has been redesigned.
- Updated sounds for Medicinal Leeks, Big Roots, Energy Roots and Revival Herbs.
- Updated UI sounds for clicking and evolving Pokémon.
- Changes to mulch buffs/durations
- Dreepy is now shoulder mountable
- Link Cable recipe has been redesigned
- Updated potion sprites
- Weedle, Caterpie, Spearow, Smoochum, Mew, Murkrow, Larvitar, Taillow, Plusle, Minun, Beldum, Starly, Buneary, Combee, Pachirisu, Pidove, Petilil, Hisui Bias Petilil, Zorua, Hisuian Zorua, Elgyem, Fletchling, Skrelp, Klefki, Fomantis, Morelull, Dreepy, Shroodle, Tatsugiri, Glimmet, and Roaming Gimmighoul are now shoulder mountable
- Chikorita's ability to look around removed due to the awkwardness of it.
- Link Cable recipe has been redesigned
- Updated potion sprites
- Updated sounds for Medicinal Leeks, Big Roots, Energy Roots and Revival Herbs
- Updated UI sounds for clicking and evolving Pokémon
- Removed the interchangeable evolution results for the first stage Hisuian starters to prevent confusion. The method still exists for stage 2 to 3, but stage 1 to 2 was not distinguishable enough for many users.
- Kakuna, Beedrill, Snorlax, Munchlax, Krabby, Kingler, and Lurantis received model updates.
- Vivillon wings will stop being clear when you remove any Vivillon related resourcepacks. The default pattern will be the meadow wings.
- Adjusted Pokémon sendout positions for single battles.
- Wild Pokémon interaction range increased to 12 blocks (from 10 blocks).
- Player trade range increased to 12 blocks (from 10 blocks).
- PvP battle range increased to 32 blocks (from 10 blocks).
- Spectate range increased to 64 blocks (from 10 blocks).
- The inventory key now closes our GUIs if they are open, mirroring how Minecraft screens usually work.
- The noise from the pasture and PC blocks being interacted with now activates Sculk sensors.
- Updated dialogue GUI assets with proper assets.
- Updated party switching interface when in battle.
- Relic coin pouches can now be waterlogged.
- Pokemon Name (and level) labels are now only visible when crouching by default, set the `displayEntityLabelsWhenCrouchingOnly` to `false` to always them even when not crouching
- The "Press R to start battle prompt" will now disappear after a player's first battle has been won
- The default number of digits for the Pokédex number in the summary has been increased, from 3 to 4.
- Kakuna, Beedrill, Snorlax, Munchlax, and Lurantis received model updates
### Fixes
- Scaled down Amaura's fetus model to avoid clipping through the tank while animating.
- Fixed trading Pokémon setting their friendsip to 0 instead of the base value.
- Fixed a scenario where a Pokémon that rolled to spawn with a special Tera type sometimes has a Tera type that is already a part of their natural typing.
- Fixed PokemonProperty argument suggestions for ``tera`` and ``tera_type`` not suggesting ``stellar``.
- Fixed being able to stack Relic Coin Pouches on top of each other.
- Fixed Model loader generating misleading crash-reports when client is crashing
- Scaled down Amaura's fetus model to avoid clipping through the tank while animating
- Big Roots and Energy Roots now share the same sounds as intented.
- Fixed Cubone's cry not having a sound.
- Fixed sounds made by blocks playing at a lower pitch than intended.
- Fixed the Seafloor spawning context not being a usable context.
- Fixed Pokemon spawning in non-full blocks like slabs.
- Fixed Gilded Chests not dropping the chest itself when broken, only the contents.
- Fixed Pokémon losing their Hidden Ability through evolution if the middle stage did not have a Hidden Ability.
- Hidden Power no longer plays the water type action effect (It now plays the normal type action effect).
- Fixed Chimchar and Monferno T-posing whenever they sleep.
- Fixed the Magby line not having any placeholder walk animations.
- Fixed Duskull and Dusclops using skylight levels for their nether spawn data. There is no sun in the Nether!
- Fixed Hisuian Zoroark using base Zoroark stats.
- Fixed Bellossom clipping into the player head when shoulder mounted.
- Fixed Shroomish's look range to prevent it from looking higher than it should.
- Fixed Crumbling Arch not blending in with the world.
- Fixed Energy Root applying being usable on a fainted Pokémon.
- Fixed Pokémon entity not spawning when starting a battle while recalling said Pokémon.
- Fixed species comparison not using namespace for both sides in `PokemonProperties.isSubsetOf`.
- Fixed `PokemonProperties#asString` prefixing nicknames with a '$'.
- Fixed PC not saving when Pokémon was changed outside.
- Fixed shearable Pokémon not dropping correct wool colors.
- Fixed pasture spawning Pokémon inside solid blocks when closest spawning position is blocked off.
- Fixed Tumbling Down advancement not being granted by tumblestone variants.
- Improve error handling when loading spawn sets to ensure invalid configurations don't crash the server.
- Fixed empty `JsonPlayerData` files resulting in players being unable to join server/world.
- Evolution notification and UI sound is now correctly playing (again).
- Fix Starter prompt not showing up.
- Fix evolution now granting Pokédex entries.
- Fix Pokédex crash when a form is looked up without ever seeing the "normal" form.
- Fixed owned poké balls floating upwards if pausing mid-transition.
- Fixed all mouse buttons working for Battle UI navigation (now it's only primary/left click).
- Fixed trading Pokémon setting their friendship to 0 instead of the base value.
- Fixed a scenario where a Pokémon that rolled to spawn with a special Tera type sometimes has a Tera type that is already a part of their natural typing.
- Fixed PokemonProperty argument suggestions for `tera` and `tera_type` not suggesting `stellar`.
- Fixed being able to stack Relic Coin Pouches on top of each other.
- Fixed Model loader generating misleading crash-reports when client is crashing.
- Big Roots and Energy Roots now share the same sounds as intended.
- Fixed sounds made by blocks playing at a lower pitch than intended.
- Hidden Power no longer plays the water type action effect. (It now plays the normal type action effect.)
- Fixed Chimchar and Monferno T posing whenever they sleep.
- Fixed the Magby line not having any placeholder walk animations.
- Fixed Duskull and Dusclops using skylight levels for their nether spawn data. There is no sun in the Nether!
- Fixed Hisuian Zoroark using base Zoroark stats.
- Fixed Bellossom clipping into the player head when shoulder mounted.
- Fixed Shroomish's look range to prevent it from looking higher than it should.
- Fixed Maushold's faint animations not playing. It's sadder than Tandemaus!
- Fixed Slowking's battle idle.
- Fixed Grafaiai's walk speed to prevent model sliding.
- 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.
- Fixed `crumbling_arch_ruins` from generating a giant cube of air and removed the chest.
- Fixed Crumbling Arch not blending in with the world.
- Fixed Energy Root applying being usable on a fainted Pokemon.
- Fixed Pokémon entity not spawning when starting a battle while recalling said Pokémon
- Fixed species comparison not using namespace for both sides in PokemonProperties.isSubsetOf.
- Fixed PokemonProperties#asString prefixing nicknames with a '$'
- Fixed reviving items throwing an exception when used in battle
- Fixed messages for Focus Sash, Confusion, Mummy, Ice Face, Own Tempo, and Revive
- Fixed PC not saving when Pokemon was changed outside.
- Fixed shearble Pokemon not dropping correct wool colors.
- Fixed pasture spawning Pokemon inside solid blocks when closest spawning position is blocked off.
- Fixed Tumbling Down advancement not being granted by tumblestone variants.
- Improve error handling when loading spawn-sets to ensure invalid configurations don't crash the server
- Fixed empty JsonPlayerData files resulting in players being unable to join server/world
- Fixed crumbling_arch_ruins from generating a giant cube of air and removed the chest
- Fixed possible crash on large population servers due to concurrent access of data.
- Quickly sending out and recalling Pokémon now looks smoother and can no longer be spammed to cause desync issues.
- Pokémon are now invulnerable during sendout animation and intangible during recall animation.
- Pokémon are now invincible during sendout animation and intangible during recall animation.
- Scrolling with party keybinding now behaves properly when using high scroll speed or scroll sensitivity.
- Fixed battle log GUI flashing when Battle GUI is opened.
- Bag Items used during a turn will now be refunded if the battle ends before the next turn begins.
- Pokémon whose current owner is not its Original Trainer now gains extra experience.
- Fixed Fossil Resurrection advancement not being granted.
- Fixed village generation caps not applying for berry farms. You were not meant to see more than two berry farms per village!
- Fixed the Ice Face ability activation being displayed incorrectly in a battle
### Developer
- `SpawnCause` is now an implementation of `SpawningInfluence`.
- Many types related to `Pokemon` including itself now have dedicated `Codec`, please migrate to them from the NBT/JSON/PacketBuffer write/read methods.
- `TeraTypes` now implements `Iterable`.
- `PokemonBattle` now starts on turn 0.
- SpawnCause is now an implementation of SpawningInfluence.
- Renamed Pokemon.hp to Pokemon.maxHealth to make it clearer. Backwards compatibility is provided but it is an active deprecation, please migrate away from it.
- Added "forcedAspects" to Pokemon to make it easier to easily add basic aspects to a Pokémon in a way that persists. We already know that everyone is going to overuse this.
- Made the Pokemon.aspects setter private. This could technically break sidemods but if you are affected by this then you were using it wrong! Use Pokemon.forcedAspects to fix it.
- Many types related to ``Pokemon`` including itself now have dedicated ``Codec``, please migrate to them from the NBT/JSON/PacketBuffer write/read methods.
- Serialization of PokemonStores and Pokemon themselves now require registry access, which will break some sidemods. This sucked for us more than it will suck for you! If you have a world or player instance, you can get it from there.
- ``TeraTypes`` now implements ``Iterable``.
- PokemonBattle now starts on turn 0.
- All sounds related to evolving Pokémon have been moved to the "sounds/evolution" folder.
- Added `forcedAspects` to Pokémon to make it easier to easily add basic aspects to a Pokémon in a way that persists. We already know that everyone is going to overuse this.
- Made the `Pokemon.aspects` setter private. This could technically break side-mods but if you are affected by this then you were using it wrong! Use `Pokemon.forcedAspects` to fix it.
- Cobblemon's main logger is properly static now.
- PokemonEntity.enablePoseTypeRecalculation can be used to disable automatic pose type recalculation.
### Data Pack & Resource Pack Creators
- Added support for "shedders" similar to Shedinja's evolution logic.
- Fixed the placeholder `WingFlapIdle` animation so the wings are not rotating opposite to each other.
- 'player' type dialogue faces can now be explicitly stated so that NPC mods that use fake players can show in dialogue portraits.
- Added `isLeftSide` field for dialogue faces. This determines what side of dialogue box the portrait is on.
### Localization
- Updated translations for:
- Czech, German, Greek, Spanish, Mexican Spanish, French, Canadian French, Hungarian, Italian, Japanese, Korean, Dutch, Polish, Portuguese, Brazilian Portuguese, Russian, Ukrainian, Simplified Chinese, and Traditional Chinese
- Migrated translation project to Weblate at https://lang.cobblemon.com/projects/cobblemon/mod/
- Fixed the placeholder WingFlapIdle animation so the wings are not rotating opposite to each other.
## [1.5.2 (May 27th, 2024)](#1-5-2)
### Fixes
@@ -272,13 +254,13 @@
- Added parametric motion and rotation support to particle effects.
- Added entity_scale as a molang var for particles (likely only applicable to Pokemon)
- Added support for primary quirk animations using the following format:
`JSON
```JSON
{
"quirks": [
"q.bedrock_primary_quirk('<pokemon>', '<animation>', <minSeconds>, <maxSeconds>, <loopTimes>, '<excludedLabels>', q.curve('<waveFunction>'))"
]
}
`
```
- Added support for custom stashes, similar to Gimmighoul's coin and scrap stashes.
### Localization
@@ -584,7 +566,7 @@ These are our compromise for having both regular and Hisuian forms of these Pok
- Fixed mod incompatibility with [Just Enough Resources](https://modrinth.com/mod/just-enough-resources-jer).
- Fixed mod incompatibility with [Visual Overhaul](https://modrinth.com/mod/visual-overhaul).
- Fixed an issue that allowed Pokémon to spawn on rails.
- Fixed the data set on Pokémon when abilities are given via properties such as in `/spawnpokemon`.
- Fixed the data set on Pokémon when abilities are given via properties such as in ``/spawnpokemon``.
- Fixed Pokémon sometimes losing hidden abilities when evolving. This fix will only take effect for newly created Pokémon because of technical reasons that were explained to me but which I then forgot.
- Fixed capture messages not displaying in battle.
- Fixed the Illusion ability causing the wrong Pokémon to be targeted in battle.
@@ -597,17 +579,17 @@ These are our compromise for having both regular and Hisuian forms of these Pok
### Developer
- Significantly changed the way the properties in PokemonEntity work. This could break some plugins (not in a big way, but changes will be needed for some things).
- Rebuilt the scheduling API to more clearly force side choices and temporal frames of reference for tasks. The developers say that how it works is cool but I stopped listening.
- Added dialogue API and data registry. This is powerful, and there is a full example in the Cobblemon mod you can trigger using `/opendialogue <username> cobblemon:example`. The example JSON is inside the data folder of the mod.
- Added dialogue API and data registry. This is powerful, and there is a full example in the Cobblemon mod you can trigger using /opendialogue \<username\> cobblemon:example. The example JSON is inside the data folder of the mod.
- Opened up the ItemDropEntry class to allow for it to be extended.
- Added a new "advancement" evolution variant that takes an advancement identifier and succeeds if the player has the advancement.
- Made the spawning API capable of non-entity spawning. You can make it 'spawn' commands if you want. Go nuts.
- Made `PokemonProperties.parse` more interoperable with Java. Save yourself and stop using Java.
- Added the `HeldItemEvent`, this comes with 2 implementation `HeldItemEvent.Pre` and `HeldItemEvent.Post`.
- Corrections to the ability implementation have been made that make `Ability.forced` function as intended and never reroll an ability for a Pokémon, please check your implementations to ensure the corrected behavior is intended for your use case.
- `Pokemon.ability` no longer has a public setter. Please migrate to using `Pokemon#updateAbility`, this handles the ability coordinate storage for you when necessary.
- `Ability.forced`, `Ability.index` and `Ability.priority` have all had their setters internalized, there is no longer any need to manually adjust these migrate to the method mentioned above to handle that process for you.
- `AbilityChanger` has been added to API alongside some implementations, this is subject to change as the ability capsule and patch currently only expect the traditional behaviour in the Pokémon games of 1 or 2 regular abilities and 1 hidden ability.
- `Pokemon#rollAbility` has been added which re9rolls for a legal ability for the Pokémon.
- Made PokemonProperties.parse more interoperable with Java. Save yourself and stop using Java.
- Added the ``HeldItemEvent``, this comes with 2 implementation ``HeldItemEvent.Pre`` and ``HeldItemEvent.Post``.
- Corrections to the ability implementation have been made that make ``Ability.forced`` function as intended and never reroll an ability for a Pokémon, please check your implementations to ensure the corrected behavior is intended for your use case.
- ``Pokemon.ability`` no longer has a public setter. Please migrate to using ``Pokemon#updateAbility``, this handles the ability coordinate storage for you when necessary.
- ``Ability.forced``, ``Ability.index`` and ``Ability.priority`` have all had their setters internalized, there is no longer any need to manually adjust these migrate to the method mentioned above to handle that process for you.
- ``AbilityChanger`` has been added to API alongside some implementations, this is subject to change as the ability capsule and patch currently only expect the traditional behaviour in the Pokémon games of 1 or 2 regular abilities and 1 hidden ability.
- ``Pokemon#rollAbility`` has been added which rerolls for a legal ability for the Pokémon.
- `docs/cobblemon-tags` tags and spawn-presets can now be auto-generated from the current tags in the Cobblemon mod with the `generateSpawnPresetList.py` and `generateTagList.py` scripts.
- `public_spawns_to_json.py` script has been added to give the powerful spawn.json generator a simplistic UI.
- Updated Showdown with Gen 9 DLC.
@@ -616,14 +598,14 @@ These are our compromise for having both regular and Hisuian forms of these Pok
### Datapack & Resourcepack Creators
- The maximum amount of fossils that can fit in the Fossil Analyzer can be adjusted in the config.
- Custom fossils can be defined using a list of items and the resulting Pokémon. An example of `aerodactyl.json`:
`JSON
```JSON
{
"result": "aerodactyl",
"fossils": [
"cobblemon:old_amber_fossil"
]
}
`
```
- Fetus models can be defined in `bedrock/fossils`.
- Fetus textures can be defined in `textures/fossils`.
- Fuel for the Restoration Machine is registered inside the `natural_materials` folder. To register more fuels, create a JSON file containing an array of objects. Each object supports the following fields:
@@ -632,10 +614,10 @@ These are our compromise for having both regular and Hisuian forms of these Pok
- `returnItem`: Identifier of an item to return to the player after consumption.
- Added 3 new item tags: `ancient_poke_balls`, `fossils`, and `tumblestones`.
- Added a spawn rules system to modify general spawning behaviour, see the [wiki](https://wiki.cobblemon.com/index.php/Spawn_Rules) for more information.
- Added dialogue datapack folder and `/opendialogue` command.
- Added the item tag `cobblemon:held/leaves_leftovers` this can be used to flag apple-like items that can create leftovers when eaten.
- Added dialogue datapack folder and /opendialogue command.
- Added the item tag ``cobblemon:held/leaves_leftovers`` this can be used to flag apple-like items that can create leftovers when eaten.
- You can now add support for dynamic lighting implementations, Cobblemon ships with a default implementation for [LambDynamicLights](https://modrinth.com/mod/lambdynamiclights) and the [Dynamic Lights Reforged](https://www.curseforge.com/minecraft/mc-mods/dynamiclights-reforged), add the following to any species or form:
`JSON
```JSON
{
"lightingData": {
"lightLevel": 14,
@@ -644,10 +626,10 @@ These are our compromise for having both regular and Hisuian forms of these Pok
"_liquidGlowModeCommentRemoveMe": "Above supports LAND, UNDERWATER or BOTH"
}
}
`
- Following up on this change `light_source` was removed as a possible shoulder effect due to becoming unnecessary and never having had a default implementation.
- Added the item tag `cobblemon:ability_changers`, contains the `cobblemon:ability_capsule` & `cobblemon:ability_patch` by default.
- Added the item tag `cobblemon:held/is_friendship_booster`, allows items to give the Soothe Bell effect of a boost of 1.5x Friendship gained, contains `cobblemon:soothe_bell` by default.
```
- Following up on this change ``light_source`` was removed as a possible shoulder effect due to becoming unnecessary and never having had a default implementation.
- Added the item tag ``cobblemon:ability_changers``, contains the ``cobblemon:ability_capsule`` & ``cobblemon:ability_patch`` by default.
- Added the item tag ``cobblemon:held/is_friendship_booster``, allows items to give the Soothe Bell effect of a boost of 1.5x Friendship gained, contains ``cobblemon:soothe_bell`` by default.
## [1.4.1 (December 23rd, 2023)](#1-4-1)
@@ -663,7 +645,7 @@ These are our compromise for having both regular and Hisuian forms of these Pok
- Mooshtank can now be milked with a bowl for Mushroom Stew.
- Updated Showdown version to use generation 9 battle data.
- Added cries to Beldum, Metang and Metagross.
- Added a `/bedrockparticle` command to run Snowstorm-format particle effects.
- Added a /bedrockparticle command to run Snowstorm-format particle effects.
- Added data for Dipplin, Fezandipiti, Munkidori, Ogerpon, Okidogi, Poltchageist and Sinistcha.
- Added additional nickname trigger "Grumm" for Inkay's evolution.
@@ -1156,7 +1138,7 @@ Thank you so much to all of our community translators that bring the mod to the
- Fixed apricorn tree leaves looking gross on the Fast graphics mode.
- Fixed hoes not breaking apricorn tree leaves any faster.
- Fixed Shiftry's PC model position.
- Fixed the `/pc` command not playing the opening sound.
- Fixed the /pc command not playing the opening sound.
- Fixed different forms of Pokémon not being able to appear as different sizes.
- Fixed the Healing Machine soft locking you from using others when removed by non-players.
- Fixed animations being sped up when using the Replay Mod.
@@ -1173,11 +1155,11 @@ Thank you so much to all of our community translators that bring the mod to the
- Fixed battles soft-locking when consecutive Pokémon faint on switch-in.
- Fixed timing and color of battle window messages.
- Fixed players being able to trade, battle and let out their Pokémon while in spectator mode.
- Fixed Galarian Yamask not being able to evolve and by proxy the `damage_taken` evolution requirement.
- Fixed Bisharp not being able to evolve and by proxy the `defeat` evolution requirement.
- Fixed White-Striped Basculin not being able to evolve because of a broken `recoil` evolution requirement.
- Fixed Primeape, Qwilfish and Stantler not being able to evolve because of a broken `use_move` evolution requirement.
- Fixed Bramblin, Pawmo, and Rellor not being able to evolve because of a broken `blocks_traveled` evolution requirement.
- Fixed Galarian Yamask not being able to evolve and by proxy the ``damage_taken`` evolution requirement.
- Fixed Bisharp not being able to evolve and by proxy the ``defeat`` evolution requirement.
- Fixed White-Striped Basculin not being able to evolve because of a broken ``recoil`` evolution requirement.
- Fixed Primeape, Qwilfish and Stantler not being able to evolve because of a broken ``use_move`` evolution requirement.
- Fixed Bramblin, Pawmo, and Rellor not being able to evolve because of a broken ``blocks_traveled`` evolution requirement.
- Fixed displayName property in spawn files not doing what it's meant to do.
- Fixed Pokémon not sleeping in the wild like we wanted them to.
@@ -1192,11 +1174,11 @@ Thank you so much to all of our community translators that bring the mod to the
- Added MongoDB support for storing Pokémon and Player data. Must be enabled in config, requires MongoDB core and sync drivers (4.10.0+).
- CobblemonShowdown updated to version 10.
- Generation of a battle can be set in BattleFormat.
- Pokémon now have `teraType`, `dmaxLevel`, and `gmaxFactor` properties. Gimmicks can be used during battle by adding the respective identifiers to `keyItems` in PlayerData: `key_stone`, `z_ring`, `dynamax_band`, and `tera_orb`. Dynamax is only supported in Gen 8 battles. Mega Evolution and Z-Power require custom held items to be added (e.g. an item with the path `gengarite` will allow Gengar to Mega Evolve). Currently custom Z-Crystals and Mega Stones are not supported.
- Pokémon now have ``teraType``, ``dmaxLevel``, and ``gmaxFactor`` properties. Gimmicks can be used during battle by adding the respective identifiers to ``keyItems`` in PlayerData: ``key_stone``, ``z_ring``, ``dynamax_band``, and ``tera_orb``. Dynamax is only supported in Gen 8 battles. Mega Evolution and Z-Power require custom held items to be added (e.g. an item with the path ``gengarite`` will allow Gengar to Mega Evolve). Currently custom Z-Crystals and Mega Stones are not supported.
### Datapack & Resourcepack Creators
- All potion related shoulder effects have had their IDs changed. They now all share the same type being `potion_effect` and use the vanilla Potion data [parameters](https://minecraft.fandom.com/wiki/Potion#Item_data). For example, here is the converted Pidgey asset:
- `json
- ```json
{
"type": "potion_effect",
"effect": "minecraft:slow_falling",
@@ -1205,18 +1187,18 @@ Thank you so much to all of our community translators that bring the mod to the
"showParticles": false,
"showIcon": false
}
`
- Renamed the `walked_steps` evolution requirement to `blocks_traveled`.
```
- Renamed the ``walked_steps`` evolution requirement to ``blocks_traveled``.
- Added support for scale in animations.
- Added support for jump keyframes (i.e. pre and post keyframes)
- Added structure spawning conditions
- Added Advancement trigger for defeating Pokémon and collecting varieties of Pokémon.
- Added support for "isBattle" and "isTouchingWater" properties on resource pack Pokémon poses. This allows your custom Pokémon to be posed differently when in battle.
- Added support for "isVisible" on a transformed part on resource pack Pokémon poses. This allows your custom Pokémon to have bones disappear in specific poses, such as hiding Greninja's throwing star when not in a battle pose.
- Added support for battle music. Sounds can be added to the `battle.pvp.default` and `battle.pvw.default` sound events.
- Added support for battle music. Sounds can be added to the ``battle.pvp.default`` and ``battle.pvw.default`` sound events.
- Added 'enabled' optional property on model layers, allowing later variations to disable previously-defined layers. See [this issue](https://gitlab.com/cable-mc/cobblemon/-/issues/335) for how this looks.
- Cobblemon items can now all have their own tooltips via resourcepacks. To add a tooltip, add a lang entry like "item.cobblemon.{item_id}.tooltip". If you want to add multiple tooltip lines you can do so with "item.cobblemon.{item_id}.tooltip_1" and upwards.
- Item interaction evolutions and held item requirements now support NBT by creating an object JSON containing the key `item` for what used to be the existing condition support and a `nbt` key for the NBT format, this is the string [format](https://minecraft.fandom.com/wiki/NBT_format) expected in commands. Existing data does not need to be updated.
- Item interaction evolutions and held item requirements now support NBT by creating an object JSON containing the key ``item`` for what used to be the existing condition support and a ``nbt`` key for the NBT format, this is the string [format](https://minecraft.fandom.com/wiki/NBT_format) expected in commands. Existing data does not need to be updated.
- Fixed faint animations not working properly in add-ons.
- Fixed non-existent species in spawn pool files causing random species to spawn.
@@ -1234,7 +1216,7 @@ Thank you so much to all of our community translators that bring the mod to the
- Added animations to Staryu line and Porygon line.
- Added faint animations to Klink line.
- Add lava surface spawn preset.
- Added an `any` evolution requirement allowing you to define `possibilities` of other evolution requirements, for example, this allows you to create an evolution that requires the Pokémon to be shiny or a female.
- Added an ``any`` evolution requirement allowing you to define ``possibilities`` of other evolution requirements, for example, this allows you to create an evolution that requires the Pokémon to be shiny or a female.
- Added the `/spawnpokemonfrompool [amount]` or `/forcespawn [amount]` command to spawn Pokémon(s) in the surrounding area using the natural spawn rates/pool of that area, this will be a cheat command in the Minecraft permission system or use the permission `cobblemon.command.spawnpokemon` if a permission mod is present. On a successful execution of the command, the amount of Pokémon spawned will be the output.
- Added the `/pokebox` and `/pokeboxall` commands to move Pokémon(s) to the PC from a Player's party, this will be a cheat command in the Minecraft permission system or use the permission `cobblemon.command.pokebox` if a permission mod is present. On a successful execution of the command the output will be the number of pokemon moved to the Player's PC.
- Added the `/pc` command which opens up the PC UI the same way interacting with the block would, this will be a cheat command in the Minecraft permission system or use the permission `cobblemon.command.pc` if a permission mod is present.
@@ -1359,7 +1341,7 @@ Thank you so much to all of our community translators that bring the mod to the
- Added support for custom Pokémon to implement 'quirks' such as blinks.
- Added sound effect for harvesting Apricorns.
- Added icon to summary and PC interfaces to indicated if a Pokémon is shiny.
- Added the `/spawnpokemonat <pos> <properties>` command, the `pos` argument uses the same syntax as the Minecraft [summon](https://minecraft.fandom.com/wiki/Commands/summon) command.
- Added the ``/spawnpokemonat <pos> <properties>`` command, the ``pos`` argument uses the same syntax as the Minecraft [summon](https://minecraft.fandom.com/wiki/Commands/summon) command.
- Added the `/giveallpokemon` command which is definitely safe and not insane.
- Added compatibility with Carry On by preventing the mod being able to interact with Cobblemon entities, the mod caused too many gameplay detrimental features to stay enabled.
- Added healing to your party when you sleep in a bed.
@@ -1368,18 +1350,18 @@ Thank you so much to all of our community translators that bring the mod to the
- Added a config option for disallowing players from damaging Pokémon by hand.
- Apricorn seeds can now be used with the [Composter](https://minecraft.fandom.com/wiki/Composter), these have the layer increase chance of 65% like Apricorns and various Minecraft crops.
- Added support for Pokémon species data appending making it so datapack developers no longer need to overwrite files.
- Added an implementation of every [catch rate](https://bulbapedia.bulbagarden.net/wiki/Catch_rate) from generation 1 to 9, these can be used by changing the `captureCalculator` config value:
- `generation_1` Sets the calculator to the generation 1 implementation.
- `generation_2` Sets the calculator to the generation 2 implementation.
- `generation_2_fixed` Sets the calculator to the generation 2 implementation with the status multiplier bug fixed.
- `generation_3_4` Sets the calculator to the generation 3 and 4 implementation.
- `generation_5` Sets the calculator to the generation 5 implementation.
- `generation_6` Sets the calculator to the generation 6 implementation.
- `generation_7` Sets the calculator to the generation 7 implementation.
- `generation_8` Sets the calculator to the generation 8 implementation.
- `generation_9` Sets the calculator to the generation 9 implementation.
- `cobblemon` Sets the calculator to the custom Cobblemon implementation. This is the default value.
- `debug` Sets the calculator to the debug/cheat implementation, every attempt will be a successful critical capture.
- Added an implementation of every [catch rate](https://bulbapedia.bulbagarden.net/wiki/Catch_rate) from generation 1 to 9, these can be used by changing the ``captureCalculator`` config value:
- ``generation_1`` Sets the calculator to the generation 1 implementation.
- ``generation_2`` Sets the calculator to the generation 2 implementation.
- ``generation_2_fixed`` Sets the calculator to the generation 2 implementation with the status multiplier bug fixed.
- ``generation_3_4`` Sets the calculator to the generation 3 and 4 implementation.
- ``generation_5`` Sets the calculator to the generation 5 implementation.
- ``generation_6`` Sets the calculator to the generation 6 implementation.
- ``generation_7`` Sets the calculator to the generation 7 implementation.
- ``generation_8`` Sets the calculator to the generation 8 implementation.
- ``generation_9`` Sets the calculator to the generation 9 implementation.
- ``cobblemon`` Sets the calculator to the custom Cobblemon implementation. This is the default value.
- ``debug`` Sets the calculator to the debug/cheat implementation, every attempt will be a successful critical capture.
### Changes
- Pokémon now save to the world by default, meaning the same Pokémon will remain in the world and not disappear after you log out and log back in. They still despawn over time though.
@@ -1392,9 +1374,9 @@ Thank you so much to all of our community translators that bring the mod to the
- Made it so that particles are not shown whenever you have a shoulder Pokémon that gives potion effects.
- Changed hitbox and size definitions for Decidueye, Blastoise, and Magmortar
- Apricorns can now be harvested with Axes, the speed will scale with enchantments and tool material, only dropping the Apricorn if fully grown, these will still destroy the Apricorn so the manual harvest still is recommended unless you're just keen on destroying trees.
- Apricorns are now a part of the vanilla tag `minecraft:blocks/mineable/axe`.
- Apricorns are now a part of the vanilla tag ``minecraft:blocks/mineable/axe``.
- Apricorns are now compatible with any mod that breaks a whole tree at once.
- Apricorns no longer have a config value for the seed drop chance these are now a part of their loot table which can be found in `cobblemon/loot_tables/blocks/<color>_apricorn.json`.
- Apricorns no longer have a config value for the seed drop chance these are now a part of their loot table which can be found in ``cobblemon/loot_tables/blocks/<color>_apricorn.json``.
- Advancements were redone to be slightly more interesting, with improved names, descriptions, and sorting.
- Updated models and textures for Tentacool line, Gengar, Slowpoke line, Tyrogue line, Doduo line, Dratini, Dragonair, Quagsire, and Piplup line. There were probably others, the team lost track.
- Improved sending out Pokémon at the start of battle so that they are positioned in a less annoying way.
@@ -1432,17 +1414,17 @@ Thank you so much to all of our community translators that bring the mod to the
- Fixed positioning of Poké Balls when rendered in Healing Machines.
- Fixed a desync issue on servers where all Pokémon seemed like they were special forms when they weren't.
- Fixed an incompatibility with [Exordium](https://www.curseforge.com/minecraft/mc-mods/exordium).
- Fixed datapack Pokémon language key generation. A Pokémon under the namespace `example` named `Pogemon` will now correctly look for the lang key `example.species.pogemon.name`.
- Fixed datapack Pokémon language key generation. A Pokémon under the namespace ``example`` named ``Pogemon`` will now correctly look for the lang key ``example.species.pogemon.name``.
- Fixed client not receiving messages for the different "stages" for the move Bide.
- Fixed the Medium Slow and Medium Fast experience group IDs, they're now `medium_slow` and `medium_fast`. Any custom datapack Pokémon will require an update.
- Fixed the Medium Slow and Medium Fast experience group IDs, they're now ```medium_slow``` and ``medium_fast``. Any custom datapack Pokémon will require an update.
- Fixed Pokémon friendship being capped to the maximum level config value instead of the friendship one when loading Pokémon data.
- Fixed Poké Balls taking forever to capture Pokémon if you are underwater or up in the air where it takes a long time to hit the ground.
- Fixed Pokémon being unable to spawn on blocks such as snow layers.
- Fixed Pokémon spawning inside of trees.
- Fixed Pokémon experience not loading after a restart and instead going back to the minimal amount for the current level.
- Fixed being able to use `/healpokemon` in battle.
- Fixed being able to use ``/healpokemon`` in battle.
- Fixed being able to select fainted party members in the switch menu causing the battle to lock.
- Fixed `/spawnpokemon` command not supporting any command source other than players.
- Fixed ``/spawnpokemon`` command not supporting any command source other than players.
- Fixed issues with Charizard's sleep pose.
- Fixed players being able to use multiple healer machines at once.
- Fixed Pokémon layers not rendering when a Pokémon is on your shoulder.
@@ -1477,7 +1459,7 @@ Thank you so much to all of our community translators that bring the mod to the
- Added models for Natu and Xatu, Murkrow and Honchkrow, Wailmer and Wailord.
- Added new PC interface and it is beautiful.
- Reworked the battle system so that battles load faster, cause fewer bugs, and can run on shared server hosts. This is a very big change that also reduced the size of the mod by 50mb!
- Added full resource pack and data pack customization of models, textures, animations, spawning, and spawn file presets to make custom Pokémon species and variations very easy to create. You can find the guide for creating custom Pokémon on [our website](https://cobblemon.com/guides/custompokemon.html)!
- Added full resource pack / data pack customization of models, textures, animations, spawning, and spawn file presets to make custom Pokémon species and variations very easy to create. You can find the guide for creating custom Pokémon on [our website](https://cobblemon.com/guides/custompokemon.html)!
- Added water surface spawning for Pokémon like Lapras.
- Added emissive texture support to Pokémon render layers.
- Added compatibility for Mod Menu ([CurseForge](https://www.curseforge.com/minecraft/mc-mods/modmenu), [Modrinth](https://modrinth.com/mod/modmenu)).
@@ -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;
import com.cobblemon.mod.common.entity.npc.NPCEntity;
import java.util.Collection;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.sensing.Sensor;
import net.minecraft.world.entity.ai.sensing.SensorType;
public class GenericsCheatClass {
// For some reason, doing this in the NPCEntity class in Kotlin gives confusing generic errors I could not resolve.
public static Brain.Provider<NPCEntity> createNPCBrain(Collection<MemoryModuleType<?>> memories, Collection<SensorType<? extends Sensor<? super NPCEntity>>> sensors) {
return Brain.provider(memories, sensors);
}
}
@@ -17,4 +17,6 @@ public interface SoundManagerDuck {
void pauseSounds(@Nullable ResourceLocation id, @Nullable SoundSource category);
void resumeSounds(@Nullable ResourceLocation id, @Nullable SoundSource category);
void stopSounds(@Nullable ResourceLocation id, @Nullable SoundSource category);
}
@@ -10,12 +10,18 @@ package com.cobblemon.mod.common.duck;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundSource;
public interface SoundEngineDuck {
public interface SoundSystemDuck {
void pauseSounds(@Nullable ResourceLocation id, @Nullable SoundSource category);
void resumeSounds(@Nullable ResourceLocation id, @Nullable SoundSource category);
void stopSounds(@Nullable ResourceLocation id, @Nullable SoundSource category);
Collection<SoundInstance> getSounds(@Nullable SoundSource category);
}
@@ -1,65 +0,0 @@
/*
* 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.mixin;
import static com.cobblemon.mod.common.datafixer.CobblemonSchemas.VERSION_KEY;
import com.cobblemon.mod.common.datafixer.CobblemonSchemas;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.mojang.serialization.MapCodec;
import java.util.Optional;
import java.util.function.Supplier;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.IntTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.util.datafix.DataFixTypes;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.storage.ChunkStorage;
import net.minecraft.world.level.storage.DimensionDataStorage;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ChunkStorage.class)
public class ChunkStorageMixin {
//Modifies the NBT returned by the vanilla upgradeChunkTag to also incorporate cobblemon fixup stuff
@ModifyExpressionValue(
method = "upgradeChunkTag(Lnet/minecraft/resources/ResourceKey;Ljava/util/function/Supplier;Lnet/minecraft/nbt/CompoundTag;Ljava/util/Optional;)Lnet/minecraft/nbt/CompoundTag;",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/util/datafix/DataFixTypes;updateToCurrentVersion(Lcom/mojang/datafixers/DataFixer;Lnet/minecraft/nbt/CompoundTag;I)Lnet/minecraft/nbt/CompoundTag;"
)
)
public CompoundTag cobblemon$doChunkFix(
CompoundTag vanillaFixed
) {
int curVersion = vanillaFixed.contains(VERSION_KEY) ? vanillaFixed.getInt(VERSION_KEY) : 0;
return DataFixTypes.CHUNK.update(CobblemonSchemas.getDATA_FIXER(), vanillaFixed, curVersion, CobblemonSchemas.DATA_VERSION);
}
@Inject(
method = "upgradeChunkTag(Lnet/minecraft/resources/ResourceKey;Ljava/util/function/Supplier;Lnet/minecraft/nbt/CompoundTag;Ljava/util/Optional;)Lnet/minecraft/nbt/CompoundTag;",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/nbt/NbtUtils;addCurrentDataVersion(Lnet/minecraft/nbt/CompoundTag;)Lnet/minecraft/nbt/CompoundTag;"
)
)
public void cobblemon$addChunkVersion(
ResourceKey<Level> resourceKey,
Supplier<DimensionDataStorage> supplier,
CompoundTag compoundTag,
Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> optional,
CallbackInfoReturnable<CompoundTag> cir
) {
compoundTag.put(VERSION_KEY, IntTag.valueOf(CobblemonSchemas.DATA_VERSION));
}
}
@@ -19,7 +19,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(CrashReport.class)
public final class CrashReportMixin {
@Inject(method = "getDetails(Ljava/lang/StringBuilder;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/SystemReport;appendToCrashReportString(Ljava/lang/StringBuilder;)V"))
@Inject(method = "getDetails", at = @At(value = "INVOKE", target = "Lnet/minecraft/SystemReport;appendToCrashReportString(Ljava/lang/StringBuilder;)V"))
public void cobblemon$printCobblemonDetails(StringBuilder builder, CallbackInfo callback) {
CrashReportCategory cobblemon = new CrashReportCategory("Cobblemon");
cobblemon.setDetail("Version", CobblemonBuildDetails.VERSION);
@@ -21,13 +21,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(DecoratedPotPatterns.class)
public abstract class DecoratedPotPatternsMixin {
@Inject(method = "getPatternFromItem", at=@At("HEAD"), cancellable = true)
private static void cobblemon$getCobblemonSherdTexture(
Item sherd,
CallbackInfoReturnable<ResourceKey<DecoratedPotPattern>> cir
) {
private static void cobblemon$getCobblemonSherdTexture(Item sherd,
CallbackInfoReturnable<ResourceKey<DecoratedPotPattern>> cir) {
if (CobblemonSherds.INSTANCE.getSherdToPattern().containsKey(sherd)) {
cir.setReturnValue(CobblemonSherds.INSTANCE.getSherdToPattern().get(sherd));
cir.cancel();
return;
}
}
}
@@ -1,40 +0,0 @@
/*
* 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.mixin;
import static com.cobblemon.mod.common.datafixer.CobblemonSchemas.VERSION_KEY;
import com.cobblemon.mod.common.datafixer.CobblemonSchemas;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.IntTag;
import net.minecraft.util.datafix.DataFixTypes;
import net.minecraft.world.level.chunk.storage.EntityStorage;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(EntityStorage.class)
public class EntityStorageMixin {
//Modifies the NBT returned by the vanilla upgradeChunkTag to also incorporate cobblemon fixup stuff
@ModifyExpressionValue(
method = "method_31731(Lnet/minecraft/world/level/ChunkPos;Ljava/util/Optional;)Lnet/minecraft/world/level/entity/ChunkEntities;",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/chunk/storage/SimpleRegionStorage;upgradeChunkTag(Lnet/minecraft/nbt/CompoundTag;I)Lnet/minecraft/nbt/CompoundTag;"
)
)
public CompoundTag cobblemon$doEntityFix(
CompoundTag vanillaFixed
) {
int curVersion = vanillaFixed.contains(VERSION_KEY) ? vanillaFixed.getInt(VERSION_KEY) : 0;
CompoundTag newTag = DataFixTypes.ENTITY_CHUNK.update(CobblemonSchemas.getDATA_FIXER(), vanillaFixed, curVersion, CobblemonSchemas.DATA_VERSION);
newTag.put(VERSION_KEY, IntTag.valueOf(CobblemonSchemas.DATA_VERSION));
return newTag;
}
}
@@ -1,31 +0,0 @@
/*
* 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.mixin;
import com.cobblemon.mod.common.item.PokedexItem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.renderer.ItemInHandRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ItemInHandRenderer.class)
public class ItemInHandRendererMixin {
@Inject(method = "renderArmWithItem", at = @At(value = "HEAD"), cancellable = true)
private void cobblemon$renderArmWithItem(AbstractClientPlayer abstractClientPlayer, float f, float g, InteractionHand interactionHand, float h, ItemStack itemStack, float i, PoseStack poseStack, MultiBufferSource multiBufferSource, int j, CallbackInfo ci) {
if (abstractClientPlayer.isUsingItem() && abstractClientPlayer.getUseItem().getItem() instanceof PokedexItem) {
ci.cancel();
}
}
}
@@ -8,30 +8,21 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.ModAPI;
import com.cobblemon.mod.common.client.CobblemonBakingOverrides;
import com.cobblemon.mod.common.item.PokeBallItem;
import com.cobblemon.mod.common.item.PokedexItem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.ItemModelShaper;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
/*
I think it would be nice to maybe use the existing mixin we have for custom item rendering [BuiltinModelItemRendererMixin]
@@ -39,52 +30,25 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
*/
@Mixin(ItemRenderer.class)
public abstract class ItemRendererMixin {
@Unique private final String MODEL_PATH = Cobblemon.implementation.getModAPI() == ModAPI.FABRIC ? "fabric_resource" : "standalone";
@Shadow @Final private ItemModelShaper itemModelShaper;
@Shadow public abstract void render(ItemStack stack, ItemDisplayContext renderMode, boolean leftHanded, PoseStack matrices, MultiBufferSource vertexConsumers, int light, int overlay, BakedModel model);
@Inject(
method = "render(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemDisplayContext;ZLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;IILnet/minecraft/client/resources/model/BakedModel;)V",
method = "Lnet/minecraft/client/renderer/entity/ItemRenderer;render(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemDisplayContext;ZLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;IILnet/minecraft/client/resources/model/BakedModel;)V",
at = @At("HEAD"),
cancellable = true
)
private void cobblemon$overrideItemModel(ItemStack stack, ItemDisplayContext renderMode, boolean leftHanded, PoseStack matrices, MultiBufferSource multiBufferSource, int light, int overlay, BakedModel model, CallbackInfo ci) {
boolean shouldBe2d = renderMode == ItemDisplayContext.GUI || renderMode == ItemDisplayContext.FIXED;
if (!shouldBe2d && stack.getItem() instanceof PokedexItem) {
private void cobblemon$overridePokedexModel(ItemStack stack, ItemDisplayContext renderMode, boolean leftHanded, PoseStack matrices, MultiBufferSource multiBufferSource, int i, int j, BakedModel model, CallbackInfo ci) {
boolean shouldOverride = renderMode != ItemDisplayContext.GUI && renderMode != ItemDisplayContext.FIXED;
if (shouldOverride && stack.getItem() instanceof PokedexItem) {
BakedModel replacementModel = CobblemonBakingOverrides.INSTANCE.getPokedexOverride(((PokedexItem) stack.getItem()).getType()).getModel();
if (!cobblemon$isSameModel(model, replacementModel)) {
if (!model.equals(replacementModel)) {
ci.cancel();
render(stack, renderMode, leftHanded, matrices, multiBufferSource, light, overlay, replacementModel);
}
} else if (shouldBe2d && stack.getItem() instanceof PokeBallItem pokeBallItem) {
BakedModel replacementModel = this.itemModelShaper.getModelManager().getModel(new ModelResourceLocation(pokeBallItem.getPokeBall().getModel2d(), "inventory"));
if (!cobblemon$isSameModel(model, replacementModel)) {
ci.cancel();
render(stack, renderMode, leftHanded, matrices, multiBufferSource, light, overlay, replacementModel);
render(stack, renderMode, leftHanded, matrices, multiBufferSource, i, j, replacementModel);
}
}
}
@Inject(method = "getModel", at = @At("HEAD"), cancellable = true)
private void cobblemon$bakePokeballModel(ItemStack stack, Level world, LivingEntity entity, int seed, CallbackInfoReturnable<BakedModel> cir) {
if (stack.getItem() instanceof PokeBallItem pokeBallItem) {
BakedModel model = this.itemModelShaper.getModelManager().getModel(new ModelResourceLocation(pokeBallItem.getPokeBall().getModel3d(), MODEL_PATH));
ClientLevel clientWorld = world instanceof ClientLevel ? (ClientLevel) world : null;
BakedModel overriddenModel = model.getOverrides().resolve(model, stack, clientWorld, entity, seed);
cir.setReturnValue(overriddenModel == null ? this.itemModelShaper.getModelManager().getMissingModel() : overriddenModel);
}
}
// this method aims to cover an issue with modernfixes and it's "dynamic resource" causing the usual .equals check to always fail, resulting in a StackOverflow
// this falls back to comparing the model contents (e.g. location of the texture, width and height)
@Unique
private static boolean cobblemon$isSameModel(BakedModel original, BakedModel replacement) {
return original.equals(replacement) ||
(original.getParticleIcon().contents().name().equals(replacement.getParticleIcon().contents().name()) &&
original.getParticleIcon().contents().width() == replacement.getParticleIcon().contents().width() &&
original.getParticleIcon().contents().height() == replacement.getParticleIcon().contents().height());
}
}
@@ -1,50 +0,0 @@
/*
* 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.mixin;
import com.cobblemon.mod.common.util.DataKeys;
import com.mojang.serialization.Dynamic;
import net.minecraft.util.datafix.fixes.ItemStackComponentizationFix;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.stream.Stream;
@Mixin(ItemStackComponentizationFix.class)
public class ItemStackComponentizationFixMixin
{
@Inject(method = "fixItemStack", at = @At("TAIL"))
private static void fixItemStack(ItemStackComponentizationFix.ItemStackData itemStackData, Dynamic<?> dynamic, CallbackInfo ci) {
cobblemon$attemptPokemonModelDataFix(itemStackData, dynamic);
}
@Unique
private static void cobblemon$attemptPokemonModelDataFix(ItemStackComponentizationFix.ItemStackData itemStackData, Dynamic<?> dynamic)
{
var species = itemStackData.removeTag(DataKeys.POKEMON_ITEM_SPECIES).asString();
var aspects = itemStackData.removeTag(DataKeys.POKEMON_ITEM_ASPECTS).asList(dynamic1 -> dynamic1);
var redTint = itemStackData.removeTag(DataKeys.POKEMON_ITEM_TINT_RED).asNumber();
var greenTint = itemStackData.removeTag(DataKeys.POKEMON_ITEM_TINT_GREEN).asNumber();
var blueTint = itemStackData.removeTag(DataKeys.POKEMON_ITEM_TINT_BLUE).asNumber();
var alphaTint = itemStackData.removeTag(DataKeys.POKEMON_ITEM_TINT_ALPHA).asNumber();
if(species.hasResultOrPartial() && aspects != null) {
var pokeComponent = dynamic.emptyMap().set("species", dynamic.createString(species.getOrThrow()));
pokeComponent = pokeComponent.set("aspects", dynamic.createList(aspects.stream()));
if(redTint.hasResultOrPartial() || blueTint.hasResultOrPartial() || greenTint.hasResultOrPartial() || alphaTint.hasResultOrPartial()) {
pokeComponent = pokeComponent.set("tint", dynamic.createList(Stream.of(redTint, greenTint, blueTint, alphaTint)
.map(x -> x.mapOrElse(Number::floatValue, error -> 0F))
.map(dynamic::createFloat)));
}
itemStackData.setComponent("cobblemon:pokemon_item", pokeComponent);
}
}
}
@@ -8,7 +8,7 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.client.sound.BattleMusicController;
import com.cobblemon.mod.common.client.sound.battle.BattleMusicController;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.client.sounds.MusicManager;
@@ -21,7 +21,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(MusicManager.class)
public class MusicManagerMixin {
public class MusicTrackerMixin {
@Shadow @Final
private Minecraft minecraft;
@@ -43,16 +43,16 @@ public class PartyScrollMixin {
)
public void cobblemon$scrollParty(long window, double horizontal, double vertical, CallbackInfo ci) {
if (PartySendBinding.INSTANCE.getWasDown()) {
int i = (int) accumulatedScrollY;
int i = (int)accumulatedScrollY;
if (i > 0) {
accumulatedScrollY -= i;
CobblemonClient.INSTANCE.getStorage().shiftSelected(false);
while (i-- > 0) CobblemonClient.INSTANCE.getStorage().shiftSelected(false);
ci.cancel();
accumulatedScrollY = 0;
PartySendBinding.INSTANCE.actioned();
} else if (i < 0) {
accumulatedScrollY -= i;
CobblemonClient.INSTANCE.getStorage().shiftSelected(true);
while (i++ < 0) CobblemonClient.INSTANCE.getStorage().shiftSelected(true);
ci.cancel();
accumulatedScrollY = 0;
PartySendBinding.INSTANCE.actioned();
}
}
@@ -119,12 +119,14 @@ public abstract class PlayerMixin extends LivingEntity {
private void recallPokemon(UUID uuid) {
// We need to do this cause the Entity doesn't store a reference to its storage
final PartyStore party = Cobblemon.INSTANCE.getStorage().getParty(this.getUUID(), this.registryAccess());
for (Pokemon pokemon : party) {
if (pokemon.getUuid().equals(uuid)) {
pokemon.recall();
try {
final PartyStore party = Cobblemon.INSTANCE.getStorage().getParty(this.uuid);
for (Pokemon pokemon : party) {
if (pokemon.getUuid().equals(uuid)) {
pokemon.recall();
}
}
}
} catch (NoPokemonStoreException ignored) {}
}
private boolean isShoulderPokemon(CompoundTag nbt) {
@@ -8,12 +8,9 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.client.sound.BlockEntitySoundTracker;
import com.cobblemon.mod.common.client.sound.EntitySoundTracker;
import com.cobblemon.mod.common.client.sound.BattleMusicController;
import com.cobblemon.mod.common.client.sound.SoundTracker;
import com.cobblemon.mod.common.client.sound.battle.BattleMusicController;
import com.cobblemon.mod.common.duck.SoundManagerDuck;
import com.cobblemon.mod.common.duck.SoundEngineDuck;
import com.cobblemon.mod.common.duck.SoundSystemDuck;
import net.minecraft.client.resources.sounds.BiomeAmbientSoundsHandler;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.client.resources.sounds.UnderwaterAmbientSoundInstances;
@@ -58,13 +55,19 @@ public abstract class SoundManagerMixin implements SoundManagerDuck {
/** Pauses the queried SoundInstance(s). If id is null, will pause all sounds belonging to the SoundCategory. */
@Override
public void pauseSounds(@Nullable ResourceLocation id, @Nullable SoundSource category) {
((SoundEngineDuck)soundEngine).pauseSounds(id, category);
((SoundSystemDuck)soundEngine).pauseSounds(id, category);
}
/** Resumes the queried SoundInstance(s). If id is null, will resume all sounds belonging to the SoundCategory. */
@Override
public void resumeSounds(@Nullable ResourceLocation id, @Nullable SoundSource category) {
((SoundEngineDuck)soundEngine).resumeSounds(id, category);
((SoundSystemDuck)soundEngine).resumeSounds(id, category);
}
/** Stops the queried SoundInstance(s). If id is null, will stop all sounds belonging to the SoundCategory. */
@Override
public void stopSounds(@Nullable ResourceLocation id, @Nullable SoundSource category) {
((SoundSystemDuck)soundEngine).stopSounds(id, category);
}
/** Blocks filtered sounds from being played while a BattleMusicInstance is in progress. */
@@ -88,10 +91,4 @@ public abstract class SoundManagerMixin implements SoundManagerDuck {
public void playEnd(SoundInstance sound, int delay, CallbackInfo cb) {
if (ambientLoopCondition(sound)) this.pauseSounds(sound.getLocation(), SoundSource.AMBIENT);
}
/** Clears tracked sounds on the client when disconnected. */
@Inject(method = "stop()V", at = @At("TAIL"))
public void stop(CallbackInfo cb) {
SoundTracker.Companion.clear();
}
}
@@ -8,8 +8,8 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.client.sound.BattleMusicController;
import com.cobblemon.mod.common.duck.SoundEngineDuck;
import com.cobblemon.mod.common.client.sound.battle.BattleMusicController;
import com.cobblemon.mod.common.duck.SoundSystemDuck;
import com.google.common.collect.Multimap;
import com.mojang.blaze3d.audio.Channel;
import net.minecraft.client.resources.sounds.SoundInstance;
@@ -28,7 +28,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
@Mixin(SoundEngine.class)
public abstract class SoundEngineMixin implements SoundEngineDuck {
public abstract class SoundSystemMixin implements SoundSystemDuck {
@Shadow
private boolean loaded;
@@ -91,7 +91,7 @@ public abstract class SoundEngineMixin implements SoundEngineDuck {
/** Allows stopping all SoundInstances that belong to a queried category. */
@Inject(method = "stop(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/sounds/SoundSource;)V", at = @At("HEAD"), cancellable = true)
public void stopSounds(@Nullable ResourceLocation id, @Nullable SoundSource category, CallbackInfo cb) {
public void stopSoustopnds(@Nullable ResourceLocation id, @Nullable SoundSource category, CallbackInfo cb) {
if (id == null && category != null) {
this.instanceBySource.get(category).forEach(this::stop);
cb.cancel();
@@ -9,26 +9,22 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.world.CobblemonStructureIDs;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.pools.*;
import net.minecraft.world.level.levelgen.structure.pools.alias.PoolAliasLookup;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.pools.JigsawPlacement;
import net.minecraft.world.level.levelgen.structure.pools.LegacySinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.apache.commons.lang3.mutable.MutableObject;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.util.*;
@@ -43,11 +39,11 @@ public abstract class StructurePoolGeneratorMixin {
String pokecenter = "pokecenter";
String berry = "berry_farm";
structureGroups.put(CobblemonStructureIDs.VILLAGE_PLAINS_POKECENTER, Set.of(pokecenter));
structureGroups.put(CobblemonStructureIDs.VILLAGE_DESERT_POKECENTER, Set.of(pokecenter));
structureGroups.put(CobblemonStructureIDs.VILLAGE_SAVANNA_POKECENTER, Set.of(pokecenter));
structureGroups.put(CobblemonStructureIDs.VILLAGE_SNOWY_POKECENTER, Set.of(pokecenter));
structureGroups.put(CobblemonStructureIDs.VILLAGE_TAIGA_POKECENTER, Set.of(pokecenter));
structureGroups.put(CobblemonStructureIDs.PLAINS_POKECENTER, Set.of(pokecenter));
structureGroups.put(CobblemonStructureIDs.DESERT_POKECENTER, Set.of(pokecenter));
structureGroups.put(CobblemonStructureIDs.SAVANNA_POKECENTER, Set.of(pokecenter));
structureGroups.put(CobblemonStructureIDs.SNOWY_POKECENTER, Set.of(pokecenter));
structureGroups.put(CobblemonStructureIDs.TAIGA_POKECENTER, Set.of(pokecenter));
structureGroups.put(CobblemonStructureIDs.SAVANNA_BERRY_SMALL, Set.of(berry));
structureGroups.put(CobblemonStructureIDs.SAVANNA_BERRY_LARGE, Set.of(berry));
@@ -121,25 +117,53 @@ public abstract class StructurePoolGeneratorMixin {
return reducedList.iterator();
}
@Inject(method = "tryPlacingChildren", at = @At(value = "NEW", target = "net/minecraft/world/level/levelgen/structure/PoolElementStructurePiece", ordinal = 0), locals = LocalCapture.CAPTURE_FAILHARD)
private void afterTryPlacingChildren(
PoolElementStructurePiece poolElementStructurePiece,
MutableObject<VoxelShape> mutableObject,
int i,
boolean bl,
LevelHeightAccessor levelHeightAccessor,
RandomState randomState,
PoolAliasLookup poolAliasLookup,
LiquidSettings liquidSettings,
CallbackInfo ci,
@Local(ordinal = 1) StructurePoolElement structurePoolElement
) {
ResourceLocation structureLocationKey = getCobblemonOnlyLocation(structurePoolElement);
@ModifyVariable(method = "tryPlacingChildren", at = @At("HEAD"), ordinal = 0, argsOnly = true)
private PoolElementStructurePiece injected(PoolElementStructurePiece poolStructurePiece) {
ResourceLocation structureLocationKey = getCobblemonOnlyLocation(poolStructurePiece.getElement());
if (structureLocationKey != null) {
incrementStructureCount(structureLocationKey);
}
return poolStructurePiece;
}
// This doesn't SEEM necessary? It appears to work without.
// @Inject(method = "generatePiece", at = @At("HEAD"))
// private void beforeGeneratePiece(PoolStructurePiece piece, MutableObject<VoxelShape> pieceShape, int minY, boolean modifyBoundingBox, HeightLimitView world, NoiseConfig noiseConfig, CallbackInfo ci) {
//// Identifier structureLocationKey = getCobblemonOnlyLocation(piece.getPoolElement());
////
//// if (structureLocationKey != null) {
//// Integer currentlyGenerated = generatedStructureCounts.get(structureLocationKey);
//// if (currentlyGenerated == null) currentlyGenerated = 0;
//// generatedStructureCounts.put(structureLocationKey, currentlyGenerated + 1);
//// }
////
//// List<StructurePoolBasedGenerator.ShapedPoolStructurePiece> reducedStructurePiecesList = structurePieces.stream().toList();
////
//// for (Identifier maxStructureLocationKey : structureMaxes.keySet()) {
//// Integer maxAllowed = structureMaxes.get(maxStructureLocationKey);
////
//// Integer currentlyGenerated = generatedStructureCounts.get(maxStructureLocationKey);
//// if (currentlyGenerated == null) currentlyGenerated = 0;
//// if (currentlyGenerated < maxAllowed) {
//// continue;
//// }
////
//// //Already have max so need to remove
//// reducedStructurePiecesList = reducedStructurePiecesList.stream()
//// .filter(shapedStructurePiece -> {
//// Identifier locationKey = getCobblemonOnlyLocation(shapedStructurePiece.piece.getPoolElement());
//// if (locationKey == null) {
//// return true;
//// }
////
//// return !locationKey.equals(maxStructureLocationKey);
//// })
//// .collect(Collectors.toList());
//// }
////
//// structurePieces = new ArrayDeque<>(reducedStructurePiecesList);
// }
private static ResourceLocation getCobblemonOnlyLocation(StructurePoolElement structurePoolElement) {
ResourceLocation location = getLocationIfAvailable(structurePoolElement);
if (location == null) return null;
@@ -162,4 +186,26 @@ public abstract class StructurePoolGeneratorMixin {
return null;
}
}
// private List<StructurePoolElement> removeInstanceOfLocationKeyFrom(List<StructurePoolElement> structureList, Integer allowedNumberOfInstances, Identifier locationKey) {
// List<StructurePoolElement> reducedList = new ArrayList<>();
// int instancesFound = 0;
//
// for (StructurePoolElement structurePoolElement: structureList) {
// Identifier structureKey = getCobblemonOnlyLocation(structurePoolElement);
// if (structureKey == null || !structureKey.equals(locationKey)) {
// reducedList.add(structurePoolElement);
// continue;
// }
//
// if (instancesFound >= allowedNumberOfInstances) {
// continue;
// }
//
// reducedList.add(structurePoolElement);
// instancesFound++;
// }
//
// return reducedList;
// }
}
@@ -1,68 +0,0 @@
/*
* 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.mixin;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.CobblemonVillagerProfessions;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import kotlin.Pair;
import net.minecraft.ChatFormatting;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.monster.ZombieVillager;
import net.minecraft.world.entity.npc.Villager;
import net.minecraft.world.entity.npc.VillagerProfession;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(RenderLayer.class)
public abstract class VillagerProfessionLayerMixin {
@Inject(method = "renderColoredCutoutModel", at = @At(value = "HEAD"), cancellable = true)
private static <T extends LivingEntity> void cobblemon$renderVillagerProfessionLayer(EntityModel<T> entityModel, ResourceLocation resourceLocation, PoseStack poseStack, MultiBufferSource multiBufferSource, int i, T livingEntity, int j, CallbackInfo ci) {
if (resourceLocation.getNamespace().equals(Cobblemon.MODID)) {
VillagerProfession profession = null;
boolean isZombie = false;
if (livingEntity instanceof Villager) profession = ((Villager) livingEntity).getVillagerData().getProfession();
if (livingEntity instanceof ZombieVillager) {
profession = ((ZombieVillager) livingEntity).getVillagerData().getProfession();
isZombie = true;
}
Pair<String, String[]> override = CobblemonVillagerProfessions.INSTANCE.getNameTagOverride(profession);
if (profession != null && override != null) {
String entityName = ChatFormatting.stripFormatting(livingEntity.getName().getString());
boolean nameMatches = false;
for (String name: override.getSecond())
if (entityName.endsWith(name)) {
nameMatches = true;
break;
}
if (nameMatches && resourceLocation.equals(getProfessionResourceLocation(resourceLocation.getNamespace(), ResourceLocation.parse(profession.name()).getPath(), isZombie))) {
ResourceLocation textureLocation = getProfessionResourceLocation(resourceLocation.getNamespace(), override.getFirst(), isZombie);
VertexConsumer vertexConsumer = multiBufferSource.getBuffer(RenderType.entityCutoutNoCull(textureLocation));
entityModel.renderToBuffer(poseStack, vertexConsumer, i, LivingEntityRenderer.getOverlayCoords(livingEntity, 0.0F), j);
ci.cancel();
}
}
}
}
private static ResourceLocation getProfessionResourceLocation(String namespace, String profession, Boolean isZombie) {
return ResourceLocation.fromNamespaceAndPath(namespace, "textures/entity/" + (isZombie ? "zombie_villager" : "villager") + "/profession/" + profession + ".png");
}
}
@@ -1,19 +0,0 @@
/*
* 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.mixin.accessor;
import net.minecraft.world.level.biome.Biome;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(Biome.class)
public interface BiomeAccessor {
@Accessor("climateSettings")
Biome.ClimateSettings getClimateSettings();
}
@@ -32,7 +32,7 @@ public class GuiMixin {
// sus af. You have to get the method name by looking at the bytecode of the class. Can do in intellij By clicking View -> Show Bytecode (top left)
@Inject(method = "method_55797", at = @At("HEAD"), cancellable = true)
private static void cobblemon$dontRenderUiInPokedex(Minecraft minecraft, CallbackInfoReturnable<Boolean> cir) {
if (CobblemonClient.INSTANCE.getPokedexUsageContext().getScanningGuiOpen() && minecraft.options.getCameraType().isFirstPerson()) {
if (CobblemonClient.INSTANCE.getPokedexUsageContext().getScanningGuiOpen()) {
cir.setReturnValue(false);
}
}
@@ -0,0 +1,69 @@
/*
* 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.mixin.pokeball;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.ModAPI;
import com.cobblemon.mod.common.item.PokeBallItem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.ItemModelShaper;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ItemRenderer.class)
public abstract class ItemRendererMixin {
@Unique private final String MODEL_PATH = Cobblemon.implementation.getModAPI() == ModAPI.FABRIC ? "fabric_resource" : "standalone";
@Shadow @Final private ItemModelShaper itemModelShaper;
@Shadow public abstract void render(ItemStack stack, ItemDisplayContext renderMode, boolean leftHanded, PoseStack matrices, MultiBufferSource vertexConsumers, int light, int overlay, BakedModel model);
@Inject(method = "getModel", at = @At("HEAD"), cancellable = true)
private void cobblemon$bakePokeballModel(ItemStack stack, Level world, LivingEntity entity, int seed, CallbackInfoReturnable<BakedModel> cir) {
if (stack.getItem() instanceof PokeBallItem pokeBallItem) {
BakedModel model = this.itemModelShaper.getModelManager().getModel(new ModelResourceLocation(pokeBallItem.getPokeBall().getModel3d(), MODEL_PATH));
ClientLevel clientWorld = world instanceof ClientLevel ? (ClientLevel) world : null;
BakedModel overriddenModel = model.getOverrides().resolve(model, stack, clientWorld, entity, seed);
cir.setReturnValue(overriddenModel == null ? this.itemModelShaper.getModelManager().getMissingModel() : overriddenModel);
}
}
@Inject(
method = "render(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemDisplayContext;ZLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;IILnet/minecraft/client/resources/model/BakedModel;)V",
at = @At("HEAD"),
cancellable = true
)
private void cobblemon$determinePokeballModel(ItemStack stack, ItemDisplayContext renderMode, boolean leftHanded, PoseStack matrices, MultiBufferSource vertexConsumers, int light, int overlay, BakedModel model, CallbackInfo ci) {
boolean shouldBe2d = renderMode == ItemDisplayContext.GUI || renderMode == ItemDisplayContext.FIXED;
if (shouldBe2d && stack.getItem() instanceof PokeBallItem pokeBallItem) {
BakedModel replacementModel = this.itemModelShaper.getModelManager().getModel(new ModelResourceLocation(pokeBallItem.getPokeBall().getModel2d(), "inventory"));
if (!model.equals(replacementModel)) {
ci.cancel();
render(stack, renderMode, leftHanded, matrices, vertexConsumers, light, overlay, replacementModel);
}
}
}
}
@@ -9,6 +9,9 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.CobblemonBuildDetails.smallCommitHash
//import com.cobblemon.mod.common.api.storage.player.factory.MongoPlayerDataStoreFactory
import com.cobblemon.mod.common.advancement.CobblemonCriteria
import com.cobblemon.mod.common.advancement.criterion.EvolvePokemonContext
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.SeasonResolver
import com.cobblemon.mod.common.api.data.DataProvider
@@ -16,7 +19,15 @@ import com.cobblemon.mod.common.api.drop.CommandDropEntry
import com.cobblemon.mod.common.api.drop.DropEntry
import com.cobblemon.mod.common.api.drop.ItemDropEntry
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.CobblemonEvents.BATTLE_STARTED_POST
import com.cobblemon.mod.common.api.events.CobblemonEvents.BATTLE_VICTORY
import com.cobblemon.mod.common.api.events.CobblemonEvents.DATA_SYNCHRONIZED
import com.cobblemon.mod.common.api.events.CobblemonEvents.EVOLUTION_COMPLETE
import com.cobblemon.mod.common.api.events.CobblemonEvents.LEVEL_UP_EVENT
import com.cobblemon.mod.common.api.events.CobblemonEvents.POKEMON_CAPTURED
import com.cobblemon.mod.common.api.events.CobblemonEvents.POKEMON_SCANNED
import com.cobblemon.mod.common.api.events.CobblemonEvents.STARTER_CHOSEN
import com.cobblemon.mod.common.api.events.CobblemonEvents.TRADE_COMPLETED
import com.cobblemon.mod.common.api.permission.PermissionValidator
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalculator
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalculators
@@ -43,6 +54,7 @@ import com.cobblemon.mod.common.api.spawning.prospecting.SpawningProspector
import com.cobblemon.mod.common.api.starter.StarterHandler
import com.cobblemon.mod.common.api.storage.PokemonStoreManager
import com.cobblemon.mod.common.api.storage.adapter.conversions.ReforgedConversion
import com.cobblemon.mod.common.api.storage.adapter.database.MongoDBStoreAdapter
import com.cobblemon.mod.common.api.storage.adapter.flatfile.FileStoreAdapter
import com.cobblemon.mod.common.api.storage.adapter.flatfile.JSONStoreAdapter
import com.cobblemon.mod.common.api.storage.adapter.flatfile.NBTStoreAdapter
@@ -51,7 +63,7 @@ import com.cobblemon.mod.common.api.storage.molang.NbtMoLangDataStoreFactory
import com.cobblemon.mod.common.api.storage.pc.PCStore
import com.cobblemon.mod.common.api.storage.pc.link.PCLinkManager
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreManager
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreType
import com.cobblemon.mod.common.api.storage.player.adapter.DexDataNbtBackend
import com.cobblemon.mod.common.api.storage.player.adapter.PlayerDataJsonBackend
import com.cobblemon.mod.common.api.storage.player.factory.CachedPlayerDataStoreFactory
@@ -64,16 +76,16 @@ import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.battles.ShowdownThread
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.command.argument.DexArgumentType
import com.cobblemon.mod.common.command.argument.DialogueArgumentType
import com.cobblemon.mod.common.command.argument.FormArgumentType
import com.cobblemon.mod.common.command.argument.MoveArgumentType
import com.cobblemon.mod.common.command.argument.NPCClassArgumentType
import com.cobblemon.mod.common.command.argument.PartySlotArgumentType
import com.cobblemon.mod.common.command.argument.SpeciesArgumentType
import com.cobblemon.mod.common.command.argument.PokemonPropertiesArgumentType
import com.cobblemon.mod.common.command.argument.PokemonStoreArgumentType
import com.cobblemon.mod.common.command.argument.SpawnBucketArgumentType
import com.cobblemon.mod.common.command.argument.SpeciesArgumentType
import com.cobblemon.mod.common.config.CobblemonConfig
import com.cobblemon.mod.common.config.LastChangedVersion
import com.cobblemon.mod.common.config.constraint.IntConstraint
@@ -92,9 +104,7 @@ import com.cobblemon.mod.common.pokemon.evolution.variants.BlockClickEvolution
import com.cobblemon.mod.common.pokemon.feature.TagSeasonResolver
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.pokemon.properties.AspectPropertyType
import com.cobblemon.mod.common.pokemon.properties.FreezeFrameProperty
import com.cobblemon.mod.common.pokemon.properties.HiddenAbilityPropertyType
import com.cobblemon.mod.common.pokemon.properties.NoAIProperty
import com.cobblemon.mod.common.pokemon.properties.UnaspectPropertyType
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.pokemon.properties.tags.PokemonFlagProperty
@@ -106,6 +116,7 @@ import com.cobblemon.mod.common.world.feature.CobblemonPlacedFeatures
import com.cobblemon.mod.common.world.feature.ore.CobblemonOrePlacedFeatures
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import com.mongodb.client.MongoClient
import com.mongodb.client.MongoClients
import java.io.File
import java.io.FileReader
import java.io.FileWriter
@@ -129,7 +140,6 @@ object Cobblemon {
const val MODID = CobblemonBuildDetails.MOD_ID
const val VERSION = CobblemonBuildDetails.VERSION
const val CONFIG_PATH = "config/$MODID/main.json"
@JvmField
val LOGGER: Logger = LogManager.getLogger()
lateinit var implementation: CobblemonImplementation
@@ -162,7 +172,7 @@ object Cobblemon {
this.implementation = implementation
this.LOGGER.info("Launching Cobblemon ${CobblemonBuildDetails.VERSION}${if(CobblemonBuildDetails.SNAPSHOT) "-SNAPSHOT" else ""} ")
if (CobblemonBuildDetails.SNAPSHOT) {
if(CobblemonBuildDetails.SNAPSHOT) {
this.LOGGER.info(" - Git Commit: ${smallCommitHash()} (https://gitlab.com/cable-mc/cobblemon/-/commit/${CobblemonBuildDetails.GIT_COMMIT})")
this.LOGGER.info(" - Branch: ${CobblemonBuildDetails.BRANCH}")
}
@@ -175,7 +185,6 @@ object Cobblemon {
implementation.registerEntityTypes()
implementation.registerEntityAttributes()
implementation.registerBlockEntityTypes()
implementation.registerVillagers()
implementation.registerWorldGenFeatures()
implementation.registerParticles()
implementation.registerEntityDataSerializers()
@@ -202,7 +211,7 @@ object Cobblemon {
storage.onPlayerDataSync(it)
playerDataManager.syncAllToPlayer(it)
starterHandler.handleJoin(it)
ServerSettingsPacket(this.config.preventCompletePartyDeposit, this.config.displayEntityLevelLabel, this.config.displayEntityNameLabel).sendToPlayer(it)
ServerSettingsPacket(this.config.preventCompletePartyDeposit, this.config.displayEntityLevelLabel).sendToPlayer(it)
}
PlatformEvents.SERVER_PLAYER_LOGOUT.subscribe {
PCLinkManager.removeLink(it.player.uuid)
@@ -277,8 +286,6 @@ object Cobblemon {
CustomPokemonProperty.register(HiddenAbilityPropertyType)
CustomPokemonProperty.register(AspectPropertyType)
CustomPokemonProperty.register(UnaspectPropertyType)
CustomPokemonProperty.register(FreezeFrameProperty)
CustomPokemonProperty.register(NoAIProperty)
ifDedicatedServer {
isDedicatedServer = true
@@ -306,8 +313,8 @@ object Cobblemon {
val pokedexNbtFactory = CachedPlayerDataStoreFactory(DexDataNbtBackend())
pokedexNbtFactory.setup(server)
playerDataManager.setFactory(generalJsonFactory, PlayerInstancedDataStoreTypes.GENERAL)
playerDataManager.setFactory(pokedexNbtFactory, PlayerInstancedDataStoreTypes.POKEDEX)
playerDataManager.setFactory(generalJsonFactory, PlayerInstancedDataStoreType.GENERAL)
playerDataManager.setFactory(pokedexNbtFactory, PlayerInstancedDataStoreType.POKEDEX)
if (config.storageFormat == "nbt") {
NBTStoreAdapter(pokemonStoreRoot.absolutePath, useNestedFolders = true, folderPerClass = true)
@@ -358,7 +365,7 @@ object Cobblemon {
}
PlatformEvents.SERVER_STOPPED.subscribe {
storage.unregisterAll(it.server.registryAccess())
storage.unregisterAll()
playerDataManager.saveAllStores()
}
PlatformEvents.SERVER_STARTED.subscribe {
@@ -489,7 +496,6 @@ object Cobblemon {
this.implementation.registerCommandArgument(cobblemonResource("dialogue"), DialogueArgumentType::class, SingletonArgumentInfo.contextFree(DialogueArgumentType::dialogue))
this.implementation.registerCommandArgument(cobblemonResource("form"), FormArgumentType::class, SingletonArgumentInfo.contextFree(FormArgumentType::form))
this.implementation.registerCommandArgument(cobblemonResource("dex"), DexArgumentType::class, SingletonArgumentInfo.contextFree (DexArgumentType::dex))
this.implementation.registerCommandArgument(cobblemonResource("npc_class"), NPCClassArgumentType::class, SingletonArgumentInfo.contextFree(NPCClassArgumentType::npcClass))
}
}
@@ -14,8 +14,6 @@ object CobblemonActivities {
val activities = mutableListOf<Activity>()
val BATTLING_ACTIVITY = Activity("pokemon_battling")
val NPC_BATTLING = Activity("npc_battling")
val NPC_ACTION_EFFECT = Activity("npc_action_effect")
val NPC_CHATTING = Activity("npc_chatting")
fun register(activity: Activity): Activity {
activities.add(activity)
@@ -661,6 +661,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
BlockBehaviour.Properties.of()
.sound(CobblemonSounds.RELIC_COIN_POUCH_SOUNDS)
.pushReaction(PushReaction.DESTROY)
.strength(0.4f)
.noOcclusion(), true
)
)
@@ -17,7 +17,6 @@ import net.minecraft.commands.Commands
object CobblemonCommands {
fun register(dispatcher: CommandDispatcher<CommandSourceStack>, registry: CommandBuildContext, selection: Commands.CommandSelection) {
SpawnPokemon.register(dispatcher)
SpawnNPCCommand.register(dispatcher)
GivePokemon.register(dispatcher)
TakePokemon.register(dispatcher)
ChangeScaleAndSize.register(dispatcher)
@@ -52,8 +51,6 @@ object CobblemonCommands {
OpenDialogueCommand.register(dispatcher)
NPCEditCommand.register(dispatcher)
AbandonMultiTeam.register(dispatcher)
FreezePokemonCommand.register(dispatcher)
ApplyPlayerTextureCommand.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
@@ -97,12 +97,6 @@ interface CobblemonImplementation {
*/
fun registerBlockEntityTypes()
/**
* TODO
*
*/
fun registerVillagers()
/**
* TODO
*
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.dialogue.ActiveDialogue
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.ListCodec
import com.mojang.serialization.codecs.PrimitiveCodec
@@ -22,8 +20,6 @@ object CobblemonMemories {
val BATTLING_POKEMON = register("battling_pokemon", ListCodec(UUIDUtil.CODEC, 0, 31))
val NPC_BATTLING = register("npc_battling", PrimitiveCodec.BOOL)
val DIALOGUES = register<List<ActiveDialogue>>("npc_dialogues")
val ACTIVE_ACTION_EFFECT = register<ActionEffectContext>("active_action_effect")
fun <U> register(id: String, codec: Codec<U>): MemoryModuleType<U> {
val memoryModule = MemoryModuleType(Optional.of(codec))
@@ -32,7 +32,7 @@ import com.cobblemon.mod.common.client.net.pasture.ClosePastureHandler
import com.cobblemon.mod.common.client.net.pasture.OpenPastureHandler
import com.cobblemon.mod.common.client.net.pasture.PokemonPasturedHandler
import com.cobblemon.mod.common.client.net.pasture.PokemonUnpasturedHandler
import com.cobblemon.mod.common.client.net.pokedex.ServerConfirmedRegisterHandler
import com.cobblemon.mod.common.client.net.pokedex.ServerConfirmedScanHandler
import com.cobblemon.mod.common.client.net.pokemon.update.PokemonUpdatePacketHandler
import com.cobblemon.mod.common.client.net.settings.ServerSettingsPacketHandler
import com.cobblemon.mod.common.client.net.sound.UnvalidatedPlaySoundS2CPacketHandler
@@ -87,7 +87,6 @@ import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.Rem
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
import com.cobblemon.mod.common.net.messages.client.sound.UnvalidatedPlaySoundS2CPacket
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnGenericBedrockPacket
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
@@ -185,7 +184,7 @@ 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.client.pokedex.ServerConfirmedRegisterPacket
import com.cobblemon.mod.common.net.messages.client.pokedex.ServerConfirmedScanPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.FinishScanningPacket
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.FinishScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanningHandler
@@ -323,7 +322,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(PokeRodRegistrySyncPacket.ID, PokeRodRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(ScriptRegistrySyncPacket.ID, ScriptRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(PokedexDexSyncPacket.ID, PokedexDexSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(DexEntrySyncPacket.ID, DexEntrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(FishingBaitRegistrySyncPacket.ID, FishingBaitRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
// Effects
@@ -335,9 +333,8 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(UnvalidatedPlaySoundS2CPacket.ID, UnvalidatedPlaySoundS2CPacket::decode, UnvalidatedPlaySoundS2CPacketHandler))
list.add(PacketRegisterInfo(SpawnPokemonPacket.ID, SpawnPokemonPacket::decode, SpawnExtraDataEntityHandler()))
list.add(PacketRegisterInfo(SpawnPokeballPacket.ID, SpawnPokeballPacket::decode, SpawnExtraDataEntityHandler()))
list.add(PacketRegisterInfo(SpawnGenericBedrockPacket.ID, SpawnGenericBedrockPacket::decode, SpawnExtraDataEntityHandler()))
list.add(PacketRegisterInfo(SpawnNPCPacket.ID, SpawnNPCPacket::decode, SpawnExtraDataEntityHandler()))
list.add(PacketRegisterInfo(ToastPacket.ID, ToastPacket::decode, ToastPacketHandler))
list.add(PacketRegisterInfo(SpawnGenericBedrockPacket.ID, SpawnGenericBedrockPacket::decode, SpawnExtraDataEntityHandler()))
// Trade packets
list.add(PacketRegisterInfo(TradeAcceptanceChangedPacket.ID, TradeAcceptanceChangedPacket::decode, TradeAcceptanceChangedHandler))
@@ -375,7 +372,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(OpenNPCEditorPacket.ID, OpenNPCEditorPacket::decode, OpenNPCEditorHandler))
//Pokedex scanning
list.add(PacketRegisterInfo(ServerConfirmedRegisterPacket.ID, ServerConfirmedRegisterPacket::decode, ServerConfirmedRegisterHandler))
list.add(PacketRegisterInfo(ServerConfirmedScanPacket.ID, ServerConfirmedScanPacket::decode, ServerConfirmedScanHandler))
return list
}
@@ -39,8 +39,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val PC_CLICK = this.create("pc.click")
@JvmField
val VILLAGER_WORK_NURSE = this.create("entity.villager.work_nurse")
@JvmField
val HEALING_MACHINE_ACTIVE = this.create("healing_machine.active")
@@ -53,14 +51,10 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val POKE_BALL_OPEN = this.create("poke_ball.open")
@JvmField
val POKE_BALL_SHINY_OPEN = this.create("poke_ball.shiny_open")
@JvmField
val POKE_BALL_HIT = this.create("poke_ball.hit")
@JvmField
val POKE_BALL_SEND_OUT = this.create("poke_ball.send_out")
@JvmField
val POKE_BALL_SHINY_SEND_OUT = this.create("poke_ball.shiny_send_out")
@JvmField
val POKE_BALL_RECALL = this.create("poke_ball.recall")
@JvmField
val POKE_BALL_THROW = this.create("poke_ball.throw")
@@ -72,23 +66,29 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val POKEDEX_CLOSE = this.create("item.pokedex.close")
@JvmField
val POKEDEX_CLICK = this.create("item.pokedex.click")
@JvmField
val POKEDEX_CLICK_SHORT = this.create("item.pokedex.click_short")
@JvmField
val POKEDEX_SCAN_OPEN = this.create("item.pokedex.scan_open")
@JvmField
val POKEDEX_SCAN_CLOSE = this.create("item.pokedex.scan_close")
@JvmField
val POKEDEX_SCAN = this.create("item.pokedex.scan")
@JvmField
val POKEDEX_SCAN_LOOP = this.create("item.pokedex.scan_loop")
@JvmField
val POKEDEX_SCAN_DETAIL = this.create("item.pokedex.scan_detail")
@JvmField
val POKEDEX_SCAN_REGISTER_POKEMON = this.create("item.pokedex.scan_register_pokemon")
val POKEDEX_CLICK = this.create("item.pokedex.click")
@JvmField
val POKEDEX_SCAN_REGISTER_ASPECT = this.create("item.pokedex.scan_register_aspect")
val POKEDEX_CLICK_SHORT = this.create("item.pokedex.click_short")
@JvmField
val POKEDEX_SCAN_ZOOM_INCREMENT = this.create("item.pokedex.scan_zoom_increment")
val POKEDEX_SNAP_PICTURE = this.create("item.pokedex.snap")
@JvmField
val POKEDEX_ZOOM_IN = this.create("item.pokedex.zoom_in")
@JvmField
val POKEDEX_ZOOM_OUT = this.create("item.pokedex.zoom_out")
@JvmField
val POKEDEX_ZOOM_INCREMENT = this.create("item.pokedex.zoom_increment")
@JvmField
val ITEM_USE = this.create("item.use")
@@ -97,7 +97,7 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val EVOLUTION_UI = this.create("evolution.ui")
@JvmField
val EVOLUTION = this.create("evolution.full")
val EVOLUTION = this.create("evolution")
@JvmField
val PVN_BATTLE = this.create("battle.pvn.default")
@@ -9,16 +9,9 @@
package com.cobblemon.mod.common
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.util.RandomSource
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.npc.VillagerData
import net.minecraft.world.entity.npc.VillagerProfession
import net.minecraft.world.entity.npc.VillagerTrades
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.trading.ItemCost
import net.minecraft.world.item.trading.MerchantOffer
import java.util.Optional
/**
* A generator for various trade offers in the mod.
@@ -39,41 +32,8 @@ object CobblemonTradeOffers {
* @return The resulting list.
*/
fun tradeOffersFor(profession: VillagerProfession): List<VillagerTradeOffer> = when (profession) {
CobblemonVillagerProfessions.NURSE -> listOf(
// requiredLevel = the level of the villager for the trade to appear for
// Novice = 1, Apprentice = 2, Journeyman = 3, Expert = 4, Master = 5
VillagerTradeOffer(CobblemonVillagerProfessions.NURSE, 1, listOf(
// i = emeraldCost, j = numberOfItems, k = maxUses, l = villagerXp, f = priceMultiplier
VillagerTrades.ItemsForEmeralds(CobblemonItems.ORAN_BERRY, 4, 1, 16, 1),
VillagerTrades.ItemsForEmeralds(CobblemonItems.PERSIM_BERRY, 4, 1, 16, 1),
VillagerTrades.ItemsForEmeralds(CobblemonItems.ENERGY_ROOT, 4, 1, 12, 1),
TradeOffer(ItemCost(CobblemonItems.MEDICINAL_LEEK, 24), ItemStack(Items.EMERALD, 1), 12, 2)
)),
VillagerTradeOffer(CobblemonVillagerProfessions.NURSE, 2, listOf(
TradeOffer(ItemCost(Items.EMERALD, 8), ItemStack(CobblemonItems.MEDICINAL_BREW), 8, 5, Optional.of<ItemCost>(ItemCost(Items.GLASS_BOTTLE))),
VillagerTrades.ItemsForEmeralds(CobblemonItems.BLUE_MINT_LEAF, 2, 1, 8, 5),
VillagerTrades.ItemsForEmeralds(CobblemonItems.CYAN_MINT_LEAF, 2, 1, 8, 5),
VillagerTrades.ItemsForEmeralds(CobblemonItems.GREEN_MINT_LEAF, 2, 1, 8, 5),
VillagerTrades.ItemsForEmeralds(CobblemonItems.PINK_MINT_LEAF, 2, 1, 8, 5),
VillagerTrades.ItemsForEmeralds(CobblemonItems.RED_MINT_LEAF, 2, 1, 8, 5),
VillagerTrades.ItemsForEmeralds(CobblemonItems.WHITE_MINT_LEAF, 2, 1, 8, 5)
)),
VillagerTradeOffer(CobblemonVillagerProfessions.NURSE, 3, listOf(
TradeOffer(ItemCost(CobblemonItems.MULCH_BASE, 4), ItemStack(Items.EMERALD, 2), 16, 20),
VillagerTrades.ItemsForEmeralds(Items.GLASS_BOTTLE, 1, 1, 16, 10)
)),
VillagerTradeOffer(CobblemonVillagerProfessions.NURSE, 4, listOf(
VillagerTrades.ItemsForEmeralds(CobblemonItems.REVIVAL_HERB, 8, 1, 16, 15),
VillagerTrades.ItemsForEmeralds(CobblemonItems.VIVICHOKE_SEEDS, 24, 1, 4, 15)
)),
VillagerTradeOffer(CobblemonVillagerProfessions.NURSE, 5, listOf(
TradeOffer(ItemCost(Items.EMERALD, 10), ItemStack(CobblemonItems.ANTIDOTE, 1), 12, 30, Optional.of<ItemCost>(ItemCost(Items.GLASS_BOTTLE))),
TradeOffer(ItemCost(Items.EMERALD, 10), ItemStack(CobblemonItems.AWAKENING, 1), 12, 30, Optional.of<ItemCost>(ItemCost(Items.GLASS_BOTTLE))),
TradeOffer(ItemCost(Items.EMERALD, 10), ItemStack(CobblemonItems.BURN_HEAL, 1), 12, 30, Optional.of<ItemCost>(ItemCost(Items.GLASS_BOTTLE))),
TradeOffer(ItemCost(Items.EMERALD, 10), ItemStack(CobblemonItems.ICE_HEAL, 1), 12, 30, Optional.of<ItemCost>(ItemCost(Items.GLASS_BOTTLE))),
TradeOffer(ItemCost(Items.EMERALD, 10), ItemStack(CobblemonItems.PARALYZE_HEAL, 1), 12, 30, Optional.of<ItemCost>(ItemCost(Items.GLASS_BOTTLE))),
TradeOffer(ItemCost(Items.EMERALD, 22), ItemStack(CobblemonItems.VIVICHOKE_DIP, 1), 4, 30, Optional.of<ItemCost>(ItemCost(Items.BOWL)))
))
VillagerProfession.FARMER -> listOf(
VillagerTradeOffer(VillagerProfession.FARMER, 3, listOf(VillagerTrades.ItemsForEmeralds(CobblemonItems.VIVICHOKE_SEEDS, 24, 1, 1, 6)))
)
VillagerProfession.FISHERMAN -> listOf(
VillagerTradeOffer(VillagerProfession.FISHERMAN, 5, listOf(
@@ -93,7 +53,7 @@ object CobblemonTradeOffers {
)
/**
* Represents trade offers that can be attached to a villager or a wandering trader.
* Represents a trade offer that can be attached to a villager or a wandering trader.
*/
interface TradeOfferHolder {
/**
@@ -103,7 +63,7 @@ object CobblemonTradeOffers {
}
/**
* Represents trade offers from a villager.
* Represents a trade offer from a villager.
*
* @property profession The target profession for this trade.
* @property requiredLevel The level required must be a possible level.
@@ -115,7 +75,7 @@ object CobblemonTradeOffers {
val profession: VillagerProfession,
val requiredLevel: Int,
override val tradeOffers: List<VillagerTrades.ItemListing>
): TradeOfferHolder {
) : TradeOfferHolder {
init {
if (this.requiredLevel < VillagerData.MIN_VILLAGER_LEVEL || this.requiredLevel > VillagerData.MAX_VILLAGER_LEVEL) {
@@ -126,7 +86,7 @@ object CobblemonTradeOffers {
}
/**
* Represents trade offers from a wandering trader.
* Represents a trade offer from a wandering trader.
*
* @property isRareTrade If this is rare or common trade.
* @property tradeOffers The list of the possible [TradeOffers.Factory].
@@ -134,26 +94,6 @@ object CobblemonTradeOffers {
data class WandererTradeOffer(
val isRareTrade: Boolean,
override val tradeOffers: List<VillagerTrades.ItemListing>
): TradeOfferHolder
/**
* Represents a trade offer from a villager.
*
* @property offeredItem The item cost offered to the villager.
* @property receivedItem The item received from the villager.
* @property maxUses How many times the trade can be used before the villager resupplies.
* @property villagerXp The experience the villager gains from the trade.
* @property optionalOfferedItem The optional, secondary item cost offered to the villager.
* @property priceMultiplier The amount multiplier of how much the item cost can rise and fall by depending on discounts and penalties
*/
class TradeOffer(
private val offeredItem: ItemCost,
private val receivedItem: ItemStack,
private val maxUses: Int,
private val villagerXp: Int = 1,
private val optionalOfferedItem: Optional<ItemCost> = Optional.empty(),
private val priceMultiplier: Float = 0.05F
): VillagerTrades.ItemListing {
override fun getOffer(entity: Entity?, randomSource: RandomSource?): MerchantOffer = MerchantOffer(offeredItem, optionalOfferedItem, receivedItem, maxUses, villagerXp, priceMultiplier)
}
) : TradeOfferHolder
}
@@ -1,34 +0,0 @@
/*
* 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.cobblemon.mod.common.platform.PlatformRegistry
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.common.collect.ImmutableSet
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceKey
import net.minecraft.world.entity.ai.village.poi.PoiType
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
object CobblemonVillagerPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Registry<PoiType>>, PoiType>() {
override val registry: Registry<PoiType> = BuiltInRegistries.POINT_OF_INTEREST_TYPE
override val resourceKey: ResourceKey<Registry<PoiType>> = Registries.POINT_OF_INTEREST_TYPE
@JvmField
val NURSE_KEY: ResourceKey<PoiType> = createKey("nurse")
@JvmField
val NURSE = create(NURSE_KEY.location().path, PoiType(getBlockStates(CobblemonBlocks.HEALING_MACHINE), 1, 1))
private fun createKey(string: String): ResourceKey<PoiType> = ResourceKey.create(Registries.POINT_OF_INTEREST_TYPE, cobblemonResource(string))
private fun getBlockStates(block: Block): Set<BlockState> = ImmutableSet.copyOf(block.stateDefinition.possibleStates)
}
@@ -1,47 +0,0 @@
/*
* 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.cobblemon.mod.common.platform.PlatformRegistry
import com.google.common.collect.ImmutableSet
import net.minecraft.core.Holder
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceKey
import net.minecraft.sounds.SoundEvent
import net.minecraft.world.entity.ai.village.poi.PoiType
import net.minecraft.world.entity.npc.VillagerProfession
object CobblemonVillagerProfessions: PlatformRegistry<Registry<VillagerProfession>, ResourceKey<Registry<VillagerProfession>>, VillagerProfession>() {
override val registry: Registry<VillagerProfession> = BuiltInRegistries.VILLAGER_PROFESSION
override val resourceKey: ResourceKey<Registry<VillagerProfession>> = Registries.VILLAGER_PROFESSION
@JvmField
val NURSE = profession(CobblemonVillagerPoiTypes.NURSE_KEY, CobblemonSounds.VILLAGER_WORK_NURSE)
/**
* Register a villager profession texture override for Cobblemon villagers of the given [VillagerProfession] for specified names.
*
* @param profession The Cobblemon [VillagerProfession] being overridden.
* @return The profession override texture file name and a list of names that will trigger the override, as a pair.
*/
fun getNameTagOverride(profession: VillagerProfession?): Pair<String, Array<String>>? = when (profession) {
NURSE -> Pair("nurse_joy", arrayOf("ジョーイ", "간호순", "祖兒", "喬伊", "乔伊", "האחות ג'וי", "الممرضة جوي", "Joy", "Joelle", "Joëlle", "Джой", "จอย"))
else -> null
}
private fun profession(resourceKey: ResourceKey<PoiType>, soundEvent: SoundEvent?): VillagerProfession =
create(resourceKey.location().path, VillagerProfession(
resourceKey.location().toString(),
{ holder: Holder<PoiType> -> holder.`is`(resourceKey) },
{ holder: Holder<PoiType> -> holder.`is`(resourceKey) },
ImmutableSet.of(), ImmutableSet.of(), soundEvent
))
}
@@ -21,18 +21,20 @@ class PokemonInteractContext(val type: ResourceLocation, val item: ResourceLocat
class PokemonInteractCriterion(
playerCtx: Optional<ContextAwarePredicate>,
val type: String,
val item: String
val type: Optional<String>,
val item: Optional<String>
): SimpleCriterionCondition<PokemonInteractContext>(playerCtx) {
companion object {
val CODEC: Codec<PokemonInteractCriterion> = RecordCodecBuilder.create { it.group(
EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter(PokemonInteractCriterion::playerCtx),
Codec.STRING.optionalFieldOf("type", "any").forGetter(PokemonInteractCriterion::type),
Codec.STRING.optionalFieldOf("item", "any").forGetter(PokemonInteractCriterion::item)
Codec.STRING.optionalFieldOf("type").forGetter(PokemonInteractCriterion::type),
Codec.STRING.optionalFieldOf("item").forGetter(PokemonInteractCriterion::item)
).apply(it, ::PokemonInteractCriterion) }
}
override fun matches(player: ServerPlayer, context: PokemonInteractContext): Boolean {
return (context.type == this.type.asIdentifierDefaultingNamespace() || this.type == "any") && (context.item == this.item.asIdentifierDefaultingNamespace() || this.item == "any")
val otherType = this.type.orElse("any")
val otherItem = this.item.orElse("any")
return (context.type == otherType.asIdentifierDefaultingNamespace() || otherType == "any") && (context.type == otherItem.asIdentifierDefaultingNamespace() || otherItem == "any")
}
}
@@ -33,6 +33,7 @@ import java.io.PrintWriter
* @since April 1st, 2022.
*/
object JsonManifestWalker {
// im walkin ere
/**
* Builds a manifest at [manifestPath] using the files inside the same folder.
*
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.battles.model
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.CobblemonNetwork
@@ -42,8 +41,6 @@ import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.battles.interpreter.ContextManager
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
@@ -93,7 +90,6 @@ open class PokemonBattle(
.filterIsInstance<LastBattleCriticalHitsEvolutionProgress>()
.forEach { it.reset() }
}
actor.setupStruct()
}
}
@@ -185,11 +181,6 @@ open class PokemonBattle(
*/
fun getActor(player: ServerPlayer) = actors.firstOrNull { it.isForPlayer(player) }
/**
* Gets the first battle actor whom the given NPC controls, or null if there is no such actor.
*/
fun getActor(npc: NPCEntity) = actors.firstOrNull { it is NPCBattleActor && it.npc == npc }
/**
* Gets a [BattleActor] and an [ActiveBattlePokemon] from a pnx key, e.g. p2a
*
@@ -8,8 +8,6 @@
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.net.NetworkPacket
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
@@ -36,8 +34,6 @@ abstract class BattleActor(
pokemonList.forEach { it.actor = this }
}
val struct = QueryStruct(hashMapOf())
lateinit var showdownId: String
lateinit var battle: PokemonBattle
@@ -138,10 +134,6 @@ abstract class BattleActor(
battle.writeShowdownAction(">$showdownId ${showdownMessages.joinToString()}")
}
open fun setupStruct() {
struct.addFunction("uuid") { StringValue(uuid.toString()) }
}
abstract fun getName(): MutableComponent
/**
@@ -139,7 +139,7 @@ open class PartySelectPokemonDTO(
aspects = pokemon.aspects,
heldItem = pokemon.heldItemNoCopy(),
currentHealth = pokemon.currentHealth,
maxHealth = pokemon.maxHealth,
maxHealth = pokemon.hp,
enabled = enabled
)
@@ -22,10 +22,8 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.scheduling.ServerRealTimeTaskTracker
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
import java.util.UUID
import java.util.concurrent.CompletableFuture
import net.minecraft.server.level.ServerPlayer
/**
@@ -36,31 +34,19 @@ import net.minecraft.server.level.ServerPlayer
* @since December 27th, 2023
*/
class ActiveDialogue(var playerEntity: ServerPlayer, var dialogueReference: Dialogue) {
var initialized = false
val dialogueId = UUID.randomUUID()
val runtime = MoLangRuntime().setup()
var npc: NPCEntity? = null
var currentPage = dialogueReference.pages[0]
val playerStruct = playerEntity.asMoLangValue()
var activeInput = ActiveInput(this, currentPage.input)
val completion = CompletableFuture<Unit>()
fun initialize() {
init {
runtime.environment.query.addFunctions(
mapOf(
"dialogue" to java.util.function.Function { _ -> toMoLangStruct() },
"player" to java.util.function.Function { _ -> playerStruct }
)
)
dialogueReference.initializationAction.invoke(this)
if (!completion.isDone) {
val packet = DialogueOpenedPacket(this, true)
playerEntity.sendPacket(packet)
}
initialized = true
}
val currentPageIndex
@@ -99,9 +85,7 @@ class ActiveDialogue(var playerEntity: ServerPlayer, var dialogueReference: Dial
}
}
}
if (initialized) {
playerEntity.sendPacket(DialogueOpenedPacket(this, false))
}
playerEntity.sendPacket(DialogueOpenedPacket(this, false))
}
fun setPage(index: Int) {
@@ -130,10 +114,7 @@ class ActiveDialogue(var playerEntity: ServerPlayer, var dialogueReference: Dial
}
fun close() {
if (initialized) {
DialogueManager.stopDialogue(playerEntity)
}
completion.complete(Unit)
DialogueManager.stopDialogue(playerEntity)
}
fun escape() {
@@ -9,8 +9,6 @@
package com.cobblemon.mod.common.api.dialogue
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.resources.ResourceLocation
/**
* A dialogue that could be sent to players.
@@ -20,39 +18,29 @@ import net.minecraft.resources.ResourceLocation
*/
class Dialogue(
val pages: List<DialoguePage> = mutableListOf(),
val background: ResourceLocation = DEFAULT_BACKGROUND,
val escapeAction: DialogueAction = FunctionDialogueAction { dialogue, _ -> dialogue.close() },
val speakers: Map<String, DialogueSpeaker> = emptyMap(),
val initializationAction: DialogueAction = FunctionDialogueAction { _, _ -> }
val speakers: Map<String, DialogueSpeaker> = emptyMap()
) {
companion object {
val DEFAULT_BACKGROUND = cobblemonResource("textures/gui/dialogue/dialogue_box.png")
@JvmOverloads
fun of(
pages: Iterable<DialoguePage>,
background: ResourceLocation = DEFAULT_BACKGROUND,
escapeAction: ExpressionLike,
speakers: Map<String, DialogueSpeaker>
): Dialogue {
return Dialogue(
pages = pages.toList(),
escapeAction = ExpressionLikeDialogueAction(escapeAction),
background = background,
speakers = speakers
)
}
@JvmOverloads
fun of(
pages: Iterable<DialoguePage>,
background: ResourceLocation = DEFAULT_BACKGROUND,
escapeAction: (ActiveDialogue) -> Unit,
speakers: Map<String, DialogueSpeaker>
): Dialogue {
val dialogue = Dialogue(
pages = pages.toList(),
background = background,
escapeAction = FunctionDialogueAction { activeDialogue, _ -> escapeAction(activeDialogue) },
speakers = speakers
)
@@ -8,10 +8,8 @@
package com.cobblemon.mod.common.api.dialogue
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.util.resolve
/**
@@ -41,9 +39,6 @@ class ExpressionLikeDialogueAction(val expression: ExpressionLike) : DialogueAct
if (input != null) {
dialogue.runtime.environment.setSimpleVariable("selected_option", StringValue(input))
}
dialogue.runtime.resolve(
expression,
mapOf("player" to dialogue.playerStruct, "npc" to (dialogue.npc?.struct ?: QueryStruct(hashMapOf())))
)
dialogue.runtime.resolve(expression)
}
}
@@ -20,8 +20,6 @@ import net.minecraft.resources.ResourceLocation
* @since January 1st, 2024
*/
sealed interface DialogueFaceProvider {
val isLeftSide: Boolean
companion object {
val types = mutableMapOf(
"player" to PlayerDialogueFaceProvider::class.java,
@@ -34,24 +32,15 @@ sealed interface DialogueFaceProvider {
class ArtificialDialogueFaceProvider(
val modelType: String = "",
val identifier: ResourceLocation = cobblemonResource("bulbasaur"),
val aspects: Set<String> = setOf(),
override val isLeftSide: Boolean = true
val aspects: Set<String> = setOf()
) : DialogueFaceProvider
class ReferenceDialogueFaceProvider(
val entityId: Int,
override val isLeftSide: Boolean = true
val entityId: Int
): DialogueFaceProvider
/**
* A face provider that uses the player's skin as the face, as long as there is a player with this UUID online.
*
* What's interesting is that this works for fake players, which is what Taterzens' NPCs use.
*/
class PlayerDialogueFaceProvider(val playerId: UUID = UUID.randomUUID(), override val isLeftSide: Boolean = true) : DialogueFaceProvider
class PlayerDialogueFaceProvider(val playerId: UUID) : DialogueFaceProvider
class ExpressionLikeDialogueFaceProvider(
val providerExpression: ExpressionLike
): DialogueFaceProvider {
override val isLeftSide: Boolean = false // Doesn't get used
}
): DialogueFaceProvider
@@ -13,7 +13,7 @@ import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
import com.cobblemon.mod.common.util.activeDialogue
import com.cobblemon.mod.common.util.withNPCValue
import com.cobblemon.mod.common.util.withQueryValue
import java.util.UUID
import net.minecraft.server.level.ServerPlayer
@@ -28,25 +28,20 @@ import net.minecraft.server.level.ServerPlayer
object DialogueManager {
val activeDialogues = mutableMapOf<UUID, ActiveDialogue>()
fun startDialogue(playerEntity: ServerPlayer, dialogue: Dialogue): ActiveDialogue {
val activeDialogue = ActiveDialogue(playerEntity, dialogue)
startDialogue(activeDialogue)
return activeDialogue
fun startDialogue(playerEntity: ServerPlayer, dialogue: Dialogue) {
startDialogue(ActiveDialogue(playerEntity, dialogue))
}
fun startDialogue(playerEntity: ServerPlayer, npcEntity: NPCEntity, dialogue: Dialogue): ActiveDialogue {
fun startDialogue(playerEntity: ServerPlayer, npcEntity: NPCEntity, dialogue: Dialogue) {
val activeDialogue = ActiveDialogue(playerEntity, dialogue)
activeDialogue.runtime.withNPCValue("npc", npcEntity)
activeDialogue.npc = npcEntity
activeDialogue.runtime.withQueryValue("npc") { npcEntity.struct }
startDialogue(activeDialogue)
return activeDialogue
}
fun startDialogue(activeDialogue: ActiveDialogue) {
activeDialogue.initialize()
if (!activeDialogue.completion.isDone) {
activeDialogues[activeDialogue.playerEntity.uuid] = activeDialogue
}
activeDialogues[activeDialogue.playerEntity.uuid] = activeDialogue
val packet = DialogueOpenedPacket(activeDialogue, true)
activeDialogue.playerEntity.sendPacket(packet)
}
fun stopDialogue(playerEntity: ServerPlayer) {
@@ -17,7 +17,6 @@ import com.cobblemon.mod.common.api.dialogue.input.DialogueInput
import com.cobblemon.mod.common.api.dialogue.input.DialogueNoInput
import com.google.gson.JsonArray
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
/**
* A page of a dialogue. This has lines of text and also some kind of input expected from the player.
@@ -30,7 +29,6 @@ class DialoguePage(
var speaker: String? = null,
var lines: MutableList<DialogueText> = mutableListOf(),
var input: DialogueInput = DialogueNoInput(),
var background: ResourceLocation? = null,
var clientActions: MutableList<Expression> = mutableListOf(),
var escapeAction: DialogueAction? = null,
) {
@@ -42,7 +40,6 @@ class DialoguePage(
speaker: String? = null,
lines: Iterable<MutableComponent>,
input: DialogueInput = DialogueNoInput(),
background: ResourceLocation? = null,
clientActions: Iterable<Expression> = emptyList(),
/** The thing to do when the player presses ESC while on this page. If null, falls back to the same property on [Dialogue]. */
escapeAction: ((ActiveDialogue) -> Unit)? = null,
@@ -52,7 +49,6 @@ class DialoguePage(
speaker = speaker,
lines = lines.map { WrappedDialogueText(it) }.toMutableList(),
input = input,
background = background,
clientActions = clientActions.toMutableList(),
escapeAction = escapeAction?.let { func -> FunctionDialogueAction { activeDialogue, _ -> func(activeDialogue) } }
)
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.entity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import java.util.concurrent.CompletableFuture
/**
@@ -21,6 +20,6 @@ import java.util.concurrent.CompletableFuture
*
*/
interface PokemonSender {
fun sendingOut(pokemon: Pokemon): CompletableFuture<Unit>
fun sendingOut(): CompletableFuture<Unit>
fun recalling(pokemonEntity: PokemonEntity): CompletableFuture<Unit>
}
@@ -11,12 +11,10 @@ package com.cobblemon.mod.common.api.entity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.player.Player
interface PokemonSideDelegate : EntitySideDelegate<PokemonEntity> {
fun changePokemon(pokemon: Pokemon)
fun drop(source: DamageSource?) {}
fun updatePostDeath() {}
fun handleStatus(status: Byte) {}
fun spawnShinyParticle(player: Player) {}
}
@@ -204,6 +204,7 @@ fun drawString(
@JvmOverloads
fun drawPosablePortrait(
identifier: ResourceLocation,
aspects: Set<String>,
matrixStack: PoseStack,
scale: Float = 13F,
contextScale: Float = 1F,
@@ -227,19 +228,20 @@ fun drawPosablePortrait(
matrixStack.scale(scale, scale, -scale)
matrixStack.translate(0.0, -PORTRAIT_DIAMETER / 18.0, 0.0)
val sprite = repository.getSprite(identifier, state, SpriteType.PORTRAIT);
val sprite = repository.getSprite(identifier, aspects, SpriteType.PORTRAIT);
if (sprite == null) {
val model = repository.getPoser(identifier, state)
val model = repository.getPoser(identifier, aspects)
state.currentAspects = aspects
state.currentModel = model
val texture = repository.getTexture(identifier, state)
val texture = repository.getTexture(identifier, aspects, state.animationSeconds)
val context = RenderContext()
model.context = context
repository.getTextureNoSubstitute(identifier, state).let { context.put(RenderContext.TEXTURE, it) }
repository.getTextureNoSubstitute(identifier, aspects, 0f).let { context.put(RenderContext.TEXTURE, it) }
context.put(RenderContext.SCALE, contextScale)
context.put(RenderContext.SPECIES, identifier)
context.put(RenderContext.ASPECTS, state.currentAspects)
context.put(RenderContext.ASPECTS, aspects)
context.put(RenderContext.POSABLE_STATE, state)
val renderType = RenderType.entityCutout(texture)
@@ -272,7 +274,7 @@ fun drawPosablePortrait(
val packedLight = LightTexture.pack(11, 7)
val colour = toHex(r, g, b, a)
model.withLayerContext(immediate, state, repository.getLayers(identifier, state)) {
model.withLayerContext(immediate, state, repository.getLayers(identifier, aspects)) {
model.render(context, matrixStack, buffer, packedLight, OverlayTexture.NO_OVERLAY, colour)
immediate.endBatch()
}
@@ -290,6 +292,7 @@ fun drawPosablePortrait(
fun drawProfile(
repository: VaryingModelRepository<*>,
resourceIdentifier: ResourceLocation,
aspects: Set<String>,
matrixStack: PoseStack,
state: PosableState,
partialTicks: Float,
@@ -298,20 +301,22 @@ fun drawProfile(
RenderSystem.applyModelViewMatrix()
matrixStack.scale(scale, scale, -scale)
val sprite = repository.getSprite(resourceIdentifier, state, SpriteType.PROFILE)
val sprite = repository.getSprite(resourceIdentifier, aspects, SpriteType.PROFILE);
if (sprite == null) {
if(sprite == null) {
val model = repository.getPoser(resourceIdentifier, state)
val texture = repository.getTexture(resourceIdentifier, state)
val model = repository.getPoser(resourceIdentifier, aspects)
val texture = repository.getTexture(resourceIdentifier, aspects, state.animationSeconds)
val context = RenderContext()
model.context = context
repository.getTextureNoSubstitute(resourceIdentifier, state).let { context.put(RenderContext.TEXTURE, it) }
repository.getTextureNoSubstitute(resourceIdentifier, aspects, 0f)
.let { context.put(RenderContext.TEXTURE, it) }
context.put(RenderContext.SCALE, 1F)
context.put(RenderContext.SPECIES, resourceIdentifier)
context.put(RenderContext.ASPECTS, state.currentAspects)
context.put(RenderContext.ASPECTS, aspects)
context.put(RenderContext.POSABLE_STATE, state)
state.currentAspects = aspects
state.currentModel = model
val renderType = RenderType.entityCutout(texture)//model.getLayer(texture)
@@ -337,7 +342,7 @@ fun drawProfile(
RenderSystem.setShaderLights(light1, light2)
val packedLight = LightTexture.pack(11, 7)
model.withLayerContext(bufferSource, state, repository.getLayers(resourceIdentifier, state)) {
model.withLayerContext(bufferSource, state, repository.getLayers(resourceIdentifier, aspects)) {
model.render(context, matrixStack, buffer, packedLight, OverlayTexture.NO_OVERLAY, -0x1)
bufferSource.endBatch()
}
@@ -23,7 +23,7 @@ import com.mojang.serialization.Codec
interface ExpressionLike {
override fun toString(): String
/** Produces a [MoValue] for a [MoLangRuntime] to supply an environment. */
fun resolve(runtime: MoLangRuntime, context: Map<String, MoValue> = runtime.environment.context?.map ?: hashMapOf()): MoValue
fun resolve(runtime: MoLangRuntime, context: Map<String, MoValue> = emptyMap()): MoValue
fun getString(): String = toString()
fun resolveDouble(runtime: MoLangRuntime) = resolve(runtime).asDouble()
@@ -18,25 +18,23 @@ 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.Cobblemon
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.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.scripting.CobblemonScripts
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunctions
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.effectiveName
import com.cobblemon.mod.common.util.getBooleanOrNull
import com.cobblemon.mod.common.util.getDoubleOrNull
import com.cobblemon.mod.common.util.isInt
import com.cobblemon.mod.common.util.itemRegistry
import com.cobblemon.mod.common.util.worldRegistry
import com.mojang.datafixers.util.Either
import java.util.UUID
import net.minecraft.commands.arguments.EntityAnchorArgument
import net.minecraft.client.gui.components.tabs.Tab
import net.minecraft.core.Holder
import net.minecraft.core.Registry
import net.minecraft.core.registries.Registries
@@ -46,9 +44,10 @@ import net.minecraft.nbt.ListTag
import net.minecraft.nbt.StringTag
import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.commands.TagCommand
import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.TagKey
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.damagesource.DamageTypes
@@ -58,8 +57,6 @@ import net.minecraft.world.level.Level
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.dimension.DimensionType
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
/**
* Holds a bunch of useful MoLang trickery that can be used or extended in API
@@ -104,12 +101,6 @@ object MoLangFunctions {
runtime.environment.context = params.environment.context
val script = params.getString(0).asIdentifierDefaultingNamespace()
CobblemonScripts.run(script, runtime) ?: DoubleValue(0)
},
"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
}
)
val biomeFunctions = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
@@ -125,11 +116,10 @@ object MoLangFunctions {
map.put("save_data") { _ -> if (player is ServerPlayer) Cobblemon.molangData.save(player.uuid) else DoubleValue(0) }
map.put("main_held_item") { _ -> player.level().itemRegistry.wrapAsHolder(player.mainHandItem.item).asMoLangValue(Registries.ITEM) }
map.put("off_held_item") { _ -> player.level().itemRegistry.wrapAsHolder(player.offhandItem.item).asMoLangValue(Registries.ITEM) }
map.put("face") { params -> ObjectValue(PlayerDialogueFaceProvider(player.uuid, params.getBooleanOrNull(0) != false)) }
map.put("face") { _ -> ObjectValue(PlayerDialogueFaceProvider(player.uuid)) }
map.put("swing_hand") { _ -> player.swing(player.usedItemHand) }
map.put("food_level") { _ -> DoubleValue(player.foodData.foodLevel) }
map.put("saturation_level") { _ -> DoubleValue(player.foodData.saturationLevel) }
// map.put("has_permission") { params -> DoubleValue(Cobblemon.permissionValidator.hasPermission(player, Permission)) }
map.put("tell") { params ->
val message = params.getString(0).text()
val overlay = params.getBooleanOrNull(1) ?: false
@@ -151,35 +141,12 @@ object MoLangFunctions {
environment.query = player.asMoLangValue()
environment
}
if (player is ServerPlayer) {
map.put("is_party_at_full_health") { _ ->
DoubleValue(player.party().none(Pokemon::canBeHealed)) }
map.put("can_heal_at_healer") { params ->
val pos = params.get<ArrayStruct>(0).asBlockPos()
val healer = player.level().getBlockEntity(pos, CobblemonBlockEntities.HEALING_MACHINE).orElse(null) ?: return@put DoubleValue.ZERO
val party = player.party()
return@put DoubleValue(healer.canHeal(party))
}
map.put("put_pokemon_in_healer") { params ->
val healer = player.level()
.getBlockEntity(params.get<ArrayStruct>(0).asBlockPos(), CobblemonBlockEntities.HEALING_MACHINE)
.orElse(null) ?: return@put DoubleValue.ZERO
val party = player.party()
if (healer.canHeal(party)) {
healer.activate(player.uuid, party)
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
}
}
map
}
)
val entityFunctions = 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()) }
map.put("damage") { params ->
val amount = params.getDouble(0)
val source = DamageSource(entity.level().registryAccess().registryOrThrow(Registries.DAMAGE_TYPE).getHolder(DamageTypes.GENERIC).get())
@@ -211,34 +178,15 @@ object MoLangFunctions {
map.put("is_on_ground") { _ -> DoubleValue(entity.onGround()) }
map.put("world") { _ -> entity.level().worldRegistry.wrapAsHolder(entity.level()).asWorldMoLangValue() }
map.put("biome") { _ -> entity.level().getBiome(entity.blockPosition()).asBiomeMoLangValue() }
map.put("is_healer_in_use") { params ->
val pos = params.get<ArrayStruct>(0).asBlockPos()
val healer = entity.level().getBlockEntity(pos, CobblemonBlockEntities.HEALING_MACHINE).orElse(null) ?: return@put DoubleValue.ONE
return@put DoubleValue(healer.isInUse)
}
map.put("find_nearby_block") { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace(namespace = "minecraft")
val isTag = type.path.startsWith("#")
val range = params.getDoubleOrNull(1) ?: 10
val blockPos = entity.level().getBlockStatesWithPos(AABB.ofSize(entity.position(), range.toDouble(), range.toDouble(), range.toDouble()))
.filter { it.first.blockHolder.let { if (isTag) it.`is`(TagKey.create(Registries.BLOCK, type)) else it.`is`(type) } }
.minByOrNull { it.second.distSqr(entity.blockPosition()) }
?.second
if (blockPos != null) {
return@put ArrayStruct(mapOf("0" to DoubleValue(blockPos.x), "1" to DoubleValue(blockPos.y), "2" to DoubleValue(blockPos.z)))
} else {
return@put DoubleValue.ZERO
}
}
map
}
)
val npcFunctions = mutableListOf<(NPCEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ npc ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("class") { StringValue(npc.npc.id.toString()) }
map.put("class") { StringValue(npc.npc.resourceIdentifier.toString()) }
map.put("name") { StringValue(npc.name.string) }
map.put("face") { params -> ObjectValue(ReferenceDialogueFaceProvider(npc.id, params.getBooleanOrNull(0) != false)) }
map.put("face") { ObjectValue(ReferenceDialogueFaceProvider(npc.id)) }
map.put("in_battle") { DoubleValue(npc.isInBattle()) }
map.put("run_script_on_client") { params ->
val world = npc.level()
@@ -251,50 +199,13 @@ object MoLangFunctions {
}
map.put("run_script") { params ->
val script = params.getString(0).asIdentifierDefaultingNamespace()
val environment = params.environment
val runtime = MoLangRuntime()
runtime.environment.cloneFrom(params.environment)
runtime.environment.query = environment.query
runtime.environment.variable = environment.variable
runtime.environment.context = environment.context
CobblemonScripts.run(script, runtime) ?: DoubleValue(0)
}
map.put("run_action_effect") { params ->
val runtime = MoLangRuntime().setup()
runtime.environment.cloneFrom(params.environment)
runtime.withNPCValue(value = npc)
val actionEffect = ActionEffects.actionEffects[params.getString(0).asIdentifierDefaultingNamespace()]
if (actionEffect != null) {
val context = ActionEffectContext(
actionEffect = actionEffect,
providers = mutableListOf(NPCProvider(npc)),
runtime = runtime
)
npc.actionEffect = context
npc.brain.setMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT, context)
npc.brain.setActiveActivityIfPossible(CobblemonActivities.NPC_ACTION_EFFECT)
actionEffect.run(context).thenRun {
val npcActionEffect = npc.brain.getMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT).orElse(null)
if (npcActionEffect == context && npc.brain.isActive(CobblemonActivities.NPC_ACTION_EFFECT)) {
npc.brain.eraseMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT)
npc.actionEffect = null
}
}
return@put DoubleValue(1)
}
return@put DoubleValue(0)
}
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
val party = npc.staticParty ?: return@put DoubleValue.ZERO
if (healer.canHeal(party)) {
healer.activate(npc.uuid, party)
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
}
map.put("look_at_position") { params ->
val pos = Vec3(params.getDouble(0), params.getDouble(1), params.getDouble(2))
npc.lookAt(EntityAnchorArgument.Anchor.EYES, pos)
}
map.put("environment") { _ -> npc.runtime.environment }
map
}
@@ -309,11 +220,6 @@ object MoLangFunctions {
map.put("is_pvw") { DoubleValue(battle.isPvW) }
map.put("battle_type") { StringValue(battle.format.toString()) }
map.put("environment") { battle.runtime.environment }
map.put("get_actor") { params ->
val uuid = UUID.fromString(params.getString(0))
val actor = battle.actors.find { it.uuid == uuid } ?: return@put DoubleValue.ZERO
return@put actor.struct
}
map
}
)
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.molang
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
/**
@@ -21,7 +20,7 @@ import com.bedrockk.molang.runtime.value.MoValue
class ObjectValue<T>(
var obj: T,
val stringify: (T) -> String = { it.toString() },
val doublify: (T) -> Double = { DoubleValue.ONE.value }
val doublify: (T) -> Double = { throw NotImplementedError("Doublify not implemented for this object value, are you doing weird molang") }
) : QueryStruct(hashMapOf()) {
override fun value() = this
override fun asDouble() = doublify(obj)
@@ -118,8 +118,6 @@ open class Move(
return json
}
fun copy() = loadFromJSON(saveToJSON(JsonObject()))
fun saveToBuffer(buffer: RegistryFriendlyByteBuf) {
buffer.writeString(name)
buffer.writeSizedInt(IntSize.U_BYTE, currentPp)
@@ -54,7 +54,7 @@ class MoveSet : Iterable<Move> {
fun copyFrom(other: MoveSet) {
doWithoutEmitting {
clear()
other.getMoves().forEach { add(it.copy()) }
other.getMoves().forEach { add(it) }
}
update()
}
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.moves.animations
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.animations.keyframes.ActionEffectKeyframe
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.resolveBoolean
import net.minecraft.world.entity.Entity
@@ -39,7 +38,7 @@ class ActionEffectTimeline(
CompletableFuture.completedFuture(Unit)
} else {
val finalFuture = CompletableFuture<Unit>()
// .toList copy because I'm paranoid about iterators trying to share between identical effects playing
// .toList copy because I'm paranoid about iterators being trying to share between identical effects playing
chainKeyframes(context, timeline.toList().iterator(), finalFuture)
finalFuture
}.exceptionallyCompose {
@@ -72,6 +71,8 @@ class ActionEffectContext(
var currentKeyframes: MutableList<ActionEffectKeyframe> = mutableListOf()
) {
inline fun <reified T> findOneProvider() = providers.filterIsInstance<T>().firstOrNull()
}
class UsersProvider(users: List<Entity>): EntityProvider {
@@ -84,10 +85,6 @@ class TargetsProvider(targets: List<Entity>): EntityProvider {
constructor(vararg targets: Entity): this(targets.toList())
}
class NPCProvider(npc: NPCEntity): EntityProvider {
override val entities = listOf(npc)
}
interface EntityProvider {
val entities: List<Entity>
}
@@ -31,7 +31,7 @@ class AnimationActionEffectKeyframe : ConditionalActionEffectKeyframe(), EntityC
.filterIsInstance<EntityProvider>()
.flatMap { prov -> prov.entities.filter { test(context, it, isUser = prov is UsersProvider) } }
val expressions = variables.map { it.originalString }
val expressions = variables.map { it.originalString }.toSet()
// Treat them as expressions if possible but otherwise yeah just send them as strings
val animation = animation.map {
@@ -13,7 +13,6 @@ import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.entity.PosableEntity
import net.minecraft.world.entity.Entity
/**
@@ -25,16 +24,12 @@ import net.minecraft.world.entity.Entity
interface EntityConditionalActionEffectKeyframe {
val entityCondition: ExpressionLike
fun test(context: ActionEffectContext, entity: Entity, isUser: Boolean): Boolean {
context.runtime.environment.query
.addFunction("entity") {
if (entity is PosableEntity) {
entity.struct
} else {
QueryStruct(hashMapOf())
}.addFunction("is_user") { DoubleValue(isUser) } // Deprecated tbh
}
.addFunction("is_user") { DoubleValue(isUser) }
// TODO this should use the entity's own query struct
context.runtime.environment.query.addFunction("entity") {
QueryStruct(hashMapOf())
.addFunction("uuid") { StringValue(entity.stringUUID) }
.addFunction("is_user") { DoubleValue(isUser) }
}
return entityCondition.resolveBoolean(context.runtime)
}
}
@@ -45,7 +45,7 @@ class EntityParticlesActionEffectKeyframe : ConditionalActionEffectKeyframe(), E
}?.asIdentifierDefaultingNamespace() ?: return skip()
entities.filter { it is PosableEntity }.forEach { entity ->
val packet = SpawnSnowstormEntityParticlePacket(effectIdentifier, entity.id, listOf(locator))
val packet = SpawnSnowstormEntityParticlePacket(effectIdentifier, entity.id, locator)
val players = (entity.level() as ServerLevel).getPlayers { it.distanceTo(entity) <= visibilityRange }
packet.sendToPlayers(players)
}
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.api.moves.animations.keyframes
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.scheduling.delayedFuture
@@ -56,12 +56,16 @@ abstract class MultiblockBlock(properties: Properties) : BaseEntityBlock(propert
return super.useWithoutItem(state, world, pos, player, hit)
}
override fun onRemove(state: BlockState, level: Level, pos: BlockPos, newState: BlockState, movedByPiston: Boolean) {
val entity = level.getBlockEntity(pos)
if (entity is MultiblockEntity && entity.multiblockStructure != null && state.block != newState.block) {
entity.multiblockStructure!!.playerWillDestroy(level, pos, state, null)
override fun playerWillDestroy(world: Level, pos: BlockPos, state: BlockState, player: Player): BlockState {
val result = super.playerWillDestroy(world, pos, state, player)
if (!world.isClientSide) {
val entity = world.getBlockEntity(pos)
if (entity is MultiblockEntity && entity.multiblockStructure != null) {
entity.multiblockStructure!!.playerWillDestroy(world, pos, state, player)
}
entity?.setRemoved()
}
super.onRemove(state, level, pos, newState, movedByPiston)
return result
}
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity? {
@@ -59,8 +59,6 @@ abstract class MultiblockEntity(
}
}
override fun isValidBlockState(blockState: BlockState) = blockState.block is MultiblockBlock
abstract override fun loadAdditional(nbt: CompoundTag, registryLookup: HolderLookup.Provider)
}
@@ -1,39 +0,0 @@
/*
* 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.net.serializers
import com.cobblemon.mod.common.entity.npc.NPCPlayerModelType
import com.cobblemon.mod.common.entity.npc.NPCPlayerTexture
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readEnumConstant
import com.cobblemon.mod.common.util.writeEnumConstant
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.codec.StreamCodec
import net.minecraft.network.syncher.EntityDataSerializer
object NPCPlayerTextureSerializer : EntityDataSerializer<NPCPlayerTexture> {
val ID = cobblemonResource("npcplayertexture")
fun write(buffer: RegistryFriendlyByteBuf, texture: NPCPlayerTexture) {
buffer.writeEnumConstant(texture.model)
if (texture.model == NPCPlayerModelType.NONE) {
return
}
buffer.writeByteArray(texture.texture)
}
fun read(buffer: RegistryFriendlyByteBuf): NPCPlayerTexture {
val model = buffer.readEnumConstant(NPCPlayerModelType::class.java)
val texture = if (model == NPCPlayerModelType.NONE) ByteArray(1) else buffer.readByteArray()
return NPCPlayerTexture(texture, model)
}
override fun codec() = StreamCodec.of(::write, ::read)
override fun copy(texture: NPCPlayerTexture) = NPCPlayerTexture(texture.texture, texture.model)
}
@@ -11,15 +11,13 @@ package com.cobblemon.mod.common.api.npc
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.ExpressionLike
import com.cobblemon.mod.common.api.npc.configuration.NPCBattleConfiguration
import com.cobblemon.mod.common.api.npc.configuration.NPCConfigVariable
import com.cobblemon.mod.common.api.npc.configuration.NPCInteractConfiguration
import com.cobblemon.mod.common.api.npc.variation.NPCVariationProvider
import com.cobblemon.mod.common.api.npc.variation.RandomNPCVariationProvider
import com.cobblemon.mod.common.net.IntSize
import com.cobblemon.mod.common.util.*
import com.mojang.datafixers.util.Either
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.EntityDimensions
@@ -32,49 +30,24 @@ import net.minecraft.world.entity.EntityDimensions
*/
class NPCClass {
@Transient
lateinit var id: ResourceLocation
lateinit var resourceIdentifier: ResourceLocation
var resourceIdentifier: ResourceLocation = cobblemonResource("dummy")
var names: MutableList<Component> = mutableListOf()
var aspects: MutableSet<String> = mutableSetOf() // These only make sense when applied via presets
var names = mutableListOf(lang("npc.name.default"))
var hitbox = EntityDimensions.scalable(0.6F, 1.8F)
var battleConfiguration = NPCBattleConfiguration()
var aiScripts: MutableList<ResourceLocation> = mutableListOf()
var interaction: NPCInteractConfiguration? = null
var variations: MutableMap<String, NPCVariationProvider> = mutableMapOf()
var config: MutableList<NPCConfigVariable> = mutableListOf()
var variables = mutableMapOf<String, MoValue>() // Questionable whether this should be here.
var party: NPCPartyProvider? = null
var skill: Int = 0
// If you're adding stuff here, add it to NPCPreset and NPCClassAdapter too
var variables = mutableMapOf<String, MoValue>()
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeString(resourceIdentifier.toString())
buffer.writeCollection(names) { _, v -> buffer.writeText(v) }
buffer.writeFloat(this.hitbox.width)
buffer.writeFloat(this.hitbox.height)
buffer.writeBoolean(this.hitbox.fixed)
battleConfiguration.encode(buffer)
buffer.writeCollection(aiScripts) { _, v -> buffer.writeString(v.toString()) }
buffer.writeNullable(interaction) { _, value ->
buffer.writeString(value.type)
value.encode(buffer)
}
buffer.writeSizedInt(IntSize.U_BYTE, variations.size)
for ((key, value) in variations) {
buffer.writeString(key)
val aspects = value.aspects
buffer.writeCollection(aspects) { _, v -> buffer.writeString(v) }
}
buffer.writeCollection(config) { _, v ->
buffer.writeString(v.variableName)
buffer.writeText(v.displayName)
buffer.writeText(v.description)
buffer.writeEnumConstant(v.type)
buffer.writeString(v.defaultValue)
}
buffer.writeInt(skill)
buffer.writeMapK(size = IntSize.U_BYTE, map = variables) { (key, value) ->
buffer.writeString(key)
buffer.writeString(value.asString())
@@ -82,7 +55,6 @@ class NPCClass {
}
fun decode(buffer: RegistryFriendlyByteBuf) {
resourceIdentifier = ResourceLocation.parse(buffer.readString().toString())
names = buffer.readList { buffer.readText().copy() }.toMutableList()
val length = buffer.readFloat()
val width = buffer.readFloat()
@@ -90,7 +62,6 @@ class NPCClass {
hitbox = if (fixed) EntityDimensions.fixed(length, width) else EntityDimensions.scalable(length, width)
battleConfiguration = NPCBattleConfiguration()
battleConfiguration.decode(buffer)
aiScripts = buffer.readList { ResourceLocation.parse(buffer.readString()) }.toMutableList()
interaction = buffer.readNullable {
val type = buffer.readString()
val configType = NPCInteractConfiguration.types[type] ?: return@readNullable null
@@ -98,22 +69,6 @@ class NPCClass {
instance.decode(buffer)
instance
}
val variationSize = buffer.readSizedInt(IntSize.U_BYTE)
for (i in 0 until variationSize) {
val key = buffer.readString()
val aspects = buffer.readList { buffer.readString() }.toSet()
val provider = RandomNPCVariationProvider(aspects)
variations[key] = provider
}
config = buffer.readList {
val variableName = buffer.readString()
val displayName = buffer.readText()
val description = buffer.readText()
val type = buffer.readEnumConstant(NPCConfigVariable.NPCVariableType::class.java)
val defaultValue = buffer.readString()
NPCConfigVariable(variableName, displayName, description, type, defaultValue)
}.toMutableList()
skill = buffer.readInt()
buffer.readMapK(size = IntSize.U_BYTE, map = variables) {
val key = buffer.readString()
val value = buffer.readString()
@@ -18,8 +18,6 @@ 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.npc.configuration.NPCInteractConfiguration
import com.cobblemon.mod.common.api.npc.variation.NPCVariationProvider
import com.cobblemon.mod.common.api.npc.variation.WeightedAspect
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.TimeRange
@@ -32,7 +30,6 @@ import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import com.mojang.datafixers.util.Either
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
@@ -60,12 +57,7 @@ object NPCClasses : JsonDataRegistry<NPCClass> {
.registerTypeAdapter(CompoundTag::class.java, NbtCompoundAdapter)
.registerTypeAdapter(NPCPartyProvider::class.java, NPCPartyProviderAdapter)
.registerTypeAdapter(NPCInteractConfiguration::class.java, NPCInteractConfigurationAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(NPCVariationProvider::class.java, NPCVariationProviderAdapter)
.registerTypeAdapter(MoValue::class.java, MoValueAdapter)
.registerTypeAdapter(NPCClass::class.java, NPCClassAdapter)
.registerTypeAdapter(Component::class.java, TextAdapter)
.registerTypeAdapter(WeightedAspect::class.java, WeightedAspectAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
@@ -103,7 +95,7 @@ object NPCClasses : JsonDataRegistry<NPCClass> {
/**
* Finds an [NPCClass] by its unique [ResourceLocation].
*
* @param identifier The unique [NPCClass.id] of the [NPCClass].
* @param identifier The unique [NPCClass.resourceIdentifier] of the [NPCClass].
* @return The [NPCClass] if existing.
*/
fun getByIdentifier(identifier: ResourceLocation) = this.npcClassesByIdentifier[identifier]
@@ -124,20 +116,10 @@ object NPCClasses : JsonDataRegistry<NPCClass> {
*/
fun random(): NPCClass = this.npcClassesByIdentifier.values.random()
fun dummy(): NPCClass {
val dummy = NPCClass()
dummy.id = cobblemonResource("dummy")
return dummy
}
override fun reload(data: Map<ResourceLocation, NPCClass>) {
this.npcClassesByIdentifier.clear()
data.forEach { (identifier, species) ->
species.id = identifier
// shortcut so they don't have to state the resource identifier if they don't wanna
if (species.resourceIdentifier.path == "dummy") {
species.resourceIdentifier = identifier
}
species.resourceIdentifier = identifier
this.npcClassesByIdentifier[identifier] = species
}
}
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.api.npc
import com.cobblemon.mod.common.api.npc.partyproviders.DynamicPartyProvider
import com.cobblemon.mod.common.api.npc.partyproviders.NPCParty
import com.cobblemon.mod.common.api.npc.partyproviders.SimplePartyProvider
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.entity.npc.NPCEntity
@@ -28,12 +26,16 @@ import net.minecraft.server.level.ServerPlayer
interface NPCPartyProvider {
companion object {
val types = mutableMapOf<String, (String) -> NPCPartyProvider>(
SimplePartyProvider.TYPE to { SimplePartyProvider() },
DynamicPartyProvider.TYPE to { DynamicPartyProvider() }
SimplePartyProvider.TYPE to { SimplePartyProvider() }
)
}
val type: String
fun provide(npc: NPCEntity, level: Int): NPCParty
fun provide(npc: NPCEntity, challengers: List<ServerPlayer>): PartyStore
fun encode(buffer: RegistryFriendlyByteBuf)
fun decode(buffer: RegistryFriendlyByteBuf)
fun saveToNBT(nbt: CompoundTag)
fun loadFromNBT(nbt: CompoundTag)
fun loadFromJSON(json: JsonElement)
}
@@ -8,40 +8,11 @@
package com.cobblemon.mod.common.api.npc
import com.cobblemon.mod.common.api.npc.configuration.NPCConfigVariable
import com.cobblemon.mod.common.api.npc.configuration.NPCInteractConfiguration
import com.cobblemon.mod.common.api.npc.variation.NPCVariationProvider
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.EntityDimensions
class NPCPreset {
lateinit var id: ResourceLocation
var resourceIdentifier: ResourceLocation? = null
var aspects: Set<String>? = null
var variations: Map<String, NPCVariationProvider>? = null
var config: List<NPCConfigVariable>? = null
var party: NPCPartyProvider? = null
var aiScripts: List<ResourceLocation>? = null
var interaction: NPCInteractConfiguration? = null
var names: MutableSet<Component>? = null
var hitbox: EntityDimensions? = null
var skill: Int? = null
fun applyTo(npcClass: NPCClass) {
resourceIdentifier?.let { npcClass.resourceIdentifier = it }
aspects?.let { npcClass.aspects.addAll(it) }
config?.forEach {
val variableName = it.variableName
npcClass.config.removeIf { it.variableName == variableName }
npcClass.config.add(it)
}
variations?.entries?.forEach { (key, value) -> npcClass.variations[key] = value }
party?.let { npcClass.party = it }
aiScripts?.let { npcClass.aiScripts.addAll(it) }
interaction?.let { npcClass.interaction = it }
names?.let { npcClass.names.addAll(it) }
hitbox?.let { npcClass.hitbox = it }
skill?.let { npcClass.skill = it }
}
// var mergeMode = MergeMode.KEEP
}
@@ -1,91 +0,0 @@
/*
* 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
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.ai.SleepDepth
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.drop.DropEntry
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.npc.configuration.NPCInteractConfiguration
import com.cobblemon.mod.common.api.npc.variation.NPCVariationProvider
import com.cobblemon.mod.common.api.npc.variation.WeightedAspect
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.util.adapters.*
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import com.mojang.datafixers.util.Either
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.world.entity.EntityDimensions
import net.minecraft.world.item.Item
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.block.Block
import net.minecraft.world.phys.AABB
object NPCPresets : JsonDataRegistry<NPCPreset> {
override val id = cobblemonResource("npc_preset")
override val type = PackType.SERVER_DATA
override val gson: Gson = GsonBuilder()
.registerTypeAdapter(EntityDimensions::class.java, EntityDimensionsAdapter)
.registerTypeAdapter(AABB::class.java, BoxAdapter)
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(TimeRange::class.java, IntRangesAdapter(TimeRange.timeRanges) { TimeRange(*it) })
.registerTypeAdapter(ItemDropMethod::class.java, ItemDropMethod.adapter)
.registerTypeAdapter(SleepDepth::class.java, SleepDepth.adapter)
.registerTypeAdapter(DropEntry::class.java, DropEntryAdapter)
.registerTypeAdapter(CompoundTag::class.java, NbtCompoundAdapter)
.registerTypeAdapter(NPCPartyProvider::class.java, NPCPartyProviderAdapter)
.registerTypeAdapter(NPCInteractConfiguration::class.java, NPCInteractConfigurationAdapter)
.registerTypeAdapter(WeightedAspect::class.java, WeightedAspectAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(NPCVariationProvider::class.java, NPCVariationProviderAdapter)
.registerTypeAdapter(MoValue::class.java, MoValueAdapter)
.registerTypeAdapter(Component::class.java, TextAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(Either::class.java, ResourceLocation::class.java, ExpressionLike::class.java).type, NPCScriptAdapter)
.disableHtmlEscaping()
.enableComplexMapKeySerialization()
.create()
override val typeToken: TypeToken<NPCPreset> = TypeToken.get(NPCPreset::class.java)
override val resourcePath = "npc_presets"
override val observable = SimpleObservable<NPCPresets>()
private val npcPresetsByIdentifier = mutableMapOf<ResourceLocation, NPCPreset>()
override fun sync(player: ServerPlayer) {
// TODO probably do want to sync the presets
}
override fun reload(data: Map<ResourceLocation, NPCPreset>) {
npcPresetsByIdentifier.clear()
npcPresetsByIdentifier.putAll(data)
observable.emit(this)
}
fun getPreset(identifier: ResourceLocation): NPCPreset? {
return npcPresetsByIdentifier[identifier]
}
}
@@ -1,273 +0,0 @@
# Cobbled NPCs
NPC's are AI-driven entities that are built on the same identifier-aspects rendering platform
as Cobblemon's Pokémon entities. Every NPC in the world is based on an [NPC class](#npc-classes).
## NPC Classes
An NPC class is a datapacked JSON (inside the <code>npcs</code> folder) that defines many properties of an NPC in a way that's reusable
across any number of individual NPC entities. The possible properties are explained in [NPC Properties](#npc-properties)
## NPC Class Presets
For many properties or sets of properties of an NPC class, there may be logical grouping of settings.
As an example, many types of trainer NPC classes could share the logic of challenging nearby players
that they are able to see. This could become tedious if the same AI script and range config needed to
be defined on every NPC class. Not only is it tedious, but if there was some tweak that was made in the
underlying logic of that AI, it would need to be updated in every single NPC class JSON that uses it.
The solution to this is NPC Class Presets, a datapack folder (<code>npc_presets</code>) with JSON files that have an almost identical
format to [NPC Classes](#npc-classes), but with every property being nullable. An NPC class can reference
any number of presets and the properties of those presets will be merged into the NPC class as it loads.
## NPC Properties
{
"hitbox": "player",
"resourceIdentifier": "cobblemon:bug_catcher",
"presets": [
"cobblemon:npc-standard"
],
"variations": {
"dirt": [
"clean",
"slightly_dirty",
"dirty",
"filthy"
],
"net": {
"type": "weighted",
"options": [
{
"aspect": "green-net",
"weight": 10
},
{
"aspect": "blue-net",
"weight": 5
},
{
"aspect": "red-net",
"weight": 1
}
]
}
},
"config": [
{
"variableName": "challenge_distance",
"displayName": "cobblemon.npc.challenge_distance",
"description": "cobblemon.npc.challenge_distance.description",
"type": "number",
"default": "5"
}
],
"names": [
"Vera",
"Hiro",
"John Cobblemon"
],
"interaction": {
"type": "dialogue",
"dialogue": "cobblemon:npc-example"
},
"battleConfiguration": {
"canChallenge": true
},
"skill": 5,
"party": {
"type": "simple",
"pokemon": [
"spiritomb level=1 moves=splash,firepunch,flamethrower,swift",
"porygonz level=1 speed_ev=252",
"togekiss level=1 nature=timid",
"lucario level=1 held_item=cobblemon:focus_sash",
"milotic level=1 ability=marvelscale ",
"garchomp level=1 hp_iv=31 attack_iv=31"
]
}
}
### hitbox
The hitbox of the NPC, relevant to combat and pathing. This can be defined as an object
or as a shortcut string that is predefined in the code. The only currently hardcoded
hitbox name is "player", which is the standard Steve Minecraft hitbox and is the same as
this fully defined hitbox:
"hitbox": {
"width": 0.6,
"height": 1.8
}
### resourceIdentifier
An optional property representing the 'identifier' that will be used for rendering the NPC.
In a way, this is the same as the "species" of a Pokémon. It is this value combined with
whatever aspects have been defined for the NPC that completely handles the appearance of the
NPC. This identifier is referenced in the variation JSONs on the client.
If left blank, it will fallback to the identifier used for the NPC class itself. The reason why
this is useful is that it allows several NPC classes to appear the same despite having differences
in AI, parties, interactions, etc.
### presets
A list of strings representing the identifiers of [NPC Class Presets](#npc-class-presets) that form the foundation of
this NPC class. Any property defined in this class will override the same property in the presets.
### variations
A map of variations that generate the aspects that control the appearance of the NPC. The keys of this
map should represent the kind of variation that is being defined, and the values are some kind of [NPC Variation](#npc-variation).
Variations can be defined in presets and flow through to the NPC class, but they can also be defined
in the NPC class. If the same name is used in both a preset and an NPC class, that which is on the NPC
class will override that from the preset. If the variation names are different, they will be merged together.
#### NPC Variation
An NPC variation is some way in which aspects can be generated for the NPC. The types of variation can
be added using the API, but the type most anticipated is the weighted choice aspects.
This can be defined as a list of strings, in which case the aspect will be chosen randomly from the list
with uniform distribution.
"dirt": [
"clean",
"slightly_dirty",
"dirty",
"filthy"
]
The weighted variation can also be defined more fully, which is necessary if you want some options to be
more likely than others.
"net": {
"type": "weighted",
"options": [
{
"aspect": "green-net",
"weight": 10
},
{
"aspect": "blue-net",
"weight": 5
},
{
"aspect": "red-net",
"weight": 1
}
]
}
In this example, the "net" value will be "green-net" most of the time, 10 times out of 16. The "blue-net"
aspect will be chosen 5 times out of 16, and the "red-net" aspect will be chosen 1 time out of 16.
When using a type of NPC variation that is not "weighted", the value of "type" must specify it and the
other values (instead of "options") will be in whatever format is dictated by that type.
### config
A list of NPC config values that are predefined as being necessary for this class. An NPC config
variable's value is used in the MoLang environment of the NPC and can be accessed in any MoLang
environment that has the NPC.
This class property is a way to present clear options to the user if configuring an NPC in-game using
the NPC Edit command. This does not mean that NPC variables can only be created when they were defined
in the class, but it makes it easier for the user to know what variables should be set and will set
the initial values when an NPC is initialized.
An example situation of using this is if there is an NPC class preset that defines an AI script for
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.
"config": [
{
"variableName": "challenge_distance",
"displayName": "cobblemon.npc.challenge_distance",
"description": "cobblemon.npc.challenge_distance.description",
"type": "number",
"default": "5"
}
]
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
A list of strings that represent the names that the NPC can have. The name of the NPC is chosen randomly
when initialized. These can either be simple names or translation keys so provide localization support.
### interaction
An object that defines how the NPC will handle being interacted with by players. The type of interaction
is defined by the "type" property, and different types of interaction have different properties.
#### dialogue
The "dialogue" interaction type is a simple way to have an NPC say something when interacted with. The
"dialogue" property should be the identifier of a datapacked dialogue JSON file. The dialogue that is
created will have `c.npc` as a contextual property in any script that occurs as part of that dialogue,
allowing the dialogue to work with the NPC (including the NPC config values, such as
`c.npc.config.challenge_distance`).
"interaction": {
"type": "dialogue",
"dialogue": "cobblemon:npc-example"
}
#### script
The "script" interaction type is a way to have an NPC run a predefined script when interacted with. The
script field is an identifier of a datapacked MoLang script file (in the `scripts` folder).
"interaction": {
"type": "script",
"script": "cobblemon:npc-example"
}
#### custom_script
The "custom_script" interaction type is a way to have an NPC run the MoLang script defined specifically
in this file. The script property can be a single string or an array of strings to do multiline MoLang scripts.
"interaction": {
"type": "custom_script",
"script": "c.player.tell('My name is ' + c.npc.name);"
}
### battleConfiguration
I don't really know whether this is going to stay tbh
### skill
The skill level when this NPC is used in battle. This is a number from 1 to 5, with 5 being most difficult
to beat. This does not control their Pokémon's levels or moves, but it does control how well they use them.
### party
A party provider which generates the party of Pokémon that this NPC will have when in battle. The type of
party is defined by the "type", and different types of party have different properties. An NPC party is
intended to be initialized when an NPC is spawned and uses a 'seed level'. When spawned using the world
spawning system, it will use a seed level that is based on the player's party's average level. When
spawned using the NPC command, the seed level can be defined in the command and is otherwise set to 1.
A party provider produces either 'static' or 'dynamic' parties. A static party is one that is generated
when the NPC is initialized and will not change. A dynamic party is one that may be configured using the
seed level but can be different any time the NPC is challenged.
A dynamic party prevents the NPC from doing several actions, such as using healing machines, having
persistent health of their party that changes between battles, and keeping their Pokémon out of their
balls. This is because the NPC's party is not stored in the world.
#### simple
The "simple" party type is a way to define a party of Pokémon in a simple list format. Each string in the
list should be a set of Pokémon properties, as used in commands and spawn files.
This party provider type is considered 'static'. Any level that is not mentioned in the Pokémon properties
will be set to the seed level.
"party": {
"type": "simple",
"pokemon": [
"spiritomb moves=splash,firepunch,flamethrower,swift",
"porygonz speed_ev=252",
"togekiss nature=timid",
"lucario held_item=cobblemon:focus_sash",
"milotic ability=marvelscale ",
"garchomp level=100 hp_iv=31 attack_iv=31"
]
}
#### dynamic
To be implemented
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.api.npc.configuration
import com.cobblemon.mod.common.api.npc.NPCPartyProvider
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeString
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -23,50 +25,50 @@ class NPCBattleConfiguration {
buffer.writeBoolean(canChallenge)
buffer.writeBoolean(simultaneousBattles)
buffer.writeBoolean(healAfterwards)
// buffer.writeNullable(party) { _, provider ->
// buffer.writeString(provider.type)
// provider.encode(buffer)
// }
buffer.writeNullable(party) { _, provider ->
buffer.writeString(provider.type)
provider.encode(buffer)
}
}
fun decode(buffer: RegistryFriendlyByteBuf) {
canChallenge = buffer.readBoolean()
simultaneousBattles = buffer.readBoolean()
healAfterwards = buffer.readBoolean()
// party = buffer.readNullable {
// val type = buffer.readString()
// val providerBuilder = NPCPartyProvider.types[type]
// ?: throw IllegalArgumentException("Failed to load NPC party provider of type: $type")
//
// val decodedParty = providerBuilder(type)
// decodedParty.decode(buffer)
// decodedParty
// }
party = buffer.readNullable {
val type = buffer.readString()
val providerBuilder = NPCPartyProvider.types[type]
?: throw IllegalArgumentException("Failed to load NPC party provider of type: $type")
val decodedParty = providerBuilder(type)
decodedParty.decode(buffer)
decodedParty
}
}
fun saveToNBT(nbt: CompoundTag) {
nbt.putBoolean(DataKeys.NPC_CAN_CHALLENGE, canChallenge)
nbt.putBoolean(DataKeys.NPC_SIMULTANEOUS_BATTLES, simultaneousBattles)
nbt.putBoolean(DataKeys.NPC_HEAL_AFTERWARDS, healAfterwards)
// val party = party
// if (party != null) {
// val partyNBT = CompoundTag()
// partyNBT.putString(DataKeys.NPC_PARTY_TYPE, party.type)
// party.saveToNBT(partyNBT)
// nbt.put(DataKeys.NPC_PARTY, partyNBT)
// }
val party = party
if (party != null) {
val partyNBT = CompoundTag()
partyNBT.putString(DataKeys.NPC_PARTY_TYPE, party.type)
party.saveToNBT(partyNBT)
nbt.put(DataKeys.NPC_PARTY, partyNBT)
}
}
fun loadFromNBT(nbt: CompoundTag) {
canChallenge = nbt.getBoolean(DataKeys.NPC_CAN_CHALLENGE)
simultaneousBattles = nbt.getBoolean(DataKeys.NPC_SIMULTANEOUS_BATTLES)
healAfterwards = nbt.getBoolean(DataKeys.NPC_HEAL_AFTERWARDS)
// val partyNBT = nbt.getCompound(DataKeys.NPC_PARTY)
// if (!partyNBT.isEmpty) {
// val type = partyNBT.getString(DataKeys.NPC_PARTY_TYPE)
// val providerBuilder = NPCPartyProvider.types[type]
// ?: throw IllegalArgumentException("Failed to load NPC party provider of type: $type")
// party = providerBuilder(type).also { it.loadFromNBT(partyNBT) }
// }
val partyNBT = nbt.getCompound(DataKeys.NPC_PARTY)
if (!partyNBT.isEmpty) {
val type = partyNBT.getString(DataKeys.NPC_PARTY_TYPE)
val providerBuilder = NPCPartyProvider.types[type]
?: throw IllegalArgumentException("Failed to load NPC party provider of type: $type")
party = providerBuilder(type).also { it.loadFromNBT(partyNBT) }
}
}
}
@@ -1,51 +0,0 @@
/*
* 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.configuration
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.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asTranslated
import net.minecraft.network.chat.Component
/**
* A predefined variable that will be declared and filled in on any NPC that extends from the class or preset
* that specifies this variable. This gives the client a cleaner way to represent a variable that should exist
* on the NPC.
*
* @author Hiroku
* @since August 14th, 2023
*/
class NPCConfigVariable(
val variableName: String = "variable",
val displayName: Component = "Variable".asTranslated(),
val description: Component = "A variable that can be used in the NPC's configuration.".asTranslated(),
val type: NPCVariableType = NPCVariableType.NUMBER,
val defaultValue: String = "0",
) {
enum class NPCVariableType {
NUMBER,
STRING,
BOOLEAN
}
fun apply(npc: NPCEntity, value: MoValue) {
npc.config.setDirectly(variableName, value)
}
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)
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.npc.configuration.interaction
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.dialogue.ActiveDialogue
import com.cobblemon.mod.common.api.dialogue.DialogueManager
import com.cobblemon.mod.common.api.dialogue.Dialogues
@@ -32,24 +31,10 @@ class DialogueNPCInteractionConfiguration : NPCInteractConfiguration {
override fun interact(npc: NPCEntity, player: ServerPlayer): Boolean {
val dialogue = Dialogues.dialogues[this.dialogue] ?: return false
val currentDialogues = npc.brain.getMemory(CobblemonMemories.DIALOGUES).orElse(mutableListOf())
val activeDialogue = DialogueManager.startDialogue(player, npc, dialogue)
val newDialogues = currentDialogues + activeDialogue
npc.brain.setMemory(CobblemonMemories.DIALOGUES, newDialogues)
activeDialogue.completion.thenRun { onDialogueStopped(npc, activeDialogue) }
DialogueManager.startDialogue(ActiveDialogue(player, dialogue))
return true
}
fun onDialogueStopped(npc: NPCEntity, activeDialogue: ActiveDialogue) {
val currentDialogues = npc.brain.getMemory(CobblemonMemories.DIALOGUES).orElse(mutableListOf())
val newDialogues = currentDialogues - activeDialogue
if (newDialogues.isEmpty()) {
npc.brain.eraseMemory(CobblemonMemories.DIALOGUES)
} else {
npc.brain.setMemory(CobblemonMemories.DIALOGUES, newDialogues)
}
}
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(dialogue)
}
@@ -1,52 +0,0 @@
/*
* 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.cobblemon.mod.common.api.npc.NPCPartyProvider
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.google.gson.JsonElement
import net.minecraft.resources.ResourceLocation
/**
* A provider of a party for battling the NPC. It generates a [StaticNPCParty] but using a more complex process
* that takes an input level and makes a randomized selection of Pokémon and movesets.
*
* @author Hiroku
* @since July 12th, 2024
*/
class DynamicPartyProvider : NPCPartyProvider {
companion object {
val TYPE = "dynamic"
}
class DynamicPool {
val id = ResourceLocation.parse("cobblemon:pool")
val pokemon: MutableList<DynamicPokemon> = mutableListOf()
}
class DynamicPokemon(
val pokemon: PokemonProperties,
val possibleLevels: IntRange,
val selectableTimes: IntRange,
val guaranteed: Boolean = false
)
override val type = "dynamic"
override fun loadFromJSON(json: JsonElement) {
TODO("Not yet implemented")
}
override fun provide(npc: NPCEntity, level: Int): NPCParty {
TODO("Not yet implemented")
}
}
@@ -1,91 +0,0 @@
/*
* 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.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.net.IntSize
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.readSizedInt
import com.cobblemon.mod.common.util.writeSizedInt
import net.minecraft.core.RegistryAccess
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.server.level.ServerPlayer
/**
* Represents a party of Pokemon that an NPC can have. This isn't directly a party of Pokémon so that
* a dynamically generated party can exist where a party is generated at the time of a challenge.
*
* This interface is sealed. Implementations should be either [StaticNPCParty] or [DynamicNPCParty].
* If you want a custom type then you should implement [DynamicNPCParty] and register it in the
* [DynamicNPCParty.types] map.
*
* @author Hiroku
* @since July 12th, 2024
*/
sealed interface NPCParty {
fun getParty(player: ServerPlayer, npc: NPCEntity): PartyStore?
fun encode(buffer: RegistryFriendlyByteBuf)
fun decode(buffer: RegistryFriendlyByteBuf)
fun saveToNBT(nbt: CompoundTag, registryAccess: RegistryAccess)
fun loadFromNBT(nbt: CompoundTag, registryAccess: RegistryAccess)
}
/**
* A simple wrapping around a [NPCPartyStore]. This is a straightforward party that is always the same.
*
* @author Hiroku
* @since July 12th, 2024
*/
class StaticNPCParty(val party: NPCPartyStore) : NPCParty {
override fun getParty(player: ServerPlayer, npc: NPCEntity) = party
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeSizedInt(IntSize.U_BYTE, party.size())
for (pokemon in party) {
Pokemon.S2C_CODEC.encode(buffer, pokemon)
}
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
repeat(times = buffer.readSizedInt(IntSize.U_BYTE)) {
party.add(Pokemon.S2C_CODEC.decode(buffer))
}
}
override fun saveToNBT(nbt: CompoundTag, registryAccess: RegistryAccess) {
for ((index, pokemon) in party.withIndex()) {
nbt.put(DataKeys.NPC_PARTY_POKEMON + index, pokemon.saveToNBT(registryAccess))
}
}
override fun loadFromNBT(nbt: CompoundTag, registryAccess: RegistryAccess) {
var index = 0
while (nbt.contains(DataKeys.NPC_PARTY_POKEMON + index)) {
this.party.add(Pokemon().loadFromNBT(registryAccess, nbt.getCompound(DataKeys.NPC_PARTY_POKEMON + index)))
index++
}
}
}
/**
* A dynamic party that is generated at the time of a challenge.
*
* @author Hiroku
* @since July 12th, 2024
*/
interface DynamicNPCParty : NPCParty {
companion object {
val types = mutableMapOf<String, Class<out NPCParty>>()
init {}
}
}
@@ -11,16 +11,16 @@ package com.cobblemon.mod.common.api.npc.partyproviders
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.api.storage.party.PartyStore
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.toProperties
import com.cobblemon.mod.common.net.IntSize
import com.cobblemon.mod.common.util.*
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.server.level.ServerPlayer
/**
* A basic party provider that just produces a [StaticNPCParty] based on a list of [PokemonProperties].
*
* @author Hiroku
* @since August 19th, 2023
*/
class SimplePartyProvider : NPCPartyProvider {
companion object {
const val TYPE = "simple"
@@ -31,26 +31,45 @@ class SimplePartyProvider : NPCPartyProvider {
val pokemon = mutableListOf<PokemonProperties>()
override fun loadFromJSON(json: JsonElement) {
val pokemonList = json.asJsonObject.getAsJsonArray("pokemon")
pokemonList.forEach {
val pokemon = if (it.isJsonPrimitive) {
it.asString.toProperties()
} else {
val obj = it.asJsonObject
val props = PokemonProperties()
props.loadFromJSON(obj)
props
}
this.pokemon.add(pokemon)
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeSizedInt(IntSize.U_BYTE, pokemon.size)
for (pokemon in this.pokemon) {
buffer.writeString(pokemon.originalString)
}
}
override fun provide(npc: NPCEntity, level: Int): NPCParty {
val pokemon = pokemon.map { it.copy().also { it.level = it.level ?: level }.create() }
val party = NPCPartyStore(npc)
pokemon.forEach(party::add)
return StaticNPCParty(party)
override fun decode(buffer: RegistryFriendlyByteBuf) {
repeat(times = buffer.readSizedInt(IntSize.U_BYTE)) {
pokemon.add(PokemonProperties.parse(buffer.readString()))
}
}
override fun saveToNBT(nbt: CompoundTag) {
for ((index, pokemon) in this.pokemon.withIndex()) {
nbt.putString(DataKeys.NPC_PARTY_POKEMON + index, pokemon.originalString)
}
}
override fun loadFromNBT(nbt: CompoundTag) {
var index = 0
while (nbt.contains(DataKeys.NPC_PARTY_POKEMON + index)) {
this.pokemon.add(PokemonProperties.parse(nbt.getString(DataKeys.POKEMON_PROPERTIES + index)))
index++
}
}
override fun loadFromJSON(json: JsonElement) {
json as JsonObject
if (json.has(DataKeys.NPC_PARTY_POKEMON.lowercase())) {
json.get(DataKeys.NPC_PARTY_POKEMON.lowercase()).asJsonArray.forEach { pokemon.add(PokemonProperties.parse(it.asString)) }
}
}
override fun provide(npc: NPCEntity, challengers: List<ServerPlayer>): PartyStore {
return NPCPartyStore(npc).apply {
for (properties in pokemon) {
add(properties.create())
}
}
}
}
@@ -1,41 +0,0 @@
/*
* 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.variation
import com.cobblemon.mod.common.entity.npc.NPCEntity
// TODO probably need an exclusion list of aspects to prevent double application
// also some way to add them from a command?
/**
* An NPC variation provider happens at the moment of generating a particular NPC and works by appending to the
* aspects list that gets locked onto the NPC. Then ideally they can be tweaked by an editor or code after they've
* been put on the NPC. Retaining knowledge of the variation provider might be necessary for presenting a clear
* way of moving between different options but might be out of scope.
*
* @author Hiroku
* @since August 10th, 2024
*/
interface NPCVariationProvider {
/** All of the possible aspects that could be provided. */
val aspects: Set<String>
/** Provides some number of aspects for the given entity. */
fun provideAspects(npcEntity: NPCEntity): Set<String>
companion object {
val types = mutableMapOf(
"weighted" to WeightedNPCVariationProvider::class.java,
"random" to RandomNPCVariationProvider::class.java
)
fun register(type: String, clazz: Class<out NPCVariationProvider>) {
types[type] = clazz
}
}
}
@@ -1,23 +0,0 @@
/*
* 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.variation
import com.cobblemon.mod.common.entity.npc.NPCEntity
/**
* A super basic random NPC variation provider but is mainly used to simply communicate variations to the client.
*
* @author Hiroku
* @since August 14th, 2024
*/
class RandomNPCVariationProvider(
override val aspects: Set<String> = emptySet()
) : NPCVariationProvider {
override fun provideAspects(npcEntity: NPCEntity) = setOf(aspects.random())
}
@@ -1,36 +0,0 @@
/*
* 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.variation
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.weightedSelection
/**
* An element of a [WeightedNPCVariationProvider] that provides a set of aspects with a weight.
*
* @author Hiroku
* @since August 11th, 2024
*/
class WeightedAspect(val aspects: Set<String>, val weight: Double)
/**
* A variation provider that provides a set of aspects with a weight. When providing for an entity,
* a simple weighted selection is done to determine which aspects to apply.
*
* @author Hiroku
* @since August 11th, 2024
*/
class WeightedNPCVariationProvider : NPCVariationProvider {
var options: List<WeightedAspect> = emptyList()
override val aspects: Set<String>
get() = options.flatMap(WeightedAspect::aspects).toSet()
override fun provideAspects(npcEntity: NPCEntity) = options.weightedSelection(WeightedAspect::weight)?.aspects ?: emptySet()
}
@@ -9,11 +9,10 @@
package com.cobblemon.mod.common.api.pasture
import com.cobblemon.mod.common.Cobblemon
import java.util.*
import net.minecraft.core.BlockPos
import net.minecraft.core.RegistryAccess
import net.minecraft.resources.ResourceLocation
import java.util.*
class PastureLink(val linkId: UUID, val pcId: UUID, val dimension: ResourceLocation, val pos: BlockPos, val permissions: PasturePermissions) {
fun getPC(registryAccess: RegistryAccess) = Cobblemon.storage.getPC(pcId, registryAccess)
fun getPC() = Cobblemon.storage.getPC(pcId)
}
@@ -46,7 +46,6 @@ object CobblemonPermissions {
val GIVE_ALL_POKEMON = this.create("${COMMAND_PREFIX}giveallpokemon", PermissionLevel.ALL_COMMANDS)
val SPAWN_POKEMON = this.create("${COMMAND_PREFIX}spawnpokemon", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
val SPAWN_NPC = this.create("${COMMAND_PREFIX}spawnnpc", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
val STOP_BATTLE = this.create("${COMMAND_PREFIX}stopbattle", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@@ -75,8 +74,6 @@ object CobblemonPermissions {
val POKEDEX = this.create("${COMMAND_PREFIX}pokedex", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
val NPC_EDIT = this.create("${COMMAND_PREFIX}npcedit", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
val FREEZE_POKEMON = this.create("${COMMAND_PREFIX}freezepokemon", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
val APPLY_PLAYER_TEXTURE = this.create("${COMMAND_PREFIX}applyplayertexture", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
val ABANDON_MULTITEAM = this.create("${COMMAND_PREFIX}abandonmultiteam", PermissionLevel.NONE)
@@ -172,7 +172,8 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
createDefault("net_ball", CatchRateModifiers.typeBoosting(3F, ElementalTypes.BUG, ElementalTypes.WATER))
createDefault("dive_ball", CatchRateModifiers.SUBMERGED_IN_WATER, waterDragValue = 0.99F)
createDefault("nest_ball", CatchRateModifiers.NEST)
createDefault("repeat_ball", CatchRateModifiers.REPEAT)
// ToDo implement effect once pokedex is implemented, we have a custom multiplier of 2.5 instead of the official pokeball
createDefault("repeat_ball")
createDefault("timer_ball", CatchRateModifiers.turnBased { turn -> (1F * turn * (1229F / 4096F)).coerceAtMost(4F) })
createDefault("luxury_ball", effects = listOf(FriendshipEarningBoostEffect(2F)))
createDefault("premier_ball")
@@ -8,11 +8,9 @@
package com.cobblemon.mod.common.api.pokeball.catching.calculators
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokedex.CaughtCount
import net.minecraft.server.level.ServerPlayer
import kotlin.math.roundToInt
import kotlin.random.Random
import net.minecraft.server.level.ServerPlayer
/**
* A provider for the possibility at a critical capture.
@@ -31,7 +29,8 @@ interface CriticalCaptureProvider {
* @return If the critical capture should occur
*/
fun shouldHaveCriticalCapture(player: ServerPlayer, modifiedCatchRate: Float): Boolean {
val caughtCount = Cobblemon.playerDataManager.getPokedexData(player).getGlobalCalculatedValue(CaughtCount)
// ToDo once pokedex is implemented change number here
val caughtCount = 0
val caughtMultiplier = when {
caughtCount <= 30 -> 0F
// This one is exact
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.api.pokeball.catching.calculators
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokedex.CaughtCount
import net.minecraft.server.level.ServerPlayer
/**
@@ -27,7 +25,8 @@ interface PokedexProgressCaptureMultiplierProvider {
* @return The multiplier based on the caught count.
*/
fun caughtMultiplierFor(player: ServerPlayer): Float {
val caughtCount = Cobblemon.playerDataManager.getPokedexData(player).getGlobalCalculatedValue(CaughtCount)
// ToDo once pokedex is implemented change number here
val caughtCount = 0
return when {
caughtCount < 30 -> 1229F / 4096F
// This one is exact
@@ -8,14 +8,10 @@
package com.cobblemon.mod.common.api.pokeball.catching.modifiers
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokeball.catching.CatchRateModifier
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.status.Status
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawnCause
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreManager
import com.cobblemon.mod.common.api.tags.CobblemonBiomeTags
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.battles.BattleRegistry
@@ -102,8 +98,8 @@ object CatchRateModifiers {
*/
val LIGHT_LEVEL = WorldStateModifier { _, entity ->
when (entity.level().getMaxLocalRawBrightness(entity.blockPosition())) {
0 -> 3.5F
in 1..7 -> 3F
0 -> 3F
in 1..7 -> 1.5F
else -> 1F
}
}
@@ -152,21 +148,6 @@ object CatchRateModifiers {
}
}) { _, pokemon -> pokemon.form.weight >= 1000F }
/**
* Used by [PokeBalls.REPEAT_BALL].
* Checks if the entity is registered as caught in the thrower's pokedex data.
* If yes boosts the catch rate by *2.5
*/
val REPEAT: CatchRateModifier = WorldStateModifier { thrower, pokemon ->
val playerDexData = Cobblemon.playerDataManager.getPokedexData(thrower.uuid)
val speciesId = pokemon.pokemon.species.resourceIdentifier
val knowledge = playerDexData.getKnowledgeForSpecies(speciesId)
when (knowledge) {
PokedexEntryProgress.CAUGHT -> 3.5F
else -> 1F
}
}
/**
* Used by [PokeBalls.NET_BALL].
* Boosts the catch rate if the target is of the given types.
@@ -198,4 +179,5 @@ object CatchRateModifiers {
val battle = BattleRegistry.getBattleByParticipatingPlayer(player) ?: return@BattleModifier 1F
multiplierCalculator.invoke(battle.turn)
}
}
@@ -13,7 +13,6 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import net.minecraft.resources.ResourceLocation
@@ -47,11 +46,6 @@ abstract class AbstractPokedexManager {
}
}
fun getHighestKnowledgeForSpecies(pokemon: Pokemon): PokedexEntryProgress {
val speciesRecord = getSpeciesRecord(pokemon.species.resourceIdentifier)
return speciesRecord?.getKnowledge() ?: PokedexEntryProgress.NONE
}
fun getHighestKnowledgeFor(entry: PokedexEntry): PokedexEntryProgress {
val speciesRecord = getSpeciesRecord(entry.speciesId) ?: return PokedexEntryProgress.NONE
val hasAllAspects = entry.conditionAspects.all(speciesRecord::hasAspect)
@@ -86,21 +80,6 @@ abstract class AbstractPokedexManager {
}
}
fun getNewInformation(pokemon: Pokemon): PokedexLearnedInformation {
val speciesRecord = getSpeciesRecord(pokemon.species.resourceIdentifier)
if (speciesRecord == null || speciesRecord.getKnowledge() == PokedexEntryProgress.NONE) {
return PokedexLearnedInformation.SPECIES
}
val formRecord = speciesRecord.getFormRecord(pokemon.form.name)
if (formRecord == null || formRecord.knowledge == PokedexEntryProgress.NONE) {
return PokedexLearnedInformation.FORM
}
if (pokemon.aspects.none(speciesRecord::hasAspect) || pokemon.gender !in formRecord.getGenders() || !formRecord.hasSeenShinyState(pokemon.shiny)) {
return PokedexLearnedInformation.VARIATION
}
return PokedexLearnedInformation.NONE
}
fun getSeenShinyStates(entry: PokedexEntry, form: PokedexForm): Set<String> {
return getSpeciesRecord(entry.speciesId)?.getFormRecord(form.displayForm)?.getSeenShinyStates() ?: return emptySet()
}
@@ -119,10 +98,6 @@ abstract class AbstractPokedexManager {
open fun onSpeciesRecordUpdated(speciesDexRecord: SpeciesDexRecord) {
// Save stuff and packet updates
clearCalculatedValues()
}
fun clearCalculatedValues() {
dexCalculatedValues.clear()
globalCalculatedValues.clear()
}
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.api.pokedex
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.pokedex.def.PokedexDef
import com.cobblemon.mod.common.api.pokedex.entry.DexEntries
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.PokedexDexSyncPacket
import com.cobblemon.mod.common.util.adapters.CodecBackedAdapter
@@ -47,6 +49,6 @@ object Dexes : JsonDataRegistry<PokedexDef> {
}
override fun sync(player: ServerPlayer) {
PokedexDexSyncPacket(dexEntryMap.values).sendToPlayer(player)
PokedexDexSyncPacket(dexEntryMap.values)
}
}
@@ -1,16 +0,0 @@
/*
* 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.pokedex
enum class PokedexLearnedInformation {
NONE,
SPECIES,
FORM,
VARIATION
}
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.pokedex
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.storage.player.InstancedPlayerData
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreType
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
import com.cobblemon.mod.common.pokemon.FormData
@@ -51,7 +50,7 @@ class PokedexManager(
override fun onSpeciesRecordUpdated(speciesDexRecord: SpeciesDexRecord) {
uuid.getPlayer()?.sendPacket(
SetClientPlayerDataPacket(
type = PlayerInstancedDataStoreTypes.POKEDEX,
type = PlayerInstancedDataStoreType.POKEDEX,
playerData = ClientPokedexManager(mutableMapOf(speciesDexRecord.id to speciesDexRecord)),
isIncremental = true
)
@@ -19,21 +19,13 @@ interface GlobalPokedexValueCalculator<T> {
fun calculate(dexManager: AbstractPokedexManager): T
}
object CaughtCount : PokedexValueCalculator<Int>, GlobalPokedexValueCalculator<Int> {
override fun calculate(dexManager: AbstractPokedexManager): Int {
return dexManager.speciesRecords.values.count { it.getKnowledge() == PokedexEntryProgress.CAUGHT }
}
object CaughtCount : PokedexValueCalculator<Int> {
override fun calculate(dexManager: AbstractPokedexManager, dex: Map<ResourceLocation, PokedexEntry>): Int {
return dex.entries.map { it.value }.count { dexManager.getKnowledgeForSpecies(it.speciesId) == PokedexEntryProgress.CAUGHT }
}
}
object SeenCount : PokedexValueCalculator<Int>, GlobalPokedexValueCalculator<Int> {
override fun calculate(dexManager: AbstractPokedexManager): Int {
return dexManager.speciesRecords.values.count { it.getKnowledge() != PokedexEntryProgress.NONE }
}
object SeenCount : PokedexValueCalculator<Int> {
override fun calculate(dexManager: AbstractPokedexManager, dex: Map<ResourceLocation, PokedexEntry>): Int {
return dex.entries.map { it.value }.count { dexManager.getKnowledgeForSpecies(it.speciesId) != PokedexEntryProgress.NONE }
}
@@ -98,7 +98,7 @@ class AggregatePokedexDef(
) { id, sortOrder, entries ->
val result = AggregatePokedexDef(id)
result.sortOrder = sortOrder
result.subDexIds.addAll(entries)
//result.subDexIds.addAll(entries)
result
}
}
@@ -54,8 +54,7 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
}
override val observable = SimpleObservable<DexEntries>()
override fun sync(player: ServerPlayer) {
DexEntrySyncPacket(entries.values).sendToPlayer(player)
DexEntrySyncPacket(entries.values)
}
}
@@ -14,7 +14,6 @@ import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.abilities.Ability
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.pokemon.stats.Stats
@@ -24,32 +23,14 @@ import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.api.types.tera.TeraTypes
import com.cobblemon.mod.common.api.types.tera.elemental.ElementalTypeTeraType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.EVs
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.IVs
import com.cobblemon.mod.common.pokemon.OriginalTrainerType
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.pokemon.*
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.isUuid
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.util.simplify
import com.cobblemon.mod.common.util.splitMap
import com.cobblemon.mod.common.util.*
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.mojang.brigadier.StringReader
import com.mojang.serialization.Codec
import java.util.UUID
import kotlin.math.min
import kotlin.random.Random
import net.minecraft.ResourceLocationException
import net.minecraft.commands.arguments.item.ItemParser
import net.minecraft.core.HolderLookup
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.StringTag
@@ -57,8 +38,9 @@ import net.minecraft.nbt.Tag
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import java.util.*
import kotlin.random.Random
/**
* A grouping of typical, selectable properties for a Pokémon. This is serializable
@@ -140,8 +122,6 @@ open class PokemonProperties {
props.tradeable = parseBooleanProperty(keyPairs, listOf("tradeable", "tradable"))
props.originalTrainerType = OriginalTrainerType.entries.toList().parsePropertyOfCollection(keyPairs, listOf("originaltrainertype", "ottype"), labelsOptional = true) { it.name.lowercase() }
props.originalTrainer = parsePlayerProperty(keyPairs, listOf("originaltrainer", "ot"))
props.moves = parseString(keyPairs, listOf("moves"))?.split(",")
props.heldItem = parseString(keyPairs, listOf("helditem", "held_item"))
val maybeIVs = IVs()
val maybeEVs = EVs()
@@ -171,16 +151,6 @@ open class PokemonProperties {
}
}
private fun parseString(keyPairs: MutableList<Pair<String, String?>>, labels: Iterable<String>): String? {
val matchingKeyPair = getMatchedKeyPair(keyPairs, labels) ?: return null
val value = matchingKeyPair.second
return if (value.isNullOrBlank()) {
null
} else {
value
}
}
private fun parseIntProperty(keyPairs: MutableList<Pair<String, String?>>, labels: Iterable<String>): Int? {
val matchingKeyPair = getMatchedKeyPair(keyPairs, labels) ?: return null
val value = matchingKeyPair.second
@@ -335,8 +305,6 @@ open class PokemonProperties {
var tradeable: Boolean? = null
var originalTrainerType: OriginalTrainerType? = null
var originalTrainer: String? = null // Original Trainer by Username or UUID
var moves: List<String>? = null
var heldItem: String? = null
var ivs: IVs? = null
var evs: EVs? = null
@@ -365,10 +333,6 @@ open class PokemonProperties {
this.commonApply(pokemonEntity.pokemon)
}
fun applyCustomProperties(pokemonEntity: PokemonEntity) {
this.customProperties.forEach { it.apply(pokemonEntity) }
}
private fun commonApply(pokemon: Pokemon) {
species?.let {
return@let try {
@@ -428,31 +392,6 @@ open class PokemonProperties {
}
pokemon.refreshOriginalTrainer()
}
moves?.let { moves ->
if (pokemon.moveSet.getMoves().isEmpty()) {
pokemon.initializeMoveset(preferLatest = true)
}
val moveTemplates = moves.mapNotNull(Moves::getByName).shuffled()
val replaceableIndices = (0..3).filterNot { pokemon.moveSet[it]?.template?.let(moveTemplates::contains) == true }.shuffled()
val insertableMoves = moveTemplates.filterNot { pokemon.moveSet.any { move -> move.template == it } }
if (replaceableIndices.isNotEmpty() && insertableMoves.isNotEmpty()) {
val insertingMoves = insertableMoves.subList(0, min(replaceableIndices.size, insertableMoves.size))
pokemon.moveSet.doWithoutEmitting {
insertingMoves.forEachIndexed { index, move -> pokemon.moveSet.setMove(replaceableIndices[index], move.create()); pokemon.moveSet[replaceableIndices[index]]!!.update() }
}
pokemon.moveSet.update()
}
}
heldItem?.let { itemKey ->
val server = server() ?: return@let
val parser = ItemParser(server.registryAccess())
val result = parser.parse(StringReader(itemKey))
val stack = ItemStack(result.item)
stack.applyComponents(result.components)
if (stack.isEmpty) return@let
pokemon.heldItem = stack
}
pokemon.updateAspects()
}
@@ -502,9 +441,8 @@ open class PokemonProperties {
dmaxLevel?.takeIf { it != pokemon.dmaxLevel }?.let { return false }
gmaxFactor?.takeIf { it != pokemon.gmaxFactor }?.let { return false }
tradeable?.takeIf { it != pokemon.tradeable }?.let { return false }
originalTrainer?.takeIf { it != pokemon.originalTrainer }?.let { return false }
originalTrainerType?.takeIf { it != pokemon.originalTrainerType }?.let { return false }
moves?.takeIf { it.any { move -> pokemon.moveSet.none { it.template.name == move } } }?.let { return false }
originalTrainer?.takeIf { it != pokemon.originalTrainer }?.let{ return false }
originalTrainerType?.takeIf { it != pokemon.originalTrainerType }?.let{ return false }
return true
}
@@ -554,7 +492,6 @@ open class PokemonProperties {
tradeable?.takeIf { it != properties.tradeable }?.let { return false }
originalTrainer?.takeIf { it != properties.originalTrainer }?.let{ return false }
originalTrainerType?.takeIf { it != properties.originalTrainerType }?.let{ return false }
moves?.takeIf { it.any { move -> properties.moves?.none { it == move } == true } }?.let { return false }
return true
}
@@ -582,7 +519,7 @@ open class PokemonProperties {
}
fun createEntity(world: Level): PokemonEntity {
return PokemonEntity(world, create()).also { applyCustomProperties(it) }
return PokemonEntity(world, create())
}
// TODO Codecs at some point
@@ -609,8 +546,6 @@ open class PokemonProperties {
tradeable?.let { nbt.putBoolean(DataKeys.POKEMON_TRADEABLE, it) }
originalTrainerType?.let { nbt.putInt(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE, it.ordinal) }
originalTrainer?.let { nbt.putString(DataKeys.POKEMON_ORIGINAL_TRAINER, it) }
moves?.let { nbt.putString(DataKeys.POKEMON_PROPERTIES_MOVES, it.joinToString(separator = ",")) }
heldItem?.let {nbt.putString(DataKeys.POKEMON_PROPERTIES_HELDITEM, it)}
val custom = ListTag()
customProperties.map { StringTag.valueOf(it.asString()) }.forEach { custom.add(it) }
nbt.put(DataKeys.POKEMON_PROPERTIES_CUSTOM, custom)
@@ -640,8 +575,6 @@ open class PokemonProperties {
tradeable = if (tag.contains(DataKeys.POKEMON_TRADEABLE)) tag.getBoolean(DataKeys.POKEMON_TRADEABLE) else null
originalTrainerType = if (tag.contains(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE)) OriginalTrainerType.valueOf(tag.getString(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE)) else null
originalTrainer = if (tag.contains(DataKeys.POKEMON_ORIGINAL_TRAINER)) tag.getString(DataKeys.POKEMON_ORIGINAL_TRAINER) else null
moves = if (tag.contains(DataKeys.POKEMON_PROPERTIES_MOVES)) tag.getString(DataKeys.POKEMON_PROPERTIES_MOVES).split(",") else null
heldItem = if (tag.contains(DataKeys.POKEMON_PROPERTIES_HELDITEM)) tag.getString(DataKeys.POKEMON_PROPERTIES_HELDITEM) else null
val custom = tag.getList(DataKeys.POKEMON_PROPERTIES_CUSTOM, Tag.TAG_STRING.toInt())
// This is kinda gross
custom.forEach { customProperties.addAll(parse(it.asString).customProperties) }
@@ -673,8 +606,6 @@ open class PokemonProperties {
tradeable?.let { json.addProperty(DataKeys.POKEMON_TRADEABLE, it) }
originalTrainerType?.let { json.addProperty(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE, it.name) }
originalTrainer?.let { json.addProperty(DataKeys.POKEMON_ORIGINAL_TRAINER, it) }
moves?.let { json.addProperty(DataKeys.POKEMON_PROPERTIES_MOVES, it.joinToString(separator = ",")) }
heldItem?.let {json.addProperty(DataKeys.POKEMON_PROPERTIES_HELDITEM, it)}
val custom = JsonArray()
customProperties.map { it.asString() }.forEach { custom.add(it) }
json.add(DataKeys.POKEMON_PROPERTIES_CUSTOM, custom)
@@ -705,8 +636,6 @@ open class PokemonProperties {
tradeable = json.get(DataKeys.POKEMON_TRADEABLE)?.asBoolean
originalTrainerType = json.get(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE)?.asString?.let { OriginalTrainerType.valueOf(it) }
originalTrainer = json.get(DataKeys.POKEMON_ORIGINAL_TRAINER)?.asString
moves = json.get(DataKeys.POKEMON_PROPERTIES_MOVES)?.asString?.split(",")
heldItem = json.get(DataKeys.POKEMON_PROPERTIES_HELDITEM)?.asString
val custom = json.get(DataKeys.POKEMON_PROPERTIES_CUSTOM)?.asJsonArray
// This is still kinda gross
custom?.forEach { customProperties.addAll(parse(it.asString).customProperties) }
@@ -741,8 +670,6 @@ open class PokemonProperties {
originalTrainerType?.let { pieces.add("originaltrainertype=${it.name}") }
originalTrainer?.let { pieces.add("originaltrainer=$it") }
customProperties.forEach { pieces.add(it.asString()) }
moves?.let { pieces.add("moves=${it.joinToString(separator = ",")}") }
heldItem?.let {pieces.add("helditem=$it")}
return pieces.joinToString(separator)
}
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.advancement.CobblemonCriteria
import com.cobblemon.mod.common.advancement.criterion.EvolvePokemonContext
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokemon.PokemonGainedEvent
import com.cobblemon.mod.common.api.events.pokemon.evolution.EvolutionCompleteEvent
import com.cobblemon.mod.common.api.events.pokemon.evolution.EvolutionTestedEvent
import com.cobblemon.mod.common.api.moves.BenchedMove
@@ -31,9 +30,6 @@ import com.cobblemon.mod.common.pokemon.evolution.variants.LevelUpEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.party
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.network.protocol.game.ClientboundSoundPacket
import net.minecraft.sounds.SoundSource
import net.minecraft.world.entity.Entity
import net.minecraft.world.item.ItemStack
@@ -156,7 +152,6 @@ interface Evolution : EvolutionLike {
val pokemonEntity = pokemon.entity
if (pokemonEntity == null || !useEvolutionEffect) {
pokemon.getOwnerPlayer()?.playNotifySound(CobblemonSounds.EVOLUTION_UI, SoundSource.PLAYERS, 1F, 1F)
evolutionMethod(pokemon)
} else {
pokemonEntity.busyLocks.add("evolving")
@@ -175,12 +170,12 @@ interface Evolution : EvolutionLike {
}
private fun evolutionAnimation(pokemon: Entity) {
val playPoseableAnimationPacket = PlayPosableAnimationPacket(pokemon.id, setOf("q.bedrock_stateful('evolution', 'evolution', 'endures_primary_animations');"), listOf())
val playPoseableAnimationPacket = PlayPosableAnimationPacket(pokemon.id, setOf("q.bedrock_stateful('evolution', 'evolution', 'endures_primary_animations');"), emptySet())
playPoseableAnimationPacket.sendToPlayersAround(pokemon.x, pokemon.y, pokemon.z, 128.0, pokemon.level().dimension())
}
private fun cryAnimation(pokemon: Entity) {
val playPoseableAnimationPacket = PlayPosableAnimationPacket(pokemon.id, setOf("cry"), emptyList())
val playPoseableAnimationPacket = PlayPosableAnimationPacket(pokemon.id, setOf("cry"), emptySet())
playPoseableAnimationPacket.sendToPlayersAround(pokemon.x, pokemon.y, pokemon.z, 128.0, pokemon.level().dimension())
}
@@ -197,9 +192,9 @@ interface Evolution : EvolutionLike {
// we want to instantly tick for example you might only evolve your Bulbasaur at level 34 so Venusaur should be immediately available
pokemon.evolutions.filterIsInstance<PassiveEvolution>().forEach { evolution -> evolution.attemptEvolution(pokemon) }
pokemon.lockedEvolutions.filterIsInstance<PassiveEvolution>().forEach { evolution -> evolution.attemptEvolution(pokemon) }
pokemon.getOwnerPlayer()?.playSound(CobblemonSounds.EVOLUTION_UI, 1F, 1F)
this.shed(pokemon)
CobblemonEvents.EVOLUTION_COMPLETE.post(EvolutionCompleteEvent(pokemon, this))
CobblemonEvents.POKEMON_GAINED.post(PokemonGainedEvent(pokemon.getOwnerUUID()!!, pokemon))
}
fun applyTo(pokemon: Pokemon) {
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.pokemon.experience
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.pokemon.OriginalTrainerType
import com.cobblemon.mod.common.pokemon.evolution.requirements.LevelRequirement
import kotlin.math.pow
import kotlin.math.roundToInt
@@ -30,9 +29,8 @@ object StandardExperienceCalculator : ExperienceCalculator {
val victorPokemon = battlePokemon.effectedPokemon
val victorLevel = victorPokemon.level
val term3 = (((2.0 * opponentLevel) + 10) / (opponentLevel + victorLevel + 10)).pow(2.5)
// ToDo 1.7 if pokemon was traded with someone of different locale/language
val nonOtBonus = if (battlePokemon.effectedPokemon.originalTrainerType == OriginalTrainerType.PLAYER &&
battlePokemon.effectedPokemon.originalTrainer.equals(battlePokemon.actor.uuid.toString())) 1.0 else 1.5
// ToDo when OT is implemented 1.5 if traded with someone from the same locale, 1.7 otherwise, 1.0 if OT
val nonOtBonus = 1.0
val luckyEggMultiplier = if (battlePokemon.effectedPokemon.heldItemNoCopy().`is`(CobblemonItemTags.LUCKY_EGG)) Cobblemon.config.luckyEggMultiplier else 1.0
val evolutionMultiplier = if (battlePokemon.effectedPokemon.evolutionProxy.server().any { evolution ->
val requirements = evolution.requirements.asSequence()
@@ -0,0 +1,36 @@
package com.cobblemon.mod.common.api.reactive.collections
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.reactive.collections.subscriptions.CollectionSubscription
interface MutableObservableCollection<T, C : Collection<T>> :
ObservableCollection<T, C>,
MutableCollection<T> {
fun subscribe(
priority: Priority = Priority.NORMAL,
anyChangeHandler: (Pair<C, T>) -> Unit,
additionHandler: ((Pair<C, T>) -> Unit)? = null,
removalHandler: ((Pair<C, T>) -> Unit)? = null,
): CollectionSubscription<T, C>
override fun add(element: T): Boolean
fun addIf(element: T, predicate: (C) -> Boolean): Boolean
override fun remove(element: T): Boolean
fun removeIf(predicate: (T) -> Boolean): Boolean
override fun addAll(elements: Collection<T>): Boolean
override fun retainAll(elements: Collection<T>): Boolean
override fun removeAll(elements: Collection<T>): Boolean
override fun iterator(): MutableIterator<T>
override fun clear()
fun mutableCopy(): MutableObservableCollection<T, C>
}
@@ -0,0 +1,8 @@
package com.cobblemon.mod.common.api.reactive.collections
interface MutableObservableCollectionIterator<T, C : Collection<T>, I : Iterator<T>> :
ObservableCollectionIterator<T, I>,
MutableIterator<T> {
override fun remove()
}
@@ -0,0 +1,32 @@
package com.cobblemon.mod.common.api.reactive.collections
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.ObservableSubscription
interface ObservableCollection<T, C : Collection<T>> :
Collection<T>,
Observable<Pair<C, T>> {
val elements: C
override val size: Int
override fun subscribe(
priority: Priority,
handler: (Pair<C, T>) -> Unit,
): ObservableSubscription<Pair<C, T>>
override fun unsubscribe(subscription: ObservableSubscription<Pair<C, T>>)
override fun isEmpty(): Boolean
fun isNotEmpty(): Boolean
override fun contains(element: T): Boolean
override fun containsAll(elements: Collection<T>): Boolean
override operator fun iterator(): Iterator<T>
fun copy(): ObservableCollection<T, C>
}
@@ -0,0 +1,7 @@
package com.cobblemon.mod.common.api.reactive.collections
interface ObservableCollectionIterator<T, I : Iterator<T>> : Iterator<T> {
override fun hasNext(): Boolean
override fun next(): T
}
@@ -0,0 +1,35 @@
package com.cobblemon.mod.common.api.reactive.collections
import com.cobblemon.mod.common.api.reactive.Observable
/**
* This attempts to cast [this] value to [Observable].
*
* @return An observable set with [this] as an [Observable] or an empty set.
*/
fun <T> T.getElementObservables(): Set<Observable<*>> {
this.tryGetObservable()?.let { return setOf(it) }
return emptySet()
}
/**
* This attempts to cast [this] value to [Observable].
*
* @return This value as an [Observable] or null.
*/
fun <T> T.tryGetObservable(): Observable<*>? = if (this is Observable<*>) this else null
fun <T> MutableIterator<T>.removeIf(predicate: (T) -> Boolean): Boolean {
var removed = false
this.forEach { element ->
if (predicate(element)) {
this.remove()
if (!removed) {
removed = true
}
}
}
return removed
}
@@ -0,0 +1,242 @@
package com.cobblemon.mod.common.api.reactive.collections.list
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.reactive.collections.MutableObservableCollection
import com.cobblemon.mod.common.api.reactive.collections.getElementObservables
import com.cobblemon.mod.common.api.reactive.collections.subscriptions.IndexedListSubscription
import com.cobblemon.mod.common.api.reactive.collections.subscriptions.ListSubscription
import com.cobblemon.mod.common.api.reactive.collections.removeIf
open class MutableObservableList<T>(
list: Collection<T> = setOf(),
elementHandler: (T) -> Set<Observable<*>> = { it.getElementObservables() },
) : MutableObservableCollection<T, List<T>>,
ObservableList<T>(list, elementHandler),
MutableList<T> {
private val additionObservable = SimpleObservable<Pair<List<T>, T>>()
private val removalObservable = SimpleObservable<Pair<List<T>, T>>()
private val setObservable = SimpleObservable<Pair<List<T>, Pair<T, T>>>()
private val swapObservable = SimpleObservable<Pair<List<T>, Pair<T, T>>>()
private val additionObservableIndexed = SimpleObservable<Triple<List<T>, T, Int>>()
private val removalObservableIndexed = SimpleObservable<Triple<List<T>, T, Int>>()
private val setObservableIndexed = SimpleObservable<Triple<List<T>, Pair<T, T>, Int>>()
private val swapObservableIndexed = SimpleObservable<Triple<List<T>, Pair<T, T>, Pair<Int, Int>>>()
override fun subscribe(
priority: Priority,
anyChangeHandler: (Pair<List<T>, T>) -> Unit,
additionHandler: ((Pair<List<T>, T>) -> Unit)?,
removalHandler: ((Pair<List<T>, T>) -> Unit)?,
): ListSubscription<T> {
return ListSubscription(
anyChange = this.subscribe(priority, anyChangeHandler),
addition = additionHandler?.let { additionObservable.subscribe(priority, it) },
removal = removalHandler?.let { removalObservable.subscribe(priority, it) },
)
}
fun subscribeAndHandle(
priority: Priority,
anyChangeHandler: (Pair<List<T>, T>) -> Unit,
additionHandler: ((Pair<List<T>, T>) -> Unit)?,
removalHandler: ((Pair<List<T>, T>) -> Unit)?,
): ListSubscription<T> {
elements.forEach { anyChangeHandler(this to it) }
return subscribe(priority, anyChangeHandler, additionHandler, removalHandler)
}
fun subscribe(
priority: Priority,
anyChangeHandler: (Pair<List<T>, T>) -> Unit,
additionHandler: ((Pair<List<T>, T>) -> Unit)?,
removalHandler: ((Pair<List<T>, T>) -> Unit)?,
setHandler: ((Pair<List<T>, Pair<T, T>>) -> Unit)?,
swapHandler: ((Pair<List<T>, Pair<T, T>>) -> Unit)?,
): ListSubscription<T> {
@Suppress("DuplicatedCode")
return ListSubscription(
anyChange = this.subscribe(priority, anyChangeHandler),
addition = additionHandler?.let { additionObservable.subscribe(priority, it) },
removal = removalHandler?.let { removalObservable.subscribe(priority, it) },
set = setHandler?.let { setObservable.subscribe(priority, it) },
swap = swapHandler?.let { swapObservable.subscribe(priority, it) },
)
}
fun subscribeAndHandle(
priority: Priority,
anyChangeHandler: (Pair<List<T>, T>) -> Unit,
additionHandler: ((Pair<List<T>, T>) -> Unit)?,
removalHandler: ((Pair<List<T>, T>) -> Unit)?,
setHandler: ((Pair<List<T>, Pair<T, T>>) -> Unit)?,
swapHandler: ((Pair<List<T>, Pair<T, T>>) -> Unit)?,
): ListSubscription<T> {
elements.forEach { anyChangeHandler(this to it) }
return subscribe(priority, anyChangeHandler, additionHandler, removalHandler, setHandler, swapHandler)
}
fun subscribeIndexed(
priority: Priority,
additionHandler: ((Triple<List<T>, T, Int>) -> Unit)?,
removalHandler: ((Triple<List<T>, T, Int>) -> Unit)?,
setHandler: ((Triple<List<T>, Pair<T, T>, Int>) -> Unit)?,
swapHandler: ((Triple<List<T>, Pair<T, T>, Pair<Int, Int>>) -> Unit)?,
): IndexedListSubscription<T> {
return IndexedListSubscription(
addition = additionHandler?.let { additionObservableIndexed.subscribe(priority, it) },
removal = removalHandler?.let { removalObservableIndexed.subscribe(priority, it) },
set = setHandler?.let { setObservableIndexed.subscribe(priority, it) },
swap = swapHandler?.let { swapObservableIndexed.subscribe(priority, it) },
)
}
/**
* @return `true` after handling [register] & all subscriptions to [additionObservable],
* [additionObservableIndexed], & [ObservableList.emitAnyChange].
*/
private fun emitAddition(element: T, index: Int): Boolean {
register(element)
additionObservable.emit(list to element)
additionObservableIndexed.emit(Triple(list, element, index))
return emitAnyChange(element)
}
/**
* @return `true` after handling [unregister] & all subscriptions to [removalObservable],
* [removalObservableIndexed] & [ObservableList.emitAnyChange].
*/
private fun emitRemoval(element: T, index: Int): Boolean {
unregister(element)
removalObservable.emit(list to element)
removalObservableIndexed.emit(Triple(list, element, index))
return emitAnyChange(element)
}
/**
* @return `true` after handling [register], [unregister] & all subscriptions to [setObservable],
* [setObservableIndexed], & [ObservableList.emitAnyChange].
*/
private fun emitSet(elements: Pair<T, T>, index: Int): Boolean {
register(elements.first)
unregister(elements.second)
setObservable.emit(list to elements)
setObservableIndexed.emit(Triple(list, elements, index))
emitAnyChange(elements.first)
emitAnyChange(elements.second)
return true
}
/**
* @return `true` after handling all subscriptions to [swapObservable],
* [swapObservableIndexed], & [ObservableList.emitAnyChange].
*/
private fun emitSwap(elements: Pair<T, T>, indices: Pair<Int, Int>): Boolean {
swapObservable.emit(list to elements)
swapObservableIndexed.emit(Triple(list, elements, indices))
emitAnyChange(elements.first)
emitAnyChange(elements.second)
return true
}
override operator fun iterator() = MutableObservableListIterator(this)
override fun listIterator() = MutableObservableListIterator(this)
override fun listIterator(index: Int) = MutableObservableListIterator(this, index)
override fun subList(fromIndex: Int, toIndex: Int): MutableObservableSubList<T> {
return MutableObservableSubList(this, fromIndex, toIndex, elementHandler)
}
override fun addIf(element: T, predicate: (List<T>) -> Boolean): Boolean {
return if (predicate(list)) add(element) else false
}
override fun add(element: T): Boolean {
list.add(element)
return emitAddition(element, lastIndex)
}
fun addAtIf(index: Int, element: T, predicate: (List<T>) -> Boolean): Boolean {
return if (predicate(list)) {
add(index, element)
true
} else {
false
}
}
override fun add(index: Int, element: T) {
list.add(index, element)
emitAddition(element, index)
}
fun setIf(index: Int, element: T, predicate: (T) -> Boolean): T? {
return if (predicate(list[index])) set(index, element) else null
}
override operator fun set(index: Int, element: T): T {
return list.set(index, element).also { emitSet(element to it, index) }
}
fun swapIf(indexOne: Int, indexTwo: Int, predicate: () -> Boolean): Boolean {
return predicate().also { if (it) swap(indexOne, indexTwo) }
}
fun swap(indexOne: Int, indexTwo: Int) {
val one = list[indexOne]
val two = list.set(indexTwo, one)
list[indexOne] = two
emitSwap(one to two, indexOne to indexTwo)
}
override fun removeIf(predicate: (T) -> Boolean): Boolean = iterator().removeIf(predicate)
override fun remove(element: T): Boolean {
val indexOf = list.indexOfFirst { it == element }
if (indexOf == -1) {
return false
}
list.removeAt(indexOf)
return emitRemoval(element, indexOf)
}
override fun removeAt(index: Int): T {
val element = list.removeAt(index)
emitRemoval(element, index)
return element
}
fun removeAtIf(index: Int, predicate: (T) -> Boolean): T? {
val element = list[index]
if (predicate(element)) {
list.removeAt(index)
emitRemoval(element, index)
}
return element
}
override fun addAll(elements: Collection<T>): Boolean {
elements.forEach { add(it) }
return true
}
override fun addAll(index: Int, elements: Collection<T>): Boolean {
var currentIndex = index
elements.forEach { add(currentIndex++, it) }
return true
}
override fun removeAll(elements: Collection<T>):Boolean = removeIf { elements.contains(it) }
override fun retainAll(elements: Collection<T>):Boolean = removeIf { !elements.contains(it) }
override fun clear() {
while (isNotEmpty()) {
removeAt(0)
}
}
}
@@ -0,0 +1,60 @@
package com.cobblemon.mod.common.api.reactive.collections.list
// the name is not quite long enough yet...
import com.cobblemon.mod.common.api.reactive.collections.MutableObservableCollectionIterator
open class MutableObservableListIterator<T>(
observableList: MutableObservableList<T>,
index: Int = 0,
) : MutableObservableCollectionIterator<T, Set<T>, ListIterator<T>>,
ObservableListIterator<T>(observableList, index),
MutableListIterator<T> {
private val mutableList: MutableObservableList<T> = observableList
private var illegalSetOrRemoveState: Boolean? = null
set (value) { if (field != value) field = value }
override fun next(): T {
illegalSetOrRemoveState = false
return super.next()
}
override fun previous(): T {
illegalSetOrRemoveState = false
return super.previous()
}
override fun remove() {
throwIfIllegalState("remove")
mutableList.removeAt(--index)
illegalSetOrRemoveState = true
}
override fun set(element: T) {
throwIfIllegalState("set")
mutableList[index -1] = element
}
override fun add(element: T) {
when {
mutableList.isEmpty() -> mutableList.add(element).run { index = 0 }
index < mutableList.size -> mutableList.add(index++, element)
else -> mutableList.add(element).run { index++ }
}
illegalSetOrRemoveState = true
}
private fun throwIfIllegalState(function: String) {
if (illegalSetOrRemoveState == null) {
throw IllegalStateException(
"Observable List iterator '$function' was called before 'next' or 'previous'."
)
} else if (illegalSetOrRemoveState == true) {
throw IllegalStateException(
"Observable List iterator '$function' was called, " +
"but 'remove' or 'add' was already called this iteration."
)
}
}
}
@@ -0,0 +1,317 @@
package com.cobblemon.mod.common.api.reactive.collections.list
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.reactive.Observable
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.reactive.collections.MutableObservableCollection
import com.cobblemon.mod.common.api.reactive.collections.subscriptions.ListSubscription
import com.cobblemon.mod.common.api.reactive.collections.removeIf
class MutableObservableSubList<T>(
private val parentList: MutableObservableList<T>,
private val fromIndex: Int,
private val toIndex: Int,
elementHandler: (T) -> Set<Observable<*>>,
) : MutableObservableCollection<T, List<T>>,
ObservableSubList<T>(parentList, fromIndex, toIndex, elementHandler),
MutableList<T> {
private val additionObservable = SimpleObservable<Pair<List<T>, T>>()
private val removalObservable = SimpleObservable<Pair<List<T>, T>>()
private val setObservable = SimpleObservable<Pair<List<T>, Pair<T, T>>>()
private val swapObservable = SimpleObservable<Pair<List<T>, Pair<T, T>>>()
override val elements: List<T>
get() = when {
isEmpty() -> emptyList()
parentList.size < maxSize -> parentList.elements.subList(fromIndex, parentList.size)
else -> parentList.elements.subList(fromIndex, currentToIndex)
}
private val maxLastIndex: Int = toIndex - 1
private val maxSize: Int = toIndex - fromIndex
override val size: Int get() = when {
isEmpty() -> 0
parentList.size < toIndex -> parentList.lastIndex - fromIndex
else -> maxLastIndex
}
/** This is the last index in the sublist window, or -1 if the window is empty. */
val currentLastIndex: Int get() = size - 1
/** This is either [fromIndex] or -1 if the sublist window is empty. */
val currentFromIndex: Int get() = if (isEmpty()) -1 else fromIndex
/**
* If [parentList] size > [toIndex] this is the toIndex,
* if the parentList size is > [fromIndex] this is the parentList size,
* or this is the [currentFromIndex].
*/
private val currentToIndex: Int
get() = when {
toIndex < parentList.size -> toIndex
fromIndex < parentList.size -> parentList.size
else -> currentFromIndex
}
private val currentLastIndexInParent: Int
get() = when {
isEmpty() -> fromIndex
parentList.lastIndex < toIndex -> parentList.lastIndex
else -> maxLastIndex
}
init {
parentList.subscribeIndexed(
priority = Priority.NORMAL,
additionHandler = { (_, element, index) -> emitAddition(element, index) },
removalHandler = { (_, element, index) -> emitRemoval(element, index) },
setHandler = { (_, elements, index) -> emitSet(elements, index) },
swapHandler = { (_, element, index) -> emitSwap(element, index) },
)
}
override fun subscribe(
priority: Priority,
anyChangeHandler: (Pair<List<T>, T>) -> Unit,
additionHandler: ((Pair<List<T>, T>) -> Unit)?,
removalHandler: ((Pair<List<T>, T>) -> Unit)?,
): ListSubscription<T> {
return ListSubscription(
anyChange = this.subscribe(priority, anyChangeHandler),
addition = additionHandler?.let { additionObservable.subscribe(priority, it) },
removal = removalHandler?.let { removalObservable.subscribe(priority, it) },
)
}
fun subscribeAndHandle(
priority: Priority,
anyChangeHandler: (Pair<List<T>, T>) -> Unit,
additionHandler: ((Pair<List<T>, T>) -> Unit)?,
removalHandler: ((Pair<List<T>, T>) -> Unit)?,
): ListSubscription<T> {
elements.forEach { anyChangeHandler(this to it) }
return subscribe(priority, anyChangeHandler, additionHandler, removalHandler)
}
fun subscribe(
priority: Priority,
anyChangeHandler: (Pair<List<T>, T>) -> Unit,
additionHandler: ((Pair<List<T>, T>) -> Unit)?,
removalHandler: ((Pair<List<T>, T>) -> Unit)?,
setHandler: ((Pair<List<T>, Pair<T, T>>) -> Unit)?,
swapHandler: ((Pair<List<T>, Pair<T, T>>) -> Unit)?,
): ListSubscription<T> {
@Suppress("DuplicatedCode")
return ListSubscription(
anyChange = this.subscribe(priority, anyChangeHandler),
addition = additionHandler?.let { additionObservable.subscribe(priority, it) },
removal = removalHandler?.let { removalObservable.subscribe(priority, it) },
set = setHandler?.let { setObservable.subscribe(priority, it) },
swap = swapHandler?.let { swapObservable.subscribe(priority, it) },
)
}
fun subscribeAndHandle(
priority: Priority,
anyChangeHandler: (Pair<List<T>, T>) -> Unit,
additionHandler: ((Pair<List<T>, T>) -> Unit)?,
removalHandler: ((Pair<List<T>, T>) -> Unit)?,
setHandler: ((Pair<List<T>, Pair<T, T>>) -> Unit)?,
swapHandler: ((Pair<List<T>, Pair<T, T>>) -> Unit)?,
): ListSubscription<T> {
elements.forEach { anyChangeHandler(this to it) }
return subscribe(priority, anyChangeHandler, additionHandler, removalHandler, setHandler, swapHandler)
}
private fun emitAddition(element: T, index: Int) {
if (index >= toIndex) {
return
}
val elements = this.elements
if (index >= fromIndex) {
handleAddition(elements, element)
} else if(parentList.lastIndex >= fromIndex) {
handleAddition(elements, parentList[fromIndex])
}
if (parentList.size > toIndex) {
handleRemoval(elements, parentList[toIndex])
}
}
private fun emitRemoval(element: T, index: Int) {
if (index >= toIndex) {
return
}
val elements = this.elements
if (index >= fromIndex) {
handleRemoval(elements, element)
} else if(parentList.lastIndex >= fromIndex - 1) {
handleRemoval(elements, parentList[fromIndex - 1])
}
if (currentLastIndexInParent >= toIndex - 1) {
handleAddition(elements, parentList[toIndex - 1])
}
}
private fun emitSet(elementPair: Pair<T, T>, index: Int) {
if (!index.isInParentRelativeWindow()) {
return
}
register(elementPair.first)
unregister(elementPair.second)
setObservable.emit(elements to elementPair)
emitAnyChange(elementPair.first)
emitAnyChange(elementPair.second)
}
private fun emitSwap(elementPair: Pair<T, T>, indices: Pair<Int, Int>) {
val firstInWindow = indices.first.isInParentRelativeWindow()
val secondInWindow = indices.second.isInParentRelativeWindow()
when {
firstInWindow && !secondInWindow -> {
val elements = this.elements
handleRemoval(elements, elementPair.first)
handleAddition(elements, elementPair.second)
}
!firstInWindow && secondInWindow -> {
val elements = this.elements
handleAddition(elements, elementPair.first)
handleRemoval(elements, elementPair.second)
}
firstInWindow && secondInWindow -> {
swapObservable.emit(elements to elementPair)
emitAnyChange(elementPair.first)
elementPair.second?.let { emitAnyChange(it) }
}
}
}
private fun handleAddition(elements: List<T>, element: T) {
register(element)
additionObservable.emit(elements to element)
emitAnyChange(element)
}
private fun handleRemoval(elements: List<T>, element: T) {
unregister(element)
removalObservable.emit(elements to element)
emitAnyChange(element)
}
override operator fun iterator() = MutableObservableSubListIterator(this)
override fun listIterator() = MutableObservableSubListIterator(this)
override fun listIterator(index: Int) = MutableObservableSubListIterator(this, index)
override fun subList(fromIndex: Int, toIndex: Int): MutableObservableSubList<T> {
return parentList.subList(parentIndex(fromIndex), parentIndex(toIndex))
}
fun swapIf(indexOne: Int, indexTwo: Int, predicate: () -> Boolean): Boolean {
return predicate().also { if (it) swap(indexOne, indexTwo) }
}
fun swap(indexOne: Int, indexTwo: Int) {
val parentIndexOne = parentIndex(indexOne)
val one = parentList[parentIndexOne]
val two = parentList.set(parentIndex(indexTwo), one)
parentList[parentIndexOne] = two
}
override fun addIf(element: T, predicate: (List<T>) -> Boolean): Boolean {
return if (predicate(this)) add(element) else false
}
override fun add(element: T): Boolean {
return if (parentList.lastIndex < toIndex) {
parentList.add(element)
} else {
parentList.add(toIndex, element)
true
}
}
fun addAtIf(index: Int, element: T, predicate: (List<T>) -> Boolean): Boolean {
return if (predicate(this)) {
add(index, element)
true
} else {
false
}
}
override fun add(index: Int, element: T) {
parentList.add(parentIndex(index), element)
}
override operator fun set(index: Int, element: T): T {
return parentList.set(parentIndex(index), element)
}
override fun removeIf(predicate: (T) -> Boolean): Boolean = iterator().removeIf(predicate)
override fun remove(element: T): Boolean {
for (i in fromIndex until parentList.size) {
val current = elements[i]
if (current == element) {
parentList.removeAt(i)
return true
}
}
return false
}
override fun removeAt(index: Int): T = parentList.removeAt(parentIndex(index))
override fun addAll(elements: Collection<T>): Boolean {
val parentIndex = if (currentLastIndex > -1) currentLastIndex + fromIndex else fromIndex
return if (parentIndex > parentList.lastIndex) {
parentList.addAll(elements)
} else {
parentList.addAll(parentIndex, elements)
}
}
override fun addAll(index: Int, elements: Collection<T>): Boolean {
return parentList.addAll(parentIndex(index), elements)
}
override fun removeAll(elements: Collection<T>): Boolean {
return iterator().removeIf { elements.contains(it) }
}
override fun retainAll(elements: Collection<T>): Boolean {
return iterator().removeIf { !elements.contains(it) }
}
/**
* This implementation will remove all elements from the window dynamically. If an element
* is removed & an element shifts into the sublist window that meets the predicate, the
* new element will also be removed.
*
* Note: The other iterators including the ObservableSubListIterator will only operate on
* the window provided before the iteration, even when the iterator is mutable.
*/
private fun removeAllIf(predicate: (T) -> Boolean): Boolean {
if (isEmpty()) {
return false
}
var mutated = false
val outOfParentBounds: (Int) -> Boolean = { it == parentList.size }
for (i in fromIndex until currentToIndex) {
if (outOfParentBounds(i)) {
return mutated
}
while (predicate(parentList[i])) {
parentList.removeAt(i).also { if (!mutated) mutated = true }
if (outOfParentBounds(i)) {
return true
}
}
}
return mutated
}
override fun clear() {
if (isNotEmpty()) {
iterator().removeIf { true }
}
}
}
Loaded 100 of 986 files, more files were not shown because too many files have changed in this diff. Show more