Compare commits

...
Author SHA1 Message Date
plastered_crab 7a141e0c6e Start to testing tasks for Teddiursa to steal honey and slather trees 2025-08-09 07:10:08 -07:00
plastered_crab 638a9e77d7 Start to testing tasks for Teddiursa to steal honey and slather trees 2025-08-09 06:35:35 -07:00
Polymeta 50c3ffc774 Merge branch 'bugfix-entity-health' into 'main'
Couple Pokémon HP / Entity health in a more sensible way.

See merge request cable-mc/cobblemon!1750
2025-07-28 08:58:23 +00:00
Polymeta daf32a5cf3 Merge branch 'food-tweaks' into 'main'
Eat your, well, lots of things

Closes #1616

See merge request cable-mc/cobblemon!1756
2025-07-28 08:56:41 +00:00
dhi_holoandPolymeta af4d2077ae Eat your, well, lots of things 2025-07-28 08:56:41 +00:00
Polymeta b1a3cbf29d Merge branch 'cleanup-crew' into 'main'
Small Fixes

Closes #1640 and #1199

See merge request cable-mc/cobblemon!1753
2025-07-27 21:08:36 +00:00
dhi_holoandPolymeta 130c50a2d6 Small Fixes 2025-07-27 21:08:35 +00:00
Hiroku 0b65259ee7 Merge branch 'let-me-break-it-down-for-you-mark-we-can-bee-bees' into 'main'
Species JSON dictates if something acts like a bee (pollinates flowers and delivers to hive/nest)

See merge request cable-mc/cobblemon!1709
2025-07-27 12:16:12 +00:00
Paul 53897a2613 Removed debug code 2025-07-27 22:04:17 +10:00
Hiroku 3318ee801a Merge branch 'extra_flows' into 'main'
Many more Pokemon function refinements and Molang Flows

See merge request cable-mc/cobblemon!1710
2025-07-26 11:04:31 +00:00
Hiroku 0b97d139af Edit CobblemonFlows.kt 2025-07-26 10:51:35 +00:00
Paul b7c5f489fd More cleanup 2025-07-26 14:31:57 +10:00
Paul 39ec6187b5 Various cleanup items and important fixes for wandering caused by bee stuff 2025-07-26 14:29:05 +10:00
Paul b7681b43f9 Merge remote-tracking branch 'origin/main' into let-me-break-it-down-for-you-mark-we-can-bee-bees 2025-07-26 13:26:19 +10:00
Konoha ae594a0790 Merge branch 'main' into extra_flows
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/molang/MoLangFunctions.kt
2025-07-24 08:04:14 -04:00
Konoha 6af299406d Merge remote-tracking branch 'origin/main' 2025-07-24 07:57:51 -04:00
Nadja Reitzenstein 29833df50e Couple Pokémon HP / Entity health in a more sensible way.
Specifically, don't touch Battle HP for wild Pokémon so that you can't hurt a Pokémon down out of battle for an easier catch, and always round entity health up so that only a HP of 0 is a health of 0
2025-07-24 13:09:33 +02:00
Paul 01d42abdb2 look_at_target requires nearest living entities memory and sensor 2025-07-23 23:16:32 +10:00
Paul baa3f47769 Removed unused memory 2025-07-23 19:11:19 +10:00
Paul 16e716411a Merge remote-tracking branch 'origin/main' into let-me-break-it-down-for-you-mark-we-can-bee-bees
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/npc/NPCBrain.kt
2025-07-23 18:58:19 +10:00
Paul 3bbc551fd7 Wander controls supporting keeping wanderers around a point, bees stay around their hive 2025-07-23 18:55:52 +10:00
Polymeta 74381d8e3c move mixin away from ModifyConstant to improve compatibility with other mods
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-07-22 23:48:21 +01:00
Polymeta 6688c32a1d Merge branch 'smashling' into 'main'
Create saccharine_sapling.json

See merge request cable-mc/cobblemon!1749
2025-07-22 21:39:24 +00:00
dhi_holo 45dcf6f23b Create saccharine_sapling.json 2025-07-22 17:02:54 +10:00
Polymeta e4d92ead6a Merge branch 'log-spawn' into 'main'
Update saccharine log spawning

See merge request cable-mc/cobblemon!1742
2025-07-19 20:11:01 +00:00
TestandPolymeta 9631f3c9d5 Update saccharine log spawning 2025-07-19 20:11:01 +00:00
Polymeta 0787f2772d Merge branch '1.7-Tree-and-Crop-Generation' into 'main'
SaccharineTreeFeature to consider #cobblemon:has_feature/saccharine_tree

See merge request cable-mc/cobblemon!1748
2025-07-19 19:24:00 +00:00
Polymeta 4011502e54 changelog and license
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-07-19 08:35:51 +01:00
Polymeta 06855f6404 adding placed features for grains and galarica nuts
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-07-19 08:17:37 +01:00
Slippy 815955b904 Hearty Grains, Galarica Nut Bushes, and Saccharine Trees now generate with appropriate rates and locations. Just need Poly to do the mixin work 2025-07-19 01:50:24 -05:00
Polymeta df9eed0da3 Merge branch '1,21-Gigawatts' into 'main'
Lightning Interactions with Pokemon

See merge request cable-mc/cobblemon!1733
2025-07-19 06:08:35 +00:00
JazzMcNadeandPolymeta 99b73aa8dd Lightning Interactions with Pokemon 2025-07-19 06:08:34 +00:00
Polymeta f2d5073c7c enforce fabric versions to match what cobblemon is built against for consistency
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-07-19 00:02:49 +01:00
Paul 3fda5f7c7a give all NPCs the dialogues and battling memories 2025-07-18 18:18:44 +10:00
Hiroku 9b81666437 Merge branch 'pokedex-percentage-command' into 'main'
Fix Pokedex Percentage Command

Closes #1525

See merge request cable-mc/cobblemon!1692
2025-07-18 04:58:34 +00:00
CeejayandHiroku b43f969c02 Fix Pokedex Percentage Command 2025-07-18 04:58:34 +00:00
Slippy 6158b36103 Saccharine trees are now rarer and generate in the correct biomes 2025-07-16 12:18:39 -05:00
Polymeta 2e14f8f8e8 hearty grains will generate with both blocks when of the right age
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-07-16 10:22:48 +01:00
Polymeta bc80d6a171 SaccharineTreeFeature to consider #cobblemon:has_feature/saccharine_tree
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-07-16 08:30:32 +01:00
Konoha 65a7fc6fd7 Implement includeLegacy parameters 2025-07-15 22:11:47 -04:00
Konoha ce019631ce Merge branch 'main' into extra_flows 2025-07-15 21:58:19 -04:00
Konoha 0534e0d201 Merge remote-tracking branch 'origin/main' 2025-07-15 21:51:31 -04:00
Konoha 895e3ca378 Update Benched Moves after teachLearnableMoves call 2025-07-15 21:51:14 -04:00
Konoha 6a7b71d5a7 Cleanup 2025-07-11 22:30:30 -04:00
Konoha a56ffe1742 Merge remote-tracking branch 'origin/main' 2025-07-11 11:04:14 -04:00
Paul 599603501a Combee should fly instead of walk, and not fly crazy fast 2025-07-05 22:42:30 +10:00
Paul 891ed6045d This is good news 2025-07-05 22:30:07 +10:00
Paul a15aa86c66 Merge remote-tracking branch 'origin/we-must-defend-our-way-of-life' into let-me-break-it-down-for-you-mark-we-can-bee-bees
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/TaskConfig.kt
2025-07-05 10:14:41 +10:00
Konoha 1984990a7e Array Utilities 2025-06-29 08:42:28 -04:00
Konoha 49e2e635db Ability Pokemon Function 2025-06-28 19:09:37 -04:00
Konoha 62e9294c8a Merge remote-tracking branch 'origin/main' 2025-06-28 11:41:40 -04:00
Konoha d795dca4fa Inventory player function 2025-06-27 16:02:18 -04:00
Konoha 503511e0ae teach_move, can_learn_move, set_iv, set_ev` Pokemon functions 2025-06-27 14:39:04 -04:00
Konoha bd4397ec55 Merge remote-tracking branch 'origin/main' 2025-06-25 21:11:08 -04:00
Konoha d5e3781fc5 Merge remote-tracking branch 'origin/legacy_and_event_moves' 2025-06-25 21:10:53 -04:00
Konoha a7f8a281a8 Minor comment fix 2025-06-25 19:53:54 -04:00
Konoha 5ba6679a84 Changelog 2025-06-25 19:49:17 -04:00
Konoha 4ecd64bdf6 Set Level and clone party for NPC / MoLang battles 2025-06-25 19:46:51 -04:00
Konoha 6863cd0602 Battle Format Parameters 2025-06-24 22:03:49 -04:00
Konoha b9e6d63049 Extra Battle Format MoLang parameters 2025-06-22 21:52:34 -04:00
Konoha cb002ff530 Battle Format and Rule Selection via MoLang Test 2025-06-22 13:34:50 -04:00
Konoha 19da349e78 Merge AI Shenanigans 2025-06-22 11:25:28 -04:00
Konoha 30c3b605a3 Merge branch 'refs/heads/feature/ai-shenanigans' into extra_flows
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/entity/npc/NPCServerDelegate.kt
2025-06-22 11:23:06 -04:00
Konoha b8e79d2d3e Attempt to not publish HELD_ITEM_PRE on the client (spoilers, it doesn't work :[ ) 2025-06-21 22:27:02 -04:00
Konoha 60c2721b53 Cleanup LootDroppedEvent Flow 2025-06-21 20:50:46 -04:00
Konoha b6f2ab25e1 Fix berry in BerryHarvestEvent 2025-06-21 20:49:07 -04:00
Konoha 6417f8cfa8 Cleanup 2025-06-21 20:45:18 -04:00
Konoha 1d473437de MoLang moveset queries
Viewable Hyper Trained IVs in PC
2025-06-21 20:19:41 -04:00
Konoha c24b60785f Player battle function and battle format setting parameters. 2025-06-21 14:17:31 -04:00
Konoha 08df2fef6c Molang setter functions for Party & PC 2025-06-21 12:13:07 -04:00
plastered_crab bae3da9411 Merge branch 'refs/heads/main' into let-me-break-it-down-for-you-mark-we-can-bee-bees
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonMemories.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/TaskConfig.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/pokemon/ai/PokemonBrain.kt
2025-06-20 06:07:01 -07:00
Konoha db7745589a Cleanup 2025-06-19 21:27:55 -04:00
Konoha 65f0f69726 Teach All Learnable Moves MoLang Function 2025-06-19 21:25:26 -04:00
Konoha 19263ddec6 Minor fix 2025-06-19 17:29:40 -04:00
Konoha dde4680ce0 Refactor Client Context on HeldItemEvent 2025-06-19 16:13:36 -04:00
Konoha a2939e34a0 Cosmetic Item Property, MoLang functions for fullness, unlearning moves. 2025-06-19 14:25:20 -04:00
plastered_crab 6a937492dd Licenses 2025-06-19 07:37:53 -07:00
Konoha d13e456d17 Minor Optimization ft. landon 2025-06-17 22:08:29 -04:00
Konoha e36de84927 1.7: The Flowing Mapmaker's Update (Friendship, Fullness, AspectChanged, CatchRate, PokeRod, ShoulderMount event flows) 2025-06-17 22:05:44 -04:00
Konoha 54cbaa9a10 Flows for PokemonHealed, PokemonSent, and ChangePCBoxWallpaper. 2025-06-17 12:43:24 -04:00
Konoha dbe743b50a Do not publish HelpItemEvent if it's running on client. 2025-06-17 08:47:38 -04:00
Konoha 7d26870a13 Pokemon Scanned, Loot Dropped, Berry Harvest Flows 2025-06-16 14:50:53 -04:00
Konoha a418cbd118 Pokemon Seen Flow 2025-06-16 12:03:00 -04:00
Konoha e45af26af3 Split Cosmetic Item Event from HeldItemEvent. 2025-06-16 10:08:52 -04:00
Konoha bdc260abca Aspects MoLang function for Pokemon 2025-06-15 21:54:39 -04:00
Konoha d9e5e52d9f Flows for ExperienceGainedEvent, CollectEggEvent, HatchEggEvent. MoLang functions to add exp, initialize a Pokemon's moveset, and validate an existing moveset. 2025-06-15 21:31:10 -04:00
Polymeta c12f991b18 testing round no1
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-06-13 01:20:15 +01:00
Polymeta 977d66dafd fixing some errors
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-06-13 01:05:55 +01:00
Polymeta f0b1843b71 StrongBattleAI without build errors
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-06-13 01:05:54 +01:00
Polymeta 785e63c7d4 rafactoring of the ActiveTracker to be on a "seen" basis with opposing pokemon
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-06-13 01:05:54 +01:00
Polymeta 61c3c995aa tidying up
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-06-13 01:05:43 +01:00
plastered_crab 5dc56bd4fc Merge branch 'refs/heads/main' into let-me-break-it-down-for-you-mark-we-can-bee-bees
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/ai/config/task/TaskConfig.kt
2025-06-10 18:11:57 -07:00
plastered_crab f9afd1d3e6 My kid pooped his pants and fell back asleep so let's code real fast 2025-06-10 02:46:26 -07:00
plastered_crab ad3cfe3708 You'll be a pet 2025-06-01 14:14:31 -07:00
plastered_crab 109895c0ff Mark this is good news 2025-05-31 17:13:36 -07:00
plastered_crab 96bcffcc84 We can finally be bees Mark 2025-05-31 14:29:35 -07:00
plastered_crab dc4f204db6 This is not our home 2025-05-31 01:11:50 -07:00
plastered_crab c07c78c331 You'll live for 30 years 2025-05-31 00:39:00 -07:00
Plastered_Crab e902e034ad This is good news 2025-05-30 14:19:31 -07:00
Plastered_Crab 77218f5250 Mark, we can BEE bees 2025-05-30 13:23:16 -07:00
Konoha 0cda6d13b7 Merge branch 'implement_pokemon' into legacy_and_event_moves 2025-05-24 09:05:18 -04:00
Konoha 1d40d424d0 Update changelog 2025-04-15 10:31:32 -04:00
Konoha 938721c0bb Merge branch 'implement_pokemon' into legacy_and_event_moves 2025-04-15 09:11:50 -04:00
Konoha 52533236ca Merge branch 'implement_pokemon' into legacy_and_event_moves 2025-04-14 07:56:05 -04:00
Konoha d560918b55 Merge branch 'main' into legacy_and_event_moves
# Conflicts:
#	common/src/main/resources/data/cobblemon/species/generation1/farfetchd.json
#	common/src/main/resources/data/cobblemon/species/generation7/oricorio.json
#	common/src/main/resources/data/cobblemon/species/generation8/sirfetchd.json
2025-04-01 08:05:27 -04:00
Konoha 427bf34a56 Add Legal move query 2025-02-12 20:15:39 -05:00
Konoha 479daa49ce Merge branch 'implement_pokemon' into legacy_and_event_moves 2025-02-12 20:10:03 -05:00
Konoha 729156924d Fix post-processing errors and Genesect form conventions 2025-02-12 20:09:33 -05:00
Konoha a34db1dc1b Legacy and event move support 2025-02-01 09:37:12 -05:00
1119 changed files with 19373 additions and 3620 deletions
+2 -1
View File
@@ -8,7 +8,8 @@
- Lure Cakes can be used to modify the spawn rate of Pokémon in the wild.
- Added the Saccharine Tree, which can be found in the wild and is used to obtain Honey Bottles.
- The leaves of the tree can be harvested to obtain honey, which can be used in the Campfire Pot to make Lure Cakes.
- The Log of the tree can be clicked on with honey to create a Honey Log, which increases the change of spawning a Pokemon with an Hidden Ability.
- The Log of the tree can be interacted with using a honey bottle to create a Honey-Slathered Saccharine Log, which increases the change of spawning a Pokémon with a Hidden Ability.
- Added Hearty Grains
- Clicking on the Progress Arrow will show the JEI Recipe Book for the Campfire Pot, which will show all recipes that can be made with it.
- Added a Ton of Redstone Compatibility.
- Dispenser can now Honey and Unhoney:
+32 -1
View File
@@ -18,6 +18,7 @@
- Added cosmetics for Gurdurr, Conkeldurr, Squirtle Line, Sneasler, Sandle line, Treecko line, Braixen, Delphox, and Dragonite.
- Added `/transformmodelpart (position|rotation|scale) <modelPart> <transform: x y z>` command that can add transformations to a pokemon's model part.
- The player executing the command must be facing the target pokemon entity. Transformations are not persistent and will revert when resources are reloaded.
- Added `legacy` and `special` sourced moves to Pokémon.
- Added lang keys for all moves and abilities up to Generation 9.
- Added `translucent_cull` boolean option into resolver's layer to allow for translucent textures with culling
- Added [LambDynamicLights](https://modrinth.com/mod/lambdynamiclights) support for items held by Pokémon, evolution stone blocks, evolution stone items, Pokédex, Luminous Moss, Flame Orb, and Magmarizer.
@@ -32,8 +33,14 @@
- Added Hyper Training items (IV Modification) as well as some additional candy items to do so (Health Candy, Sickly Candy)
- Added Galarica Nut Bushes
- Many Pokémon (mostly cats) are now feared by phantoms
- Lightning is now affected by a Pokemon's ability/typing
- Pokémon with the ability Lightning Rod draw in lightning similar to a lightning rod block albeit with a lower priority and range, gain immunity to lightning damage, and receive a temporary damage buff.
- Pokémon with the ability Motor Drive are immune to lightning damage and receive a temporary speed buff when struck by lightning
- Pokémon with the ability Volt Absorb are immune to lightning damage and receive Regeneration II for a short duration
- Ground type Pokémon are immune to lightning damage
- Added functionality to Everstone when held by a Pokémon; suppresses evolution notification and hides evolve button in summary interface.
- Added new optional property `attachment_options` for most EmitterShapes to be attached to the locator/entities scale, rotation, and/or position. Position is true by default.
- Galarica Nut bushes now generate on beaches
### Pokémon Added
@@ -231,6 +238,8 @@
- Healing Machine recipe rebalanced.
- Reorganised the `block` texture folder to be more organised, in line with the `item` texture folder.
- Offset in EmitterShape now ignores scale to be more like Blockbench by default. You can get this behaviour back by adding `"scale": true` in the `attachment_options` property in most EmitterShapes.
- Not specifying a dex in `/pokedex printcalculations {player} {dex}` will now print the National Dex statistics instead of showing all dexes. `/pokedex printcalculations {player} all` is how to view all dex statistics in one command.
- Removed Braised Vivichoke
### Fixes
- Fixed game crashing when removing national pokedex using datapacks
@@ -260,6 +269,7 @@
- Fixed Pokémon marked as silent still playing shiny sounds and effects.
- Fixed an issue with newer versions of Fabric API where underground Pokémon were spawning in The End.
- Fixed spawning not working well when you're at high points surrounded by lower altitude spawning areas, such as flying.
- Fixed some Pokémon having erroneous tutor moves if another move included a valid tutor move as a substring.
- Fixed certain Pokémon with forms not having appropriate stock Pokédex entries.
- Fixed issue with Pokédex Scanner that caused the open/close overlay to have the wrong opacity values
- Fixed dragon's breath not being usable on the restoration tank when it should be
@@ -295,6 +305,8 @@
- Fixed Pokédex Scanner not respecting the "Invert Mouse" option.
- Fixed a crash due to a ConcurrentModificationException that could occur during world generation.
- Fixed Moon Ball moon phase logic to actually work correctly
- Fixed `/pokedex printcalculations` to now show the correct percentage completed of the Pokedex
- Fixed mod incompatibility with the `Raised` mod
### Developer
- A finished battle now has winners and losers set inside of `PokemonBattle` instead of them always being empty.
@@ -313,6 +325,11 @@
- Updated NPCEntity pokeball throw positioning to properly account for the baseScale property.
- Fixed `[Pokemon].copyFrom` error causing forms, IVs, and EVs to not be applied properly when using `[Pokemon].loadFromJSON` or `[Pokemon].loadFromNBT`
- Added new item class, `WearableItem`. Instances of this class should have a corresponding 3D model. These models render when the items display context is `HEAD`.
- Added new LearnsetQuery types:
- `LEGAL` for moves that are innately compatible and learnable by the Pokémon.
- `LEGACY` for moves that were once officially learnable by the Pokémon but aren't due to GameFreak's re-balancing.
- `SPECIAL` for moves that are not learnable by the Pokémon but may have appeared in a special event or distribution.
- Pokemon now have a fireImmune attribute in their behaviour that can be set to true to ignore all fire damage (lava, magma blocks, etc.)
`JSON
{
@@ -324,6 +341,7 @@
- The IVs class has now been extended to include Hyper Trained values.
- Added `Pokemon#hyperTrainIV()` and `IVs#setHyperTrainedIV(Stat, Int)`.
- Added `HyperTrainedIvEvent.Pre` and `HyperTrainedIvEvent.Post`.
- Added `Pokemon#validateMoveSet()` to validate an existing Pokemon's moveset, clearing illegal moves.
- Added a `hoverText` option to PartySelectCallback, to display a tooltip on hovering over a Pokémon in the selection screen.
- `PokemonEntity` instances spawned into the world now appropriately finalize the spawn for mod compatibility.
- Added PokedexManager.obtain as a replacement for .catch which is not a friendly function name in Java.
@@ -348,6 +366,9 @@
- Added `pokemon` as an available Molang function for the `battleActor` functions.
- Fixed `heldItem` property inside spawn files not working and causing crashes
- Fixed `spawn_bedrock_particles` MoLang causing crashes when used in a server environment
- The following move sources are now valid for the `moves` array in species data:
- `legacy:{move}`
- `special:{move}`
- The Pokédex form lang key definition now follows `cobblemon.ui.pokedex.info.form.{species}-{formname}` instead of `cobblemon.ui.pokedex.info.form.{formname}`.
- Added `play_sound_on_server` as an available Molang function for the `worldHolder` & `player` functions.
- Added `run_molang_after` as an available Molang function for the `entity` functions when schedulable.
@@ -357,8 +378,18 @@
- The format of the `remedies.json` file has changed to allow for individual friendshipDrop amounts per remedy
- Fixed `entity.find_nearby_block` causing crashes when attempting to use a block tag
- Spawn Filters can now access `v.spawn.class` to get the identifier of an NPC class for when trying to influence NPC spawns
- Added Molang functions for Party and PC: `set_pokemon`
- Added Molang functions for Pokémon: `pokeball`, `held_item`, `remove_held_item`, `hyper_train_iv`, `validate_moveset`, `initialize_moveset`, and `add_exp`.
- Added Molang functions for Pokémon: `aspects`, `form_aspects`, `unlearn_move`, `teach_learnable_moves`, `cosmetic_item`, and `remove_cosmetic_item`.
- Added Molang functions for Pokémon: `ability`, `set_iv`, `set_ev`, `teach_move`, and `can_learn_move`.
- Adds Flows for `STARTER_CHOSEN`, `SHOULDER_MOUNTED`, `EV_GAINED`, `POKEMON_RELEASED`, `POKEMON_NICKNAMED`, `HELD_ITEM`, and `TRADE_COMPLETED` events
- Adds Flows for `POKEMON_HEALED`, `POKEMON_SCANNED`, `BERRY_HARVEST`, `LOOT_DROPPED`, `POKEMON_SEEN`, `COLLECT_EGG`, `HATCH_EGG`, and `EXPERIENCE_GAINED`.
- Adds Flows for `POKEMON_CATCH_RATE`, `BAIT_SET`, `BAIT_SET_PRE`, `BAIT_CONSUMED`, `POKEROD_CAST_PRE`, `POKEROD_CAST_POST`, `POKEROD_REEL`, and `BOBBER_SPAWN_POKEMON_PRE`.
- Adds Flows for `POKEMON_ASPECTS_CHANGED`, `FRIENDSHIP_UPDATED`, `CHANGE_PC_BOX_WALLPAPER_EVENT_PRE`, `CHANGE_PC_BOX_WALLPAPER_EVENT_POST`, and `FULLNESS_UPDATED`.
- MoLang triggered battles may now set the battle format, whether to clone the player's party, set level, or heal prior.
- Added Molang function for Player: `inventory`
- Adds Flows for `STARTER_CHOSEN`, `EV_GAINED`, `POKEMON_RELEASED`, `POKEMON_NICKNAMED`, `HELD_ITEM`, and `TRADE_COMPLETED` events
- Adds Pokemon functions for `pokeball`, `held_item` and `remove_held_item`
- Adds Pokemon functions for `pokeball`, `held_item`, `remove_held_item`, `add_aspects`, and `remove_aspects`
- Added `pokemon.hyper_train_iv` as an available Molang function.
## [1.6.1 (January 26th, 2025)](#1-6-1)
@@ -0,0 +1,44 @@
/*
* 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.entity.pokemon.PokemonEntity;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LightningBolt;
import net.minecraft.world.phys.AABB;
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;
import java.util.List;
@Mixin(LightningBolt.class)
public abstract class LightningBoltMixin {
@Inject(method = "spawnFire", at = @At(value = "Head"), cancellable = true)
private void cobblemon$spawnFire(int extraIgnitions, CallbackInfo ci) {
// This prevents fire from spawning around Pokemon who otherwise should be immune to lightning and effectively absorb the hit
final LightningBolt lightningBolt = (LightningBolt) (Object) this;
List<Entity> list = lightningBolt.level().getEntities(
lightningBolt,
new AABB(lightningBolt.getX() - 2.0, lightningBolt.getY() - 2.0, lightningBolt.getZ() - 2.0, lightningBolt.getX() + 2.0, lightningBolt.getY() + 2.0, lightningBolt.getZ() + 2.0),
it -> {
if (!it.isAlive() || !(it instanceof PokemonEntity)) {
return false;
}
String abilityName = ((PokemonEntity) it).getPokemon().getAbility().getName();
return abilityName.equals("lightningrod") || abilityName.equals("voltabsorb") || abilityName.equals("motordrive");
});
if (!list.isEmpty()) {
ci.cancel();
}
}
}
@@ -0,0 +1,44 @@
/*
* 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.entity.pokemon.PokemonEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.phys.AABB;
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.CallbackInfoReturnable;
import java.util.List;
import java.util.Optional;
@Mixin(ServerLevel.class)
public abstract class ServerLevelMixin {
@Inject(method = "findLightningRod", at = @At(value = "RETURN"), cancellable = true)
private void cobblemon$findLightningRod(BlockPos blockPos, CallbackInfoReturnable<Optional<BlockPos>> ci) {
final ServerLevel serverLevel = (ServerLevel) (Object) this;
if (ci.getReturnValue().isEmpty()) {
// Search for Pokemon with the ability Lightning Rod and redirect to them
AABB aABB = AABB.encapsulatingFullBlocks(blockPos, new BlockPos(blockPos.atY(serverLevel.getMaxBuildHeight()))).inflate(64.0);
List<LivingEntity> list = serverLevel.getEntitiesOfClass(LivingEntity.class, aABB, (livingEntity) -> livingEntity != null
&& livingEntity.isAlive()
&& livingEntity instanceof PokemonEntity
&& ((PokemonEntity) livingEntity).getBeamMode() == 0
&& ((PokemonEntity) livingEntity).getPokemon().getAbility().getName().equals("lightningrod"));
if (!list.isEmpty()) {
ci.setReturnValue(Optional.of((list.get(serverLevel.random.nextInt(list.size()))).blockPosition()));
}
}
}
}
@@ -0,0 +1,50 @@
/*
* 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.block.HeartyGrainsBlock;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.DoublePlantBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.SimpleBlockFeature;
import net.minecraft.world.level.levelgen.feature.configurations.SimpleBlockConfiguration;
import org.spongepowered.asm.mixin.Debug;
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.CallbackInfoReturnable;
@Mixin(SimpleBlockFeature.class)
@Debug(export = true, print = true)
public class SimpleBlockFeatureMixin {
//this mixin inserts the logic needed to ensure that when hearty grains are of a certain age, they place with the upper block too
//similarly to what the doubleblock logic is here
@Inject(method = "place", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockState;canSurvive(Lnet/minecraft/world/level/LevelReader;Lnet/minecraft/core/BlockPos;)Z", shift = At.Shift.AFTER), cancellable = true)
public void place(FeaturePlaceContext<SimpleBlockConfiguration> context, CallbackInfoReturnable<Boolean> cir,
@Local BlockState blockState, @Local BlockPos blockPos, @Local WorldGenLevel worldGenLevel) {
if (!(blockState.getBlock() instanceof HeartyGrainsBlock)) {
return;
}
if (blockState.getValue(HeartyGrainsBlock.Companion.getAGE()) <= HeartyGrainsBlock.AGE_AFTER_HARVEST) {
return; //block is too young to be double
}
if (!worldGenLevel.isEmptyBlock(blockPos.above())) {
cir.setReturnValue(false); //no space for the 2nd block above
return;
}
DoublePlantBlock.placeAt(worldGenLevel, blockState, blockPos, Block.UPDATE_CLIENTS);
cir.setReturnValue(true);
}
}
@@ -9,22 +9,21 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.client.gui.ScrollingWidget;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import net.minecraft.client.gui.components.AbstractSelectionList;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.ModifyConstant;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(AbstractSelectionList.class)
public abstract class EntryListWidgetMixin {
@SuppressWarnings("ConstantValue")
@ModifyConstant(method = "getEntryAtPosition", constant = @Constant(intValue = 4))
public int cobblemon$adjustOnlyIfNecessary(int input) {
@ModifyExpressionValue(method = "getEntryAtPosition", at = @At(value = "CONSTANT", args = {"intValue=4"}))
public int cobblemon$adjustOnlyIfNecessary(int original) {
if(ScrollingWidget.class.isInstance(this)) {
return 0;
}
return input;
return original;
}
}
@@ -19,6 +19,9 @@ object CobblemonActivities {
val POKEMON_GROW_CROP = register(Activity("pokemon_grow_crop"))
val POKEMON_SLEEP_ON_TRAINER_BED = register(Activity("pokemon_sleep_on_trainer_bed"))
val POKEMON_HERD = register(Activity("pokemon_herd"))
val POKEMON_POLLINATION = register(Activity("pokemon_pollination"))
val POKEMON_STEAL_HONEY = register(Activity("pokemon_steal_honey"))
fun register(activity: Activity): Activity {
activities.add(activity)
@@ -78,6 +78,9 @@ object CobblemonBehaviours : JsonDataRegistry<CobblemonBehaviour> {
*/
val autoPokemonBehaviours = mutableListOf<CobblemonBehaviour>()
/** See [autoPokemonBehaviours] but think npc instead of pokemon*/
val autoNPCBehaviours = mutableListOf<CobblemonBehaviour>()
val behaviours = mutableMapOf<ResourceLocation, CobblemonBehaviour>()
override fun sync(player: ServerPlayer) {
@@ -94,6 +97,8 @@ object CobblemonBehaviours : JsonDataRegistry<CobblemonBehaviour> {
val behaviour = parse(reader, resolvedIdentifier)
if ("pokemon/auto/" in identifier.path) {
autoPokemonBehaviours.add(behaviour)
} else if ("npc/auto/" in identifier.path) {
autoNPCBehaviours.add(behaviour)
}
data[resolvedIdentifier] = behaviour
}
@@ -93,6 +93,8 @@ object CobblemonFlows : DataRegistry {
functions: Map<String, (MoParams) -> Any> = emptyMap(),
cancelable: Cancelable? = null
) {
if (eventResourceLocation !in flows) return
if (cancelable == null) {
runtime.environment.query.functions.remove("cancel")
} else {
@@ -389,40 +389,28 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val SWEET_HEART = noSettingsItem("sweet_heart") // todo make a SweetHeartItem class for breeding purposes
@JvmField
val TASTY_TAIL = noSettingsItem("tasty_tail")
val TASTY_TAIL = create("tasty_tail", foodItem(3, 0.3f))
@JvmField // todo get final values for this from vera
val PEWTER_CRUNCHIES = regionalFoodItem("pewter_crunchies", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f, false)
@JvmField // todo get final values for this from vera
val RAGE_CANDY_BAR = regionalFoodItem("rage_candy_bar", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f, false)
@JvmField // todo get final values for this from vera
val LAVA_COOKIE = regionalFoodItem("lava_cookie", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val OLD_GATEAU = regionalFoodItem("old_gateau", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val CASTELIACONE = regionalFoodItem("casteliacone", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val SHALOUR_SABLE = regionalFoodItem("shalour_sable", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val LUMIOSE_GALETTE = regionalFoodItem("lumiose_galette", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val BIG_MALASADA = regionalFoodItem("big_malasada", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f, false, ItemStack(Items.BOWL, 1))
@JvmField // todo get final values for this from vera
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField // todo get final values for this from vera
val OPEN_FACED_SANDWICH = regionalFoodItem("open_faced_sandwich", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField
val PEWTER_CRUNCHIES = regionalFoodItem("pewter_crunchies", 16, 7, 0.343f, false)
@JvmField
val RAGE_CANDY_BAR = regionalFoodItem("rage_candy_bar", 16, 8, 0.2f, false)
@JvmField
val LAVA_COOKIE = regionalFoodItem("lava_cookie", 16, 7, 0.343f, false)
@JvmField
val OLD_GATEAU = regionalFoodItem("old_gateau", 16, 9, 0.1335f, false)
@JvmField
val CASTELIACONE = regionalFoodItem("casteliacone", 16, 5, .28f, false)
@JvmField
val LUMIOSE_GALETTE = regionalFoodItem("lumiose_galette", 16, 8, 0.2f, false)
@JvmField
val BIG_MALASADA = regionalFoodItem("big_malasada", 16, 7, 0.343f, false)
@JvmField
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 16, 10, 0.6f, false, ItemStack(Items.BOWL, 1))
@JvmField
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 16, 8, 0.2f, false)
@JvmField
val OPEN_FACED_SANDWICH = regionalFoodItem("open_faced_sandwich", 16, 13, 0.5f, false)
// todo we might need to wait on these for later? These impact battles and may be harder to do
/*@JvmField
@@ -435,30 +423,22 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val POTATO_MOCHI = create("potato_mochi", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.alwaysEdible()
.build())))
@JvmField
val FRIED_RICE = create("fried_rice", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.alwaysEdible()
.nutrition(8)
.saturationModifier(0.2375F)
.build())))
@JvmField
val CANDIED_APPLE = create("candied_apple", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.alwaysEdible()
.nutrition(6)
.saturationModifier(0.2335F)
.usingConvertsTo(Items.STICK)
.build())))
@JvmField
val CANDIED_BERRY = create("candied_berry", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.alwaysEdible()
.nutrition(5)
.saturationModifier(0.22F)
.usingConvertsTo(Items.STICK)
.build())))
//@JvmField
@@ -685,14 +665,12 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val MEDICINAL_LEEK = heldItem("medicinal_leek", MedicinalLeekItem(CobblemonBlocks.MEDICINAL_LEEK, Item.Properties().food(FoodProperties.Builder().fast().nutrition(1).saturationModifier(0.2f).build())), "leek")
@JvmField
val ROASTED_LEEK = compostableItem("roasted_leek", CobblemonItem(Item.Properties().food(FoodProperties.Builder().fast().nutrition(3).saturationModifier(0.3f).build())), 0.85f)
@JvmField
val BRAISED_VIVICHOKE = compostableItem("braised_vivichoke", foodItem(6, 0.6F), 0.85f)
val ROASTED_LEEK = compostableItem("roasted_leek", CobblemonItem(Item.Properties().food(FoodProperties.Builder().fast().nutrition(3).saturationModifier(0.2f).build())), 0.85f)
@JvmField
val VIVICHOKE_DIP = create("vivichoke_dip", object : CobblemonItem(Properties().stacksTo(1)
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.saturationModifier(0.6F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.usingConvertsTo(Items.BOWL)
@@ -703,7 +681,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
}
})
@JvmField
val ENERGY_ROOT = compostableItem("energy_root", EnergyRootItem(CobblemonBlocks.ENERGY_ROOT, Item.Properties().food(FoodProperties.Builder().nutrition(1).fast().saturationModifier(0.2f).build())), 0.60f)
val ENERGY_ROOT = compostableItem("energy_root", EnergyRootItem(CobblemonBlocks.ENERGY_ROOT, Properties()), 0.60f)
@JvmField
val REVIVAL_HERB = compostableItem("revival_herb", RevivalHerbItem(CobblemonBlocks.REVIVAL_HERB))
@JvmField
@@ -733,7 +711,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val HEAL_POWDER = create("heal_powder", HealPowderItem())
@JvmField
val LEEK_AND_POTATO_STEW = create("leek_and_potato_stew", CobblemonItem(Item.Properties().food(FoodProperties.Builder().nutrition(8).saturationModifier(0.6f).usingConvertsTo(Items.BOWL).build()).stacksTo(1)))
val LEEK_AND_POTATO_STEW = create("leek_and_potato_stew", CobblemonItem(Item.Properties().food(FoodProperties.Builder().nutrition(6).saturationModifier(0.6f).usingConvertsTo(Items.BOWL).build()).stacksTo(1)))
@JvmField
val REVIVE = create("revive", ReviveItem(max = false))
@JvmField
@@ -1544,8 +1522,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
stacksTo: Int,
nutrition: Int,
saturationModifier: Float,
effect: MobEffectInstance? = null,
effectChance: Float? = null,
alwaysEdible: Boolean = false,
convertsToOnUse: ItemStack? = null
): RegionalFoodItem {
@@ -1553,10 +1529,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
.nutrition(nutrition)
.saturationModifier(saturationModifier)
if (effect != null && effectChance != null) {
foodPropertiesBuilder.effect(effect, effectChance)
}
if (alwaysEdible == true) {
foodPropertiesBuilder.alwaysEdible()
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.ai.CobblemonAttackTargetData
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
import com.cobblemon.mod.common.api.dialogue.ActiveDialogue
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.mojang.serialization.Codec
@@ -43,11 +44,18 @@ object CobblemonMemories {
val NEAREST_VISIBLE_ATTACKER = register<LivingEntity>("nearest_visible_attacker")
val NEARBY_GROWABLE_CROPS = register<BlockPos>("nearby_growable_crops")
val RECENTLY_ATE_GRASS = register<Boolean>("recently_ate_grass")
val HIVE_LOCATION = register<BlockPos>("hive_location")
val HIVE_COOLDOWN = register<Boolean>("hive_cooldown")
val NEARBY_FLOWERS = register<List<BlockPos>>("nearby_flower")
val POLLINATED = register<Boolean>("pollinated", PrimitiveCodec.BOOL)
/** who am i following rn? */
val HERD_LEADER = register<String>("herd_leader", PrimitiveCodec.STRING)
/** how many are following me rn? */
val HERD_SIZE = register<Int>("herd_size") // Don't bother saving it, we'll try to keep count roughly.
val ATTACK_TARGET_DATA = register<CobblemonAttackTargetData>("attack_target_data") // This is used to store additional information about the attack target, such as when to give up pursuit.
val WANDER_CONTROL = register<CobblemonWanderControl>("wander_control")
val HAS_HONEY = register<Boolean>("has_honey")
val SACCHARINE_LOG_LOCATION = register<BlockPos>("saccharine_log_location")
fun <U> register(id: String, codec: Codec<U>): MemoryModuleType<U> {
val memoryModule = MemoryModuleType(Optional.of(codec))
@@ -24,6 +24,7 @@ import com.cobblemon.mod.common.client.net.data.UnlockReloadPacketHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
import com.cobblemon.mod.common.client.net.effect.RunPosableMoLangHandler
import com.cobblemon.mod.common.client.net.effect.SaccharineLogBlockParticlesHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormEntityParticleHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormParticleHandler
import com.cobblemon.mod.common.client.net.gui.InteractPokemonUIPacketHandler
@@ -77,6 +78,7 @@ import com.cobblemon.mod.common.net.messages.client.data.*
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.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.net.messages.client.effect.SaccharineLogBlockParticlesPacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.net.messages.client.npc.CloseNPCEditorPacket
@@ -402,6 +404,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler))
list.add(PacketRegisterInfo(SpawnSnowstormEntityParticlePacket.ID, SpawnSnowstormEntityParticlePacket::decode, SpawnSnowstormEntityParticleHandler))
list.add(PacketRegisterInfo(RunPosableMoLangPacket.ID, RunPosableMoLangPacket::decode, RunPosableMoLangHandler))
list.add(PacketRegisterInfo(SaccharineLogBlockParticlesPacket.ID, SaccharineLogBlockParticlesPacket::decode, SaccharineLogBlockParticlesHandler))
// Hax
list.add(PacketRegisterInfo(UnvalidatedPlaySoundS2CPacket.ID, UnvalidatedPlaySoundS2CPacket::decode, UnvalidatedPlaySoundS2CPacketHandler))
@@ -8,10 +8,7 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.DefendOwnerSensor
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.DrowsySensor
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.PokemonAdultSensor
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.PokemonGrowableCropSensor
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.*
import com.cobblemon.mod.common.pokemon.ai.PokemonDisturbancesSensor
import com.cobblemon.mod.common.entity.sensor.BattlingPokemonSensor
import com.cobblemon.mod.common.entity.sensor.NPCBattlingSensor
@@ -36,6 +33,12 @@ object CobblemonSensors {
val NEARBY_GROWABLE_CROPS = register("nearby_growable_crops", ::PokemonGrowableCropSensor)
val NEARBY_BEE_HIVE = register("nearby_bee_hive", ::BeeHiveSensor)
val NEARBY_FLOWER = register("nearby_flower", ::FlowerSensor)
val NEARBY_SACCHARINE_LOG = register("nearby_saccharine_log", ::SaccharineLogSensor)
fun <E : Entity, U : Sensor<E>> register(id: String, supplier: Supplier<U>): SensorType<U> {
val sensor = SensorType(supplier)
sensors[id] = sensor
@@ -0,0 +1,91 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.cobblemon.mod.common.util.getBooleanOrNull
import com.cobblemon.mod.common.util.getDoubleOrNull
import kotlin.math.sqrt
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.PathfinderMob
/**
* Used to maintain some settings around how the entity should wander this tick. The idea is you
* reset this with some script task at priority 0 then some number of steps before it gets to the wander
* tasks you can have other conditional tasks jump in to configure parts of this.
*
* @author Hiroku
* @since July 18, 2025
*/
class CobblemonWanderControl(
var center: WanderCenter? = null,
var maxAttempts: Int = 4, // How many times to try to find a suitable point before giving up on that tick.
var allowLand: Boolean = true,
var allowWater: Boolean = false,
var allowAir: Boolean = false,
var pathCooldownTicks: Int = 20 * 5, // Used to mitigate the cost of pathfinding.
var wanderSpeed: Float = 0.35F,
) {
class WanderCenter(val x: Number, val y: Number, val z: Number, val minRange: Float, val maxRange: Float)
@Transient
val struct: QueryStruct = QueryStruct(hashMapOf())
.addFunction("center_x") { center?.x ?: 0.0 }
.addFunction("center_y") { center?.y ?: 0.0 }
.addFunction("center_z") { center?.z ?: 0.0 }
.addFunction("max_attempts") { maxAttempts.toDouble() }
.addFunction("min_range") { center?.minRange ?: 0.0 }
.addFunction("max_range") { center?.maxRange ?: 0.0 }
.addFunction("allow_land") { if (allowLand) 1.0 else 0.0 }
.addFunction("allow_water") { if (allowWater) 1.0 else 0.0 }
.addFunction("allow_air") { if (allowAir) 1.0 else 0.0 }
.addFunction("path_cooldown_ticks") { pathCooldownTicks.toDouble() }
.addFunction("wander_speed") { wanderSpeed }
.addFunction("set_center") { params ->
if (params.params.size < 3) {
throw IllegalArgumentException("set_center requires at least 3 parameters: x, y, z")
}
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val minRange = params.getDoubleOrNull(3) ?: 0.0
val maxRange = params.getDoubleOrNull(4) ?: Float.MAX_VALUE.toDouble()
center = WanderCenter(
x = x,
y = y,
z = z,
minRange = minRange.toFloat(),
maxRange = maxRange.toFloat(),
)
}
.addFunction("set_max_attempts") { params -> maxAttempts = params.getDoubleOrNull(0)?.toInt() ?: 4 }
.addFunction("set_allow_land") { params -> allowLand = params.getBooleanOrNull(0) ?: true }
.addFunction("set_allow_water") { params -> allowWater = params.getBooleanOrNull(0) ?: false }
.addFunction("set_allow_air") { params -> allowAir = params.getBooleanOrNull(0) ?: false }
.addFunction("set_path_cooldown_ticks") { params -> pathCooldownTicks = params.getDoubleOrNull(0)?.toInt() ?: (20 * 5) }
.addFunction("set_wander_speed") { params -> wanderSpeed = params.getDoubleOrNull(0)?.toFloat() ?: 0.35F }
.addFunction("reset") {
center = null
allowLand = false
allowWater = false
allowAir = false
pathCooldownTicks = 20 * 5
wanderSpeed = 0.35F
maxAttempts = 4
}
fun isSuitable(position: BlockPos): Boolean {
val center = center ?: return true
val centerPos = BlockPos(center.x.toInt(), center.y.toInt(), center.z.toInt())
val distance = sqrt(centerPos.distSqr(position))
val suitable = distance >= center.minRange && distance <= center.maxRange
return suitable
}
}
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.OneShot
import net.minecraft.world.entity.schedule.Activity
class ActivityChangeTaskConfig : SingleTaskConfig {
val activity: Activity = Activity.IDLE
val condition: ExpressionLike = "false".asExpressionLike()
override fun getVariables(entity: LivingEntity): List<MoLangConfigVariable> = emptyList()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
return object : OneShot<LivingEntity>() {
override fun trigger(serverLevel: ServerLevel, livingEntity: LivingEntity, l: Long): Boolean {
runtime.withQueryValue("entity", livingEntity.asMostSpecificMoLangValue())
if (condition.resolveBoolean(runtime)) {
livingEntity.brain.setActiveActivityIfPossible(activity)
return true
} else {
return false
}
}
}
}
}
@@ -21,6 +21,7 @@ import net.minecraft.world.entity.Mob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.LookAtTargetSink
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.sensing.SensorType
class LookAtTargetTaskConfig : SingleTaskConfig {
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
@@ -39,7 +40,8 @@ class LookAtTargetTaskConfig : SingleTaskConfig {
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!condition.resolveBoolean()) return null
behaviourConfigurationContext.addMemories(MemoryModuleType.LOOK_TARGET)
behaviourConfigurationContext.addMemories(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)
behaviourConfigurationContext.addSensors(SensorType.NEAREST_LIVING_ENTITIES)
return WrapperLivingEntityTask(LookAtTargetSink(minDurationTicks.resolveInt(), maxDurationTicks.resolveInt()), Mob::class.java)
}
}
@@ -0,0 +1,82 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.bedrockk.molang.ast.StringExpression
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.mojang.datafixers.util.Either
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
/**
* A task that modifies the aspects of a Pokémon or NPC based on a memory module.
* If the memory is registered, the aspect is added; if it is absent, the aspect is removed.
*
* @author Hiroku
* @since July 6th, 2025
*/
class MemoryAspectTaskConfig : SingleTaskConfig {
var memory: MemoryModuleType<*> = MemoryModuleType.DUMMY
var aspect: ExpressionOrEntityVariable = Either.left(StringExpression(StringValue("")))
override fun getVariables(entity: LivingEntity) = listOf(aspect).asVariables()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity && entity !is NPCEntity) {
return null
} else if (memory == MemoryModuleType.DUMMY) {
Cobblemon.LOGGER.error("MemoryAspectTaskConfig: Memory module type is not set for ${entity.name.string}.")
return null
} else if (aspect.left().orElse(null)?.originalString == "") {
Cobblemon.LOGGER.error("MemoryAspectTaskConfig: Aspect is not set for ${entity.name.string}.")
return null
}
val aspect = aspect.resolveString()
return object : Behavior<LivingEntity>(
mapOf(memory to MemoryStatus.REGISTERED),
Int.MAX_VALUE,
Int.MAX_VALUE
) {
override fun canStillUse(level: ServerLevel, entity: LivingEntity, gameTime: Long) = true
override fun tick(level: ServerLevel, owner: LivingEntity, gameTime: Long) {
val hasMemory = !owner.brain.hasMemoryValue(memory)
if (owner is PokemonEntity) {
if (!hasMemory && aspect in owner.pokemon.forcedAspects) {
owner.pokemon.forcedAspects -= aspect
} else if (hasMemory && aspect !in owner.pokemon.forcedAspects) {
owner.pokemon.forcedAspects += aspect
} else return
owner.pokemon.updateAspects()
} else if (owner is NPCEntity) {
if (!hasMemory && aspect in owner.appliedAspects) {
owner.appliedAspects -= aspect
} else if (hasMemory && aspect !in owner.appliedAspects) {
owner.appliedAspects += aspect
} else return
owner.updateAspects()
}
}
}
}
}
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToBeeHiveTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
class PathToBeeHiveTaskConfig : SingleTaskConfig {
companion object {
const val HONEY = "honey"
}
val condition = booleanVariable(HONEY, "can_add_honey", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition,
).asVariables()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) {
return null
}
if (!checkCondition(entity, condition)) {
return null
}
return PathToBeeHiveTask.create()
}
}
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonSensors
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToFlowerTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
class PathToFlowerTaskConfig : SingleTaskConfig {
companion object {
const val POLLINATE = "pollinate"
}
val condition = booleanVariable(POLLINATE, "can_pollinate", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition
).asVariables()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) {
return null
}
if (!checkCondition(entity, condition)) {
return null
}
behaviourConfigurationContext.addMemories(
CobblemonMemories.NEARBY_FLOWERS,
CobblemonMemories.HIVE_COOLDOWN,
CobblemonMemories.HIVE_LOCATION,
CobblemonMemories.POLLINATED
)
behaviourConfigurationContext.addSensors(CobblemonSensors.NEARBY_BEE_HIVE, CobblemonSensors.NEARBY_FLOWER)
return PathToFlowerTask.create()
}
}
@@ -0,0 +1,29 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonSensors
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToSaccharineLogTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
class PathToSaccharineLogTaskConfig : SingleTaskConfig {
companion object { const val SLATHER = "slather" }
private val condition = booleanVariable(SLATHER, "can_slather_honey", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(condition).asVariables()
override fun createTask(entity: LivingEntity, ctx: BehaviourConfigurationContext): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) return null
if (!checkCondition(entity, condition)) return null
ctx.addMemories(
CobblemonMemories.SACCHARINE_LOG_LOCATION,
CobblemonMemories.HAS_HONEY
)
ctx.addSensors(CobblemonSensors.NEARBY_SACCHARINE_LOG)
return PathToSaccharineLogTask.create()
}
}
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PlaceHoneyInHiveTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
class PlaceHoneyInHiveTaskConfig : SingleTaskConfig {
companion object {
const val HONEY = "honey"
}
val condition = booleanVariable(HONEY, "can_add_honey", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition
).asVariables()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) {
return null
}
if (!checkCondition(entity, condition)) {
return null
}
return PlaceHoneyInHiveTask.create()
}
}
@@ -0,0 +1,94 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.getMemorySafely
import com.cobblemon.mod.common.util.sendParticlesServer
import com.mojang.datafixers.util.Either
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.phys.Vec3
class PollinateFlowerTaskConfig : SingleTaskConfig {
companion object {
const val POLLINATE = "pollinate"
}
val condition = booleanVariable(POLLINATE, "can_pollinate", true).asExpressible()
val durationTicks: ExpressionOrEntityVariable = Either.left("30".asExpression())
override fun getVariables(entity: LivingEntity) = listOf(
condition
).asVariables()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) {
return null
}
if (!checkCondition(entity, condition)) {
return null
}
return object : Behavior<LivingEntity>(
mapOf(
CobblemonMemories.NEARBY_FLOWERS to MemoryStatus.VALUE_PRESENT,
MemoryModuleType.WALK_TARGET to MemoryStatus.VALUE_ABSENT,
CobblemonMemories.POLLINATED to MemoryStatus.VALUE_ABSENT,
CobblemonMemories.HIVE_COOLDOWN to MemoryStatus.VALUE_ABSENT,
),
15,
45
) {
override fun checkExtraStartConditions(level: ServerLevel, owner: LivingEntity): Boolean {
return entity.brain.getMemorySafely(CobblemonMemories.NEARBY_FLOWERS).orElse(emptyList()).any {
Vec3.atCenterOf(it).distanceTo(entity.position()) <= 1.0
}
}
override fun canStillUse(level: ServerLevel, entity: LivingEntity, gameTime: Long): Boolean {
return checkExtraStartConditions(level, entity)
}
override fun start(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
super.start(level, entity, gameTime)
}
override fun tick(level: ServerLevel, owner: LivingEntity, gameTime: Long) {
level.sendParticlesServer(
particleType = ParticleTypes.DRIPPING_HONEY,
position = owner.eyePosition,
particles = 3,
offset = Vec3.ZERO,
speed = 0.0
)
}
override fun stop(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
val flowerStillThere = canStillUse(level, entity, gameTime)
if (flowerStillThere) {
entity.brain.setMemory(CobblemonMemories.POLLINATED, true)
}
}
}
}
}
@@ -0,0 +1,21 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.SlatherHoneyOnLogTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
class SlatherHoneyOnLogTaskConfig : SingleTaskConfig {
companion object { const val SLATHER = "slather" }
private val condition = booleanVariable(SLATHER, "can_slather_honey", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(condition).asVariables()
override fun createTask(entity: LivingEntity, ctx: BehaviourConfigurationContext): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) return null
if (!checkCondition(entity, condition)) return null
return SlatherHoneyOnLogTask.create()
}
}
@@ -0,0 +1,21 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.StealHoneyFromHiveTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
class StealHoneyFromHiveTaskConfig : SingleTaskConfig {
companion object { const val STEAL = "steal" }
private val condition = booleanVariable(STEAL, "can_steal_honey", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(condition).asVariables()
override fun createTask(entity: LivingEntity, ctx: BehaviourConfigurationContext): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) return null
if (!checkCondition(entity, condition)) return null
return StealHoneyFromHiveTask.create()
}
}
@@ -99,6 +99,11 @@ interface TaskConfig {
cobblemonResource("eat_grass") to EatGrassTaskConfig::class.java,
cobblemonResource("find_air") to FindAirTaskConfig::class.java,
cobblemonResource("go_to_land") to GoToLandTaskConfig::class.java,
cobblemonResource("path_to_hive") to PathToBeeHiveTaskConfig::class.java,
cobblemonResource("place_honey_in_hive") to PlaceHoneyInHiveTaskConfig::class.java,
cobblemonResource("path_to_flower") to PathToFlowerTaskConfig::class.java,
cobblemonResource("pollinate_flower") to PollinateFlowerTaskConfig::class.java,
cobblemonResource("go_to_land") to GoToLandTaskConfig::class.java,
cobblemonResource("manage_flight_in_battle") to ManageFlightInBattleTaskConfig::class.java,
cobblemonResource("attack_hostile_mobs") to AttackHostileMobsTaskConfig::class.java,
cobblemonResource("move_into_fluid") to MoveIntoFluidTaskConfig::class.java,
@@ -110,7 +115,12 @@ interface TaskConfig {
cobblemonResource("count_followers") to CountFollowersTaskConfig::class.java,
cobblemonResource("hate_entity") to HateEntityTaskConfig::class.java,
cobblemonResource("target_entity") to TargetEntityTaskConfig::class.java,
)
cobblemonResource("memory_aspect") to MemoryAspectTaskConfig::class.java,
cobblemonResource("activity_change") to ActivityChangeTaskConfig::class.java,
cobblemonResource("steal_honey_from_hive") to StealHoneyFromHiveTaskConfig::class.java,
cobblemonResource("path_to_saccharine_log") to PathToSaccharineLogTaskConfig::class.java,
cobblemonResource("slather_honey_on_log") to SlatherHoneyOnLogTaskConfig::class.java,
)
val runtime = MoLangRuntime().setup()
}
@@ -10,12 +10,14 @@ package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.util.Either
import net.minecraft.core.BlockPos
@@ -23,9 +25,11 @@ import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.util.HoverRandomPos
import net.minecraft.world.entity.ai.util.LandRandomPos
import net.minecraft.world.level.pathfinder.PathType
@@ -101,45 +105,70 @@ class WanderTaskConfig : SingleTaskConfig {
behaviourConfigurationContext.addMemories(
MemoryModuleType.WALK_TARGET,
MemoryModuleType.LOOK_TARGET,
CobblemonMemories.PATH_COOLDOWN
CobblemonMemories.PATH_COOLDOWN,
CobblemonMemories.WANDER_CONTROL
)
return BehaviorBuilder.create {
it.group(
it.absent(MemoryModuleType.WALK_TARGET),
it.registered(MemoryModuleType.LOOK_TARGET),
it.absent(CobblemonMemories.PATH_COOLDOWN)
).apply(it) { walkTarget, lookTarget, pathCooldown ->
it.absent(CobblemonMemories.PATH_COOLDOWN),
it.registered(CobblemonMemories.WANDER_CONTROL)
).apply(it) { walkTarget, lookTarget, pathCooldown, wanderControl ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || entity.isUnderWater) {
return@Trigger false
}
val wanderControl = it.tryGet(wanderControl).orElse(null) ?: CobblemonWanderControl()
val avoidsTargetingAir = avoidTargetingAir.resolveBoolean()
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val wanderChance = runtime.resolveFloat(wanderChanceExpression)
if (wanderChance <= 0 || world.random.nextFloat() > wanderChance) {
if (wanderChance <= 0 || world.random.nextFloat() > wanderChance || !wanderControl.allowLand) {
return@Trigger false
}
pathCooldown.setWithExpiry(true, 40L)
val avoidsTargetingAir = avoidTargetingAir.resolveBoolean()
// val targetVec = getLandTarget(entity) ?: return@Trigger true
val targetVec = LandRandomPos.getPos(
entity,
horizontalRange.resolveInt(),
verticalRange.resolveInt()
) ?: return@Trigger false
pathCooldown.setWithExpiry(true, wanderControl.pathCooldownTicks.toLong())
val minimumHeight = minimumHeight.resolveInt()
val maximumHeight = maximumHeight.resolveInt()
val pos = applyHeightConstraints(
pos = BlockPos.containing(targetVec),
minimumHeight = minimumHeight,
maximumHeight = maximumHeight,
world = world
)
var attempts = 0
var pos: BlockPos? = null
while (attempts++ < wanderControl.maxAttempts && pos == null) {
// val targetVec = getLandTarget(entity) ?: return@Trigger true
val targetVec = if (maximumHeight != -1) {
HoverRandomPos.getPos(
entity,
horizontalRange.resolveInt(),
maxOf(verticalRange.resolveInt(), maximumHeight), // In case they fly up pretty high and need to find a way down
entity.random.nextFloat() - 0.5,
entity.random.nextFloat() - 0.5,
Math.PI.toFloat(),
maximumHeight,
minimumHeight
)
} else {
LandRandomPos.getPos(
entity,
horizontalRange.resolveInt(),
verticalRange.resolveInt()
)
} ?: continue
pos = applyHeightConstraints(
pos = BlockPos.containing(targetVec),
minimumHeight = minimumHeight,
maximumHeight = maximumHeight,
world = world
).takeIf(wanderControl::isSuitable)
}
if (pos == null) {
return@Trigger false
}
walkTarget.set(
CobblemonWalkTarget(
pos = pos,
@@ -149,7 +178,7 @@ class WanderTaskConfig : SingleTaskConfig {
destinationNodeTypeFilter = { nodeType -> !avoidsTargetingAir || nodeType !in listOf(PathType.OPEN) }
)
)
lookTarget.erase()
lookTarget.set(BlockPosTracker(pos.toVec3d().add(0.0, entity.eyeHeight.toDouble(), 0.0)))
return@Trigger true
}
}
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.config.task.WanderTaskConfig.Companion.WANDER
@@ -30,6 +31,7 @@ import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.phys.Vec3
class WaterWanderTaskConfig : SingleTaskConfig {
val condition = booleanVariable(WANDER, "water_wanders", true).asExpressible()
@@ -54,38 +56,59 @@ class WaterWanderTaskConfig : SingleTaskConfig {
behaviourConfigurationContext.addMemories(
MemoryModuleType.WALK_TARGET,
MemoryModuleType.LOOK_TARGET,
CobblemonMemories.PATH_COOLDOWN
CobblemonMemories.PATH_COOLDOWN,
CobblemonMemories.WANDER_CONTROL
)
return BehaviorBuilder.create {
it.group(
it.absent(MemoryModuleType.WALK_TARGET),
it.registered(MemoryModuleType.LOOK_TARGET),
it.registered(CobblemonMemories.PATH_COOLDOWN)
).apply(it) { walkTarget, lookTarget, pathCooldown ->
it.registered(CobblemonMemories.PATH_COOLDOWN),
it.registered(CobblemonMemories.WANDER_CONTROL)
).apply(it) { walkTarget, lookTarget, pathCooldown, wanderControlData ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isInWater) {
return@Trigger false
}
val wanderControl = it.tryGet(wanderControlData).orElse(null) ?: CobblemonWanderControl()
if (!wanderControl.allowWater) {
return@Trigger false
}
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val wanderChance = runtime.resolveFloat(wanderChanceExpression)
if (wanderChance <= 0 || world.random.nextFloat() > wanderChance) {
return@Trigger false
}
pathCooldown.setWithExpiry(true, 60L)
pathCooldown.setWithExpiry(true, wanderControl.pathCooldownTicks.toLong())
var pos: BlockPos? = null
var target: Vec3? = null
var attempts = 0
while (attempts < wanderControl.maxAttempts && pos == null) {
attempts++
target = BehaviorUtils.getRandomSwimmablePos(entity, horizontalRange.resolveInt(), verticalRange.resolveInt())
?: continue
pos = BlockPos.containing(target).takeIf(wanderControl::isSuitable)
}
if (pos == null || target == null) {
return@Trigger false
}
val target = BehaviorUtils.getRandomSwimmablePos(entity, horizontalRange.resolveInt(), verticalRange.resolveInt()) ?: return@Trigger false
walkTarget.set(
CobblemonWalkTarget(
pos = BlockPos.containing(target),
pos = pos,
nodeTypeFilter = { it == PathType.WATER || it == PathType.WATER_BORDER },
speedModifier = speedMultiplier.resolveFloat(),
completionRange = 1
completionRange = 0
)
)
lookTarget.set(BlockPosTracker(target))
lookTarget.set(BlockPosTracker(target /* trust me dude. */))
return@Trigger true
}
}
@@ -12,9 +12,17 @@ import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.battles.PassActionResponse
import com.cobblemon.mod.common.battles.ShowdownActionResponse
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.exception.IllegalActionChoiceException
import com.cobblemon.mod.common.net.messages.client.battle.BattleFaintPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattlePersistentStatusPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleReplacePokemonPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleSwapPokemonPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleSwitchPokemonPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleTransformPokemonPacket
import java.util.UUID
abstract class AIBattleActor(
@@ -25,8 +33,15 @@ abstract class AIBattleActor(
override fun sendUpdate(packet: NetworkPacket<*>) {
super.sendUpdate(packet)
if (packet is BattleMakeChoicePacket) {
this.onChoiceRequested()
when (packet) {
is BattleMakeChoicePacket -> this.onChoiceRequested()
is BattlePersistentStatusPacket -> {}
is BattleFaintPacket -> {}
is BattleSwitchPokemonPacket -> if (!packet.isAlly) { /* TODO this might be redundant; switch on opposing site should be visible during turn */ }
is BattleSwapPokemonPacket -> { /* TODO CONSIDER MULTI SHIFTING */ }
is BattleHealthChangePacket -> battleAI.onHealthChange(packet)
is BattleTransformPokemonPacket -> if (!packet.isAlly) { /* TODO this might be redundant; switch on opposing site should be visible during turn */ }
is BattleReplacePokemonPacket -> if (!packet.isAlly) { /* TODO this might be redundant; switch on opposing site should be visible during turn */ }
}
}
@@ -35,11 +50,35 @@ abstract class AIBattleActor(
*/
open fun onChoiceRequested() {
try {
setActionResponses(request!!.iterate(this.activePokemon, battleAI::choose))
request?.let {
setActionResponses(it.iterate(this.activePokemon) { battleMon, moveset, forceSwitch ->
battleAI.choose(battleMon, battle, getSide(), moveset, forceSwitch)
})
pokemonList.forEach { pokemon ->
pokemon.willBeSwitchedIn = false
}
} ?: {
val response = mutableListOf<ShowdownActionResponse>()
repeat(activePokemon.size) {
response.add(PassActionResponse)
}
setActionResponses(response)
LOGGER.warn("AI requested choice, but no request was set. Returning PassActionResponses.")
}
} catch (exception: IllegalActionChoiceException) {
LOGGER.error("AI was unable to choose a move, we're going to need to pass!")
LOGGER.error("AI was unable to choose an action, we're going to need to pass!")
exception.printStackTrace()
setActionResponses(request!!.iterate(this.activePokemon) { _, _, _ -> PassActionResponse })
request?.let {
setActionResponses(it.iterate(this.activePokemon) { _, _ , _->
PassActionResponse
})
} ?: {
val response = mutableListOf<ShowdownActionResponse>()
repeat(activePokemon.size) {
response.add(PassActionResponse)
}
setActionResponses(response)
}
}
}
}
@@ -8,23 +8,36 @@
package com.cobblemon.mod.common.api.battles.model.ai
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.battles.ShowdownActionResponse
import com.cobblemon.mod.common.battles.ShowdownMoveset
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
/**
* Interface for an actors battle AI
* Interface for an actor battle AI
*
* @since January 16th, 2022
* @author Deltric
* @author Deltric, Polymeta
*/
interface BattleAI {
/**
* Requests that the AI choose an action for the given Pokémon
* @param activeBattlePokemon The Pokémon slot that is choosing an action
* @param moveset The [ShowdownMoveset] for this slot. This can be null if [forceSwitch] is true. Otherwise it is the available move information from Showdown.
* @param forceSwitch Whether or not this is a force switch situation.
* @param battle The ongoing [PokemonBattle]
* @param aiSide The [BattleSide] the AI is fighting on, useful for allies and opponent determination
* @param moveset The [ShowdownMoveset] for this slot. This can be null if [forceSwitch] is true. Otherwise, it is the available move information from Showdown.
* @param forceSwitch Whether this is a force switch situation.
* @return the action response
*/
fun choose(activeBattlePokemon: ActiveBattlePokemon, moveset: ShowdownMoveset?, forceSwitch: Boolean): ShowdownActionResponse
fun choose(
activeBattlePokemon: ActiveBattlePokemon,
battle: PokemonBattle,
aiSide: BattleSide,
moveset: ShowdownMoveset?,
forceSwitch: Boolean
): ShowdownActionResponse
fun onHealthChange(packet: BattleHealthChangePacket) {}
}
@@ -21,6 +21,7 @@ interface PokemonSideDelegate : EntitySideDelegate<PokemonEntity> {
fun updatePostDeath() {}
fun handleStatus(status: Byte) {}
fun spawnShinyParticle(player: Player) {}
fun spawnAspectParticle() {}
fun positionRider(passenger: Entity, positionUpdater: MoveFunction)
fun updateAge(age: Int) {}
}
@@ -231,7 +231,7 @@ object CobblemonEvents {
@JvmField
val POKEMON_GAINED = EventObservable<PokemonGainedEvent>()
@JvmField
val POKEMON_SEEN = EventObservable<PokemonSeenEvent>()
val POKEMON_SEEN = CancelableObservable<PokemonSeenEvent>()
@JvmField
val POKEMON_ASPECTS_CHANGED = EventObservable<PokemonAspectsChangedEvent>()
@JvmField
@@ -8,9 +8,14 @@
package com.cobblemon.mod.common.api.events.berry
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.berry.Berry
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import com.cobblemon.mod.common.util.toArrayStruct
import com.cobblemon.mod.common.util.worldRegistry
import net.minecraft.core.BlockPos
import net.minecraft.core.registries.Registries
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
@@ -38,4 +43,11 @@ data class BerryHarvestEvent(
val state: BlockState,
val blockEntity: BerryBlockEntity,
val drops: MutableList<ItemStack>
) : BerryEvent
) : BerryEvent {
val context = mutableMapOf(
"player" to player.asMoLangValue(),
"berry" to StringValue(berry.identifier.toString()),
"world" to world.worldRegistry.wrapAsHolder(world).asMoLangValue(Registries.DIMENSION),
"pos" to pos.toArrayStruct()
)
}
@@ -8,10 +8,16 @@
package com.cobblemon.mod.common.api.events.drops
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.drop.DropEntry
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.util.asArrayValue
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.LivingEntity
@@ -38,4 +44,14 @@ class LootDroppedEvent(
val player: ServerPlayer?,
val entity: LivingEntity?,
val drops: MutableList<DropEntry>
) : Cancelable()
) : Cancelable() {
val context = mapOf<String, MoValue>(
"player" to (player?.asMoLangValue() ?: DoubleValue.ZERO),
"entity" to (entity?.asMostSpecificMoLangValue() ?: DoubleValue.ZERO),
"drops" to drops.asArrayValue { it.asMoLangValue() }
)
val functions = moLangFunctionMap(
cancelFunc
)
}
@@ -9,10 +9,21 @@
package com.cobblemon.mod.common.api.events.fishing
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.util.server
import net.minecraft.world.item.ItemStack
/**
* Event that is fired when a bait is consumed.
* @param rod The ItemStack of the rod that consumed the bait.
*/
class BaitConsumedEvent(val rod: ItemStack) : Cancelable()
class BaitConsumedEvent(val rod: ItemStack) : Cancelable() {
val context = mutableMapOf(
"rod" to rod.asMoLangValue(server()!!.registryAccess())
)
val functions = moLangFunctionMap(
cancelFunc
)
}
@@ -8,8 +8,12 @@
package com.cobblemon.mod.common.api.events.fishing
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.api.reactive.CancelableObservable
import com.cobblemon.mod.common.util.server
import net.minecraft.world.item.ItemStack
/**
@@ -17,4 +21,12 @@ import net.minecraft.world.item.ItemStack
* @param rod The ItemStack of the rod that the bait is set on.
* @param bait The ItemStack of the bait that is set on the rod.
*/
class BaitSetEvent(val rod: ItemStack, val bait: ItemStack) : Cancelable()
class BaitSetEvent(val rod: ItemStack, val bait: ItemStack) : Cancelable() {
val context = mutableMapOf(
"rod" to rod.asMoLangValue(server()!!.registryAccess()),
"bait" to bait.asMoLangValue(server()!!.registryAccess())
)
val functions = moLangFunctionMap(
cancelFunc
)
}
@@ -9,14 +9,18 @@
package com.cobblemon.mod.common.api.events.fishing
import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.itemRegistry
import com.cobblemon.mod.common.util.server
import net.minecraft.core.registries.Registries
import net.minecraft.world.item.ItemStack
@@ -35,7 +39,14 @@ interface BobberSpawnPokemonEvent {
val bobber: PokeRodFishingBobberEntity,
val spawnAction: SpawnAction<*>,
val rod: ItemStack
) : Cancelable(), BobberSpawnPokemonEvent
) : Cancelable(), BobberSpawnPokemonEvent {
val context = mutableMapOf<String, MoValue>(
"rod" to rod.asMoLangValue(server()!!.registryAccess())
)
val functions = moLangFunctionMap(
cancelFunc
)
}
/**
* Event that is fired when a Pokemon is modified before it is spawned by a bobber.
@@ -8,8 +8,12 @@
package com.cobblemon.mod.common.api.events.fishing
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.util.server
import net.minecraft.world.item.ItemStack
/**
@@ -28,7 +32,15 @@ interface PokerodCastEvent {
override var rod: ItemStack,
var bobber: PokeRodFishingBobberEntity,
var bait: ItemStack
) : PokerodCastEvent, Cancelable()
) : PokerodCastEvent, Cancelable() {
val context = mutableMapOf<String, MoValue>(
"rod" to rod.asMoLangValue(server()!!.registryAccess()),
"bait" to bait.asMoLangValue(server()!!.registryAccess())
)
val functions = moLangFunctionMap(
cancelFunc
)
}
/**
* Event that is fired after a fishing rod is cast.
@@ -40,5 +52,10 @@ interface PokerodCastEvent {
override var rod: ItemStack,
var bobber: PokeRodFishingBobberEntity,
var bait: ItemStack
) : PokerodCastEvent
) : PokerodCastEvent {
val context = mutableMapOf<String, MoValue>(
"rod" to rod.asMoLangValue(server()!!.registryAccess()),
"bait" to bait.asMoLangValue(server()!!.registryAccess())
)
}
}
@@ -9,6 +9,9 @@
package com.cobblemon.mod.common.api.events.fishing
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.util.server
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
@@ -16,4 +19,13 @@ import net.minecraft.world.item.ItemStack
* Event that is fired when a fishing rod is reeled.
* @param rod The ItemStack of the rod that is being reeled.
*/
class PokerodReelEvent(val player: Player, val rod: ItemStack) : Cancelable()
class PokerodReelEvent(val player: Player, val rod: ItemStack) : Cancelable() {
val context = mutableMapOf(
"player" to player.asMoLangValue(),
"rod" to rod.asMoLangValue(server()!!.registryAccess())
)
val functions = moLangFunctionMap(
cancelFunc
)
}
@@ -8,6 +8,9 @@
package com.cobblemon.mod.common.api.events.pokeball
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.entity.LivingEntity
@@ -27,4 +30,17 @@ class PokemonCatchRateEvent(
val pokeBallEntity: EmptyPokeBallEntity,
val pokemonEntity: PokemonEntity,
var catchRate: Float
)
) {
val context = mutableMapOf(
"thrower" to thrower.asMostSpecificMoLangValue(),
"poke_ball_entity" to pokeBallEntity.struct,
"pokemon_entity" to pokemonEntity.struct,
"catch_rate" to DoubleValue(catchRate.toDouble())
)
val functions = moLangFunctionMap(
"set_catch_rate" to {
catchRate = it.getDouble(0).toFloat()
DoubleValue.ONE
}
)
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.events.pokedex.scanning
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -21,15 +22,14 @@ data class PokemonScannedEvent(val player: ServerPlayer, val scannedPokemonEntit
val isOwned: Boolean
get() = scannedPokemonEntityData.pokemon.getOwnerUUID() == player.uuid
val context = mutableMapOf(
val context: MutableMap<String, MoValue> = mutableMapOf(
"player" to player.asMoLangValue(),
"pokemon" to scannedPokemonEntityData.pokemon.struct,
"disguise" to (scannedPokemonEntityData.disguise?.struct ?: DoubleValue.ZERO),
"entity" to {
when (scannedEntity) {
is LivingEntity -> scannedEntity.asMostSpecificMoLangValue()
else -> DoubleValue.ZERO
}
"entity" to when (scannedEntity) {
is LivingEntity -> scannedEntity.asMostSpecificMoLangValue()
else -> DoubleValue.ZERO
},
"is_owned" to DoubleValue(if (isOwned) 1.0 else 0.0)
)
}
@@ -8,9 +8,13 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.getPlayer
import net.minecraft.server.level.ServerPlayer
class CollectEggEvent (
@@ -18,4 +22,13 @@ class CollectEggEvent (
val maleParent : Pokemon,
val femaleParent : Pokemon,
val player : ServerPlayer
) : Cancelable()
) : Cancelable() {
val context = mutableMapOf(
"male_parent" to maleParent.struct,
"female_parent" to femaleParent.struct,
"player" to (player.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO)
)
val functions = moLangFunctionMap(
cancelFunc
)
}
@@ -14,9 +14,7 @@ import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.api.pokemon.stats.EvSource
import com.cobblemon.mod.common.api.pokemon.stats.ItemEvSource
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.*
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.ifIsType
@@ -56,7 +54,15 @@ interface EvGainedEvent {
val context = mutableMapOf(
"pokemon" to source.pokemon.struct,
"stat" to StringValue(stat.identifier.toString()),
"amount" to DoubleValue(amount.toDouble())
"amount" to DoubleValue(amount.toDouble()),
"source" to StringValue(
when (source) {
is BattleEvSource -> "battle"
is ItemEvSource -> "interaction"
is SidemodEvSource -> source.sidemodId
else -> "unknown"
}
)
)
val functions = moLangFunctionMap(
cancelFunc,
@@ -78,7 +84,15 @@ interface EvGainedEvent {
val context = mutableMapOf(
"pokemon" to source.pokemon.struct,
"stat" to StringValue(stat.identifier.toString()),
"amount" to DoubleValue(amount.toDouble())
"amount" to DoubleValue(amount.toDouble()),
"source" to StringValue(
when (source) {
is BattleEvSource -> "battle"
is ItemEvSource -> "interaction"
is SidemodEvSource -> source.sidemodId
else -> "unknown"
}
)
)
}
@@ -8,9 +8,12 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.pokemon.experience.ExperienceSource
import com.cobblemon.mod.common.api.pokemon.experience.*
import com.cobblemon.mod.common.pokemon.Pokemon
/**
@@ -24,7 +27,28 @@ class ExperienceGainedPreEvent(
val pokemon: Pokemon,
val source: ExperienceSource,
var experience: Int
) : Cancelable()
) : Cancelable() {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"source" to StringValue(
when (source) {
is BattleExperienceSource -> "battle"
is CandyExperienceSource -> "interaction"
is CommandExperienceSource -> "command"
is SidemodExperienceSource -> source.sidemodId
else -> "unknown"
}
),
"experience" to DoubleValue(experience.toDouble())
)
val functions = moLangFunctionMap(
cancelFunc,
"set_amount" to {
experience = it.getInt(0)
DoubleValue.ONE
}
)
}
/**
* Event fired when experience has been gained. Information about whether it leveled up or not is
@@ -40,4 +64,20 @@ class ExperienceGainedPostEvent(
val previousLevel: Int,
val currentLevel: Int,
val learnedMoves: MutableSet<MoveTemplate>
)
) {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"source" to StringValue(
when (source) {
is BattleExperienceSource -> "battle"
is CandyExperienceSource -> "interaction"
is CommandExperienceSource -> "command"
is SidemodExperienceSource -> source.sidemodId
else -> "unknown"
}
),
"experience" to DoubleValue(experience.toDouble()),
"previous_level" to DoubleValue(previousLevel.toDouble()),
"current_level" to DoubleValue(currentLevel.toDouble())
)
}
@@ -8,7 +8,9 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.pokemon.Pokemon
/**
@@ -25,4 +27,16 @@ data class FriendshipUpdatedEvent(
set(value) {
field = value.coerceIn(0, Cobblemon.config.maxPokemonFriendship)
}
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"new_friendship" to DoubleValue(newFriendship.toDouble())
)
val functions = moLangFunctionMap(
"set_new_friendship" to {
newFriendship = it.getInt(0)
DoubleValue.ONE
}
)
}
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.api.events.pokemon
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.bedrockk.molang.runtime.value.DoubleValue
/**
* Event that is fired when a player owned Pokémon has its fullness changed
@@ -22,4 +24,16 @@ data class FullnessUpdatedEvent(
set(value) {
field = value.coerceIn(0, pokemon.getMaxFullness())
}
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"new_fullness" to DoubleValue(newFullness.toDouble())
)
val functions = moLangFunctionMap(
"set_new_fullness" to {
newFullness = it.getInt(0)
DoubleValue.ONE
}
)
}
@@ -8,15 +8,30 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.util.getPlayer
import net.minecraft.server.level.ServerPlayer
interface HatchEggEvent {
val egg: PokemonProperties
val player: ServerPlayer
data class Pre(override var egg : PokemonProperties, override var player: ServerPlayer) : HatchEggEvent, Cancelable()
data class Pre(override var egg : PokemonProperties, override var player: ServerPlayer) : HatchEggEvent, Cancelable() {
val context = mutableMapOf(
"player" to (player.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO)
)
val functions = moLangFunctionMap(
cancelFunc
)
}
data class Post(override var egg : PokemonProperties, override var player: ServerPlayer) : HatchEggEvent
data class Post(override var egg : PokemonProperties, override var player: ServerPlayer) : HatchEggEvent {
val context = mutableMapOf(
"player" to (player.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO)
)
}
}
@@ -50,7 +50,7 @@ interface HeldItemEvent {
"pokemon" to pokemon.struct,
"receiving" to receiving.asMoLangValue(server()!!.registryAccess()),
"returning" to returning.asMoLangValue(server()!!.registryAccess()),
"decrement" to DoubleValue(if (decrement) 1.0 else 0.0),
"decrement" to DoubleValue(if (decrement) 1.0 else 0.0)
)
}
val functions = moLangFunctionMap(
@@ -74,7 +74,7 @@ interface HeldItemEvent {
"pokemon" to pokemon.struct,
"received" to received.asMoLangValue(server()!!.registryAccess()),
"returned" to returned.asMoLangValue(server()!!.registryAccess()),
"decremented" to DoubleValue(if (decremented) 1.0 else 0.0),
"decremented" to DoubleValue(if (decremented) 1.0 else 0.0)
)
}
}
@@ -8,10 +8,18 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.getPlayer
import java.util.*
data class PokemonAspectsChangedEvent(
val ownerId: UUID?,
val pokemon: Pokemon,
)
) {
val context = mutableMapOf(
"player" to (ownerId?.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO),
"pokemon" to pokemon.struct
)
}
@@ -8,10 +8,23 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.getPlayer
import java.util.UUID
data class PokemonSeenEvent(
val playerId: UUID,
val pokemon: Pokemon
)
) : Cancelable() {
val context = mapOf<String, MoValue>(
"player" to playerId.getPlayer()!!.asMoLangValue(),
"pokemon" to pokemon.struct
)
val functions = moLangFunctionMap(
cancelFunc
)
}
@@ -8,7 +8,9 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.server.level.ServerLevel
@@ -25,7 +27,15 @@ data class PokemonSentPreEvent(
val pokemon: Pokemon,
val level: ServerLevel,
val position: Vec3
) : Cancelable()
) : Cancelable() {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"position" to StringValue(position.toString())
)
val functions = moLangFunctionMap(
cancelFunc
)
}
/**
* Event fired after a [PokemonEntity] is spawned from a player's party and after its animations are finished.
@@ -37,4 +47,9 @@ data class PokemonSentPreEvent(
data class PokemonSentPostEvent(
val pokemon: Pokemon,
val pokemonEntity: PokemonEntity
)
) {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"pokemon_entity" to pokemonEntity.struct
)
}
@@ -8,7 +8,10 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.server.level.ServerPlayer
@@ -22,4 +25,13 @@ data class ShoulderMountEvent(
val player: ServerPlayer,
val pokemon: Pokemon,
val isLeft: Boolean
) : Cancelable()
) : Cancelable() {
val context = mutableMapOf(
"player" to (player.asMoLangValue() ?: DoubleValue.ZERO),
"pokemon" to pokemon.struct,
"is_left" to DoubleValue(if (isLeft) 1.0 else 0.0),
)
val functions = moLangFunctionMap(
cancelFunc
)
}
@@ -8,9 +8,12 @@
package com.cobblemon.mod.common.api.events.pokemon.healing
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.item.HealingSource
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.item.berry.HealingBerryItem
import com.cobblemon.mod.common.item.interactive.PotionItem
import com.cobblemon.mod.common.item.interactive.PotionType
@@ -31,4 +34,14 @@ class PokemonHealedEvent(
) : Cancelable() {
fun isFullHeal() = amount == -1
fun isHealed() = amount > 0
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"amount" to DoubleValue(amount),
"source" to StringValue(source.toString())
)
val functions = moLangFunctionMap(
cancelFunc
)
}
@@ -8,7 +8,10 @@
package com.cobblemon.mod.common.api.events.storage
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.storage.pc.PCBox
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
@@ -42,11 +45,26 @@ interface ChangePCBoxWallpaperEvent {
override val player: ServerPlayer,
override val box: PCBox,
override var wallpaper: ResourceLocation
) : ChangePCBoxWallpaperEvent, Cancelable()
) : ChangePCBoxWallpaperEvent, Cancelable() {
val context = mutableMapOf(
"player" to player.asMoLangValue(),
"box" to StringValue(box.toString()),
"wallpaper" to StringValue(wallpaper.toString())
)
val functions = mapOf(
cancelFunc
)
}
class Post(
override val player: ServerPlayer,
override val box: PCBox,
override val wallpaper: ResourceLocation
) : ChangePCBoxWallpaperEvent
) : ChangePCBoxWallpaperEvent {
val context = mutableMapOf(
"player" to player.asMoLangValue(),
"box" to StringValue(box.toString()),
"wallpaper" to StringValue(wallpaper.toString())
)
}
}
@@ -23,12 +23,17 @@ import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonUnlockableWallpapers
import com.cobblemon.mod.common.Environment
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.dialogue.PlayerDialogueFaceProvider
import com.cobblemon.mod.common.api.dialogue.ReferenceDialogueFaceProvider
import com.cobblemon.mod.common.api.drop.DropEntry
import com.cobblemon.mod.common.api.mark.Marks
import com.cobblemon.mod.common.api.moves.BenchedMove
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
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
@@ -45,6 +50,8 @@ import com.cobblemon.mod.common.api.pokedex.SeenCount
import com.cobblemon.mod.common.api.pokedex.SeenPercent
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.experience.SidemodExperienceSource
import com.cobblemon.mod.common.api.pokemon.moves.LearnsetQuery
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker
import com.cobblemon.mod.common.api.scheduling.Schedulable
@@ -59,6 +66,11 @@ import com.cobblemon.mod.common.api.storage.pc.PCPosition
import com.cobblemon.mod.common.api.storage.pc.PCStore
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.BattleBuilder
import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.battles.BattleFormat.Companion.GEN_9_DOUBLES
import com.cobblemon.mod.common.battles.BattleFormat.Companion.GEN_9_SINGLES
import com.cobblemon.mod.common.battles.BattleFormat.Companion.GEN_9_TRIPLES
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
@@ -91,6 +103,7 @@ import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.core.Registry
import net.minecraft.core.RegistryAccess
import net.minecraft.core.Vec3i
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.nbt.CompoundTag
@@ -192,6 +205,48 @@ object MoLangFunctions {
values.forEachIndexed { index, moValue -> array.setDirectly("$index", moValue) }
return@Function array
},
"length" to java.util.function.Function { params ->
val array = params.get<ArrayStruct>(0)
return@Function DoubleValue(array.map.size.toDouble())
},
"append" to java.util.function.Function { params ->
val array = params.get<ArrayStruct>(0)
val value = params.get<MoValue>(1)
val nextIndex = array.map.size
array.setDirectly("$nextIndex", value)
return@Function array
},
"insert" to java.util.function.Function { params ->
val array = params.get<ArrayStruct>(0)
val index = params.getInt(1)
val value = params.get<MoValue>(2)
val size = array.map.size
// Shift elements at and after index up by 1
for (i in (size - 1) downTo index) {
val current = array.map[i.toString()]
if (current != null) {
array.map[(i + 1).toString()] = current
}
}
array.map[index.toString()] = value
return@Function array
},
"delete" to java.util.function.Function { params ->
val array = params.get<ArrayStruct>(0)
val index = params.getInt(1)
if (index in 0 until array.map.size) {
array.map.remove(index.toString())
// Re-index the array to keep keys sequential in numerical order
val newMap = hashMapOf<String, MoValue>()
array.map.keys.mapNotNull { it.toIntOrNull() }
.sorted()
.forEachIndexed { i, k -> newMap[i.toString()] = array.map[k.toString()]!! }
array.map.clear()
array.map.putAll(newMap)
}
return@Function array
},
"run_script" to java.util.function.Function { params ->
val runtime = MoLangRuntime()
runtime.environment.query = params.environment.query
@@ -418,6 +473,14 @@ object MoLangFunctions {
map.put("uuid") { _ -> StringValue(player.gameProfile.id.toString()) }
map.put("main_held_item") { _ -> player.mainHandItem.asMoLangValue(player.registryAccess()) }
map.put("off_held_item") { _ -> player.offhandItem.asMoLangValue(player.registryAccess()) }
map.put("inventory") { _ ->
val inventory = player.inventory
val items = ArrayStruct(hashMapOf())
for (i in 0 until inventory.containerSize) {
items.setDirectly("$i", inventory.getItem(i).asMoLangValue(player.registryAccess()))
}
return@put items
}
map.put("face") { params -> ObjectValue(PlayerDialogueFaceProvider(player.uuid, params.getBooleanOrNull(0) != false)) }
map.put("swing_hand") { _ -> player.swing(player.usedItemHand) }
map.put("food_level") { _ -> DoubleValue(player.foodData.foodLevel) }
@@ -529,6 +592,50 @@ object MoLangFunctions {
}
return@put DoubleValue.ZERO
}
map.put("start_battle") { params ->
val opponentValue = params.get<MoValue>(0)
val opponent = if (opponentValue is ObjectValue<*>) {
opponentValue.obj as ServerPlayer
} else {
val paramString = opponentValue.asString()
val playerUUID = paramString.asUUID
if (playerUUID != null) {
server()?.playerList?.getPlayer(playerUUID) ?: return@put DoubleValue.ZERO
} else {
server()?.playerList?.getPlayerByName(paramString) ?: return@put DoubleValue.ZERO
}
}
val format = params.getStringOrNull(1)
?.let(BattleFormat::fromFormatIdentifier)
?: BattleFormat.GEN_9_SINGLES
val setLevel = params.getIntOrNull(2) ?: -1
format.adjustLevel = setLevel
val rules = params.getStringOrNull(5)
?.split(",")
?.toSet()
?: emptySet()
val modifiedBattleFormat = BattleFormat.setBattleRules(
battleFormat = format,
rules = rules
)
val cloneParties = (setLevel != -1) || (params.getBooleanOrNull(3) ?: false)
val healFirst = params.getBooleanOrNull(4) ?: false
val battleStartResult = BattleBuilder.pvp1v1(
player1 = player,
player2 = opponent,
battleFormat = modifiedBattleFormat,
cloneParties = cloneParties,
healFirst = healFirst
)
var returnValue: MoValue = DoubleValue.ZERO
battleStartResult.ifSuccessful { returnValue = it.struct }
return@put returnValue
}
}
map
}
@@ -770,6 +877,55 @@ object MoLangFunctions {
map.put("is_sleeping") { _ -> DoubleValue(entity.isSleeping) }
map.put("health") { _ -> DoubleValue(entity.health) }
map.put("max_health") { _ -> DoubleValue(entity.maxHealth) }
map.put("get_wander_control_memory") {
val value = entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
?: CobblemonWanderControl()
if (
!entity.brain.checkMemory(CobblemonMemories.WANDER_CONTROL, MemoryStatus.VALUE_PRESENT)
&& entity.brain.checkMemory(CobblemonMemories.WANDER_CONTROL, MemoryStatus.REGISTERED)
) {
entity.brain.setMemory(CobblemonMemories.WANDER_CONTROL, value)
}
return@put value.struct
}
map.put("get_position_memory") { params ->
val id = params.getString(0).asIdentifierDefaultingNamespace()
val memoryType = BuiltInRegistries.MEMORY_MODULE_TYPE.get(id)
if (entity.brain.checkMemory(memoryType, MemoryStatus.VALUE_PRESENT)) {
val memory = entity.brain.getMemory(memoryType).get()
return@put when (memory) {
is Vec3i -> VariableStruct(
mapOf(
"x" to DoubleValue(memory.x),
"y" to DoubleValue(memory.y),
"z" to DoubleValue(memory.z)
)
)
is net.minecraft.core.Position -> VariableStruct(
mapOf(
"x" to DoubleValue(memory.x()),
"y" to DoubleValue(memory.y()),
"z" to DoubleValue(memory.z())
)
)
else -> DoubleValue.ZERO
}
} else {
return@put DoubleValue.ZERO
}
}
map.put("has_memory_value") { params ->
val memoryTypes = params.params.map {
it.asString().asIdentifierDefaultingNamespace()
}.map(BuiltInRegistries.MEMORY_MODULE_TYPE::get)
return@put DoubleValue(memoryTypes.all { entity.brain.checkMemory(it, MemoryStatus.VALUE_PRESENT) })
}
map.put("lacks_memory_value") { params ->
val memoryTypes = params.params.map {
it.asString().asIdentifierDefaultingNamespace()
}.map(BuiltInRegistries.MEMORY_MODULE_TYPE::get)
return@put DoubleValue(memoryTypes.none { entity.brain.checkMemory(it, MemoryStatus.VALUE_PRESENT) })
}
map.put("can_see") { params ->
val target = params.get<MoValue>(0)
val range = params.getDoubleOrNull(1) ?: 32.0
@@ -1085,9 +1241,37 @@ object MoLangFunctions {
map.put("max_hp") { DoubleValue(pokemon.maxHealth.toDouble()) }
map.put("current_hp") { DoubleValue(pokemon.currentHealth.toDouble()) }
map.put("friendship") { DoubleValue(pokemon.friendship.toDouble()) }
map.put("max_fullness") { DoubleValue(pokemon.getMaxFullness().toDouble()) }
map.put("fullness") { DoubleValue(pokemon.currentFullness.toDouble()) }
map.put("lose_fullness") { params ->
val amount = params.getDouble(0)
pokemon.loseFullness(amount.toInt())
}
map.put("feed_pokemon") { params ->
val amount = params.getDouble(0)
val playSound = params.getBooleanOrNull(1) ?: true
pokemon.feedPokemon(amount.toInt(), playSound)
}
map.put("behaviour") { pokemon.form.behaviour.struct }
map.put("behavior") { pokemon.form.behaviour.struct } // Inferior
map.put("pokeball") { StringValue(pokemon.caughtBall.toString()) }
map.put("ability") { StringValue(pokemon.ability.name) }
map.put("has_learned") { params ->
val moveName = params.getString(0)
val move = pokemon.allAccessibleMoves.find { it.name == moveName }
if(move != null) {
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
}
map.put("moveset") {
val struct = QueryStruct(hashMapOf())
for ((index, move) in pokemon.moveSet.withIndex()) {
struct.addFunction(index.toString()) { move.struct }
}
struct
}
map.put("evs") {
val struct = QueryStruct(hashMapOf())
for (stat in Stats.PERMANENT) {
@@ -1102,6 +1286,13 @@ object MoLangFunctions {
}
struct
}
map.put("hyper_trained_ivs") {
val struct = QueryStruct(hashMapOf())
for (stat in Stats.PERMANENT) {
struct.addFunction(stat.showdownId) { DoubleValue(pokemon.ivs.hyperTrainedIVs[stat] ?: -1.0) }
}
struct
}
map.put("natdex_number") {
DoubleValue(pokemon.species.nationalPokedexNumber.toDouble())
}
@@ -1190,6 +1381,17 @@ object MoLangFunctions {
}
struct
}
map.put("aspects") {
val aspects = pokemon.aspects
return@put aspects.asArrayValue { StringValue(it) }
}
map.put("form_aspects") {
val aspects = pokemon.form.aspects
return@put aspects.asArrayValue { StringValue(it) }
}
map.put("form_name") {
StringValue(pokemon.form.name.toString())
}
// Yes, this is the lazy call for a single hardcoded pre-evolution.
// Lol. Lmao, even.
// TO-DO: Subscribe to [PokemonSpecies.observable].
@@ -1214,6 +1416,18 @@ object MoLangFunctions {
map.put("remove_held_item") { _ ->
pokemon.removeHeldItem()
}
map.put("add_aspects") { params ->
for (aspect in params.params) pokemon.forcedAspects += aspect.asString()
pokemon.updateAspects()
}
map.put("remove_aspects") { params ->
for (aspect in params.params) pokemon.forcedAspects -= aspect.asString()
pokemon.updateAspects()
}
map.put("cosmetic_item") { pokemon.cosmeticItem().asMoLangValue(server()!!.registryAccess()) ?: DoubleValue.ZERO }
map.put("remove_cosmetic_item") { _ ->
pokemon.removeCosmeticItem()
}
map.put("add_marks") { params ->
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
@@ -1255,6 +1469,92 @@ object MoLangFunctions {
return@put DoubleValue.ZERO
}
}
map.put("add_exp") { params ->
val exp = params.getDouble(0).toInt()
pokemon.addExperience(SidemodExperienceSource("molang"), exp)
return@put DoubleValue.ONE
}
map.put("set_iv") { params ->
val statId = params.getString(0)
val stat = Stats.getStat(statId)
val value = params.getIntOrNull(1)?.coerceIn(0, IVs.MAX_VALUE) ?: IVs.MAX_VALUE
if (Stats.PERMANENT.contains(stat)) {
pokemon.setIV(stat, value)
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
}
map.put("set_ev") { params ->
val statId = params.getString(0)
val stat = Stats.getStat(statId)
val value = (params.getIntOrNull(1) ?: 0).coerceIn(0, 255)
if (Stats.PERMANENT.contains(stat)) {
pokemon.setEV(stat, value)
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
}
map.put("initialize_moveset") { params ->
val preferLatest = params.getBooleanOrNull(0) ?: true
pokemon.initializeMoveset(preferLatest)
return@put DoubleValue.ONE
}
map.put("validate_moveset") { params ->
val includeLegacy = params.getBooleanOrNull(0) ?: true
pokemon.validateMoveset(includeLegacy)
return@put DoubleValue.ONE
}
map.put("teach_learnable_moves") { params ->
val includeLegacy = params.getBooleanOrNull(0) ?: true
pokemon.teachLearnableMoves(includeLegacy)
return@put DoubleValue.ONE
}
map.put("teach_move") { params ->
val moveName = params.getString(0)
val moveTemplate = Moves.getByName(moveName) ?: return@put DoubleValue.ZERO
val bypass = params.getBooleanOrNull(1) ?: false
val canLearn = bypass || LearnsetQuery.ANY.canLearn(moveTemplate, pokemon.form.moves)
if (!canLearn) {
return@put DoubleValue.ZERO
}
val alreadyKnows = pokemon.moveSet.getMoves().any { it.template == moveTemplate } ||
pokemon.benchedMoves.any { it.moveTemplate == moveTemplate }
if (alreadyKnows) {
return@put DoubleValue.ZERO
}
if (pokemon.moveSet.hasSpace()) {
pokemon.moveSet.add(moveTemplate.create())
} else {
pokemon.benchedMoves.add(BenchedMove(moveTemplate, 0))
}
DoubleValue.ONE
}
map.put("can_learn_move") { params ->
val moveName = params.getString(0)
val moveTemplate = Moves.getByName(moveName) ?: return@put DoubleValue.ZERO
val includeLegacy = params.getBooleanOrNull(1) ?: true
val canLearn = LearnsetQuery.ANY.canLearn(moveTemplate, pokemon.form.moves)
return@put DoubleValue(if (canLearn) 1.0 else 0.0)
}
map.put("unlearn_move") { params ->
val moveName = params.getString(0)
val moveTemplate = Moves.getByName(moveName)
if (moveTemplate != null) {
pokemon.unlearnMove(moveTemplate)
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
}
map
}
)
@@ -1378,6 +1678,12 @@ object MoLangFunctions {
val pokemon = party.get(index) ?: return@put DoubleValue.ZERO
return@put pokemon.struct
}
map.put("set_pokemon") { params ->
val index = params.getInt(0)
val pokemon = params.get<ObjectValue<Pokemon>>(1).obj
party.set(index, pokemon)
return@put DoubleValue.ONE
}
return@mutableListOf map
})
@@ -1390,6 +1696,13 @@ object MoLangFunctions {
val pokemon = pc[PCPosition(box, slot)] ?: return@put DoubleValue.ZERO
return@put pokemon.struct
}
map.put("set_pokemon") { params ->
val box = params.getInt(0)
val slot = params.getInt(1)
val pokemon = params.get<ObjectValue<Pokemon>>(2).obj
pc.set(PCPosition(box, slot), pokemon)
return@put DoubleValue.ONE
}
map.put("resize") { params ->
val newSize = params.getInt(0)
val lockNewSize = params.getBooleanOrNull(1) == true
@@ -1568,6 +1881,21 @@ object MoLangFunctions {
}
)
val dropEntryFunctions: MutableList<(DropEntry) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ dropEntry ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("percentage") { DoubleValue(dropEntry.percentage.toDouble()) }
map.put("quantity") { DoubleValue(dropEntry.quantity.toDouble()) }
map.put("max_selectable_times") { DoubleValue(dropEntry.maxSelectableTimes.toDouble()) }
map.put("can_drop") { params ->
val pokemon = params.getOrNull<ObjectValue<Pokemon>>(0)?.obj
DoubleValue(if (dropEntry.canDrop(pokemon)) 1.0 else 0.0)
}
map
}
)
val pokemonPropertiesFunctions: MutableList<(PokemonProperties) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ props ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
@@ -1696,6 +2024,15 @@ object MoLangFunctions {
return value
}
fun DropEntry.asMoLangValue(): ObjectValue<DropEntry> {
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
value.addFunctions(dropEntryFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
return value
}
fun PokemonProperties.asMoLangValue(): ObjectValue<PokemonProperties> {
val value = ObjectValue(
obj = this,
@@ -17,6 +17,7 @@ import net.minecraft.resources.ResourceLocation
* As opposed to the [GlobalPokedexValueCalculator] this interface is always called on a specific dex definition (e.g. galar, kanto)
*/
interface PokedexValueCalculator<T> {
val outputIsPercentage: Boolean
fun calculate(dexManager: AbstractPokedexManager, dex: Map<ResourceLocation, PokedexEntry>): T
}
@@ -25,6 +26,7 @@ interface PokedexValueCalculator<T> {
* The calculation of this interface is aimed to consider all available Pokédex entries and is not limited or filtered to a particular dex
*/
interface GlobalPokedexValueCalculator<T> {
val outputIsPercentage: Boolean
fun calculate(dexManager: AbstractPokedexManager): T
}
@@ -32,6 +34,7 @@ interface GlobalPokedexValueCalculator<T> {
* Calculates the amount of caught Pokémon (globally or per dex)
*/
object CaughtCount : PokedexValueCalculator<Int>, GlobalPokedexValueCalculator<Int> {
override val outputIsPercentage = false
override fun calculate(dexManager: AbstractPokedexManager): Int {
return dexManager.speciesRecords.values.count { it.getKnowledge() == PokedexEntryProgress.CAUGHT }
}
@@ -45,6 +48,7 @@ object CaughtCount : PokedexValueCalculator<Int>, GlobalPokedexValueCalculator<I
* Calculates the amount of seen Pokémon (globally or per dex)
*/
object SeenCount : PokedexValueCalculator<Int>, GlobalPokedexValueCalculator<Int> {
override val outputIsPercentage = false
override fun calculate(dexManager: AbstractPokedexManager): Int {
return dexManager.speciesRecords.values.count { it.getKnowledge() != PokedexEntryProgress.NONE }
}
@@ -58,12 +62,14 @@ object SeenCount : PokedexValueCalculator<Int>, GlobalPokedexValueCalculator<Int
* Calculates the seen percentage globally or relative to a particular dex
*/
object SeenPercent : PokedexValueCalculator<Float>, GlobalPokedexValueCalculator<Float> {
override val outputIsPercentage = true
override fun calculate(dexManager: AbstractPokedexManager): Float {
return dexManager.speciesRecords.values.count { it.getKnowledge() != PokedexEntryProgress.NONE }.toFloat() / DexEntries.entries.count().toFloat() * 100F
return dexManager.speciesRecords.values.count { it.getKnowledge() != PokedexEntryProgress.NONE }.toFloat() / DexEntries.entries.values.map { it.speciesId }.toSet().size * 100F
}
override fun calculate(dexManager: AbstractPokedexManager, dex: Map<ResourceLocation, PokedexEntry>): Float {
return dex.entries.map { it.value }.count { dexManager.getKnowledgeForSpecies(it.speciesId) != PokedexEntryProgress.NONE }.toFloat() / DexEntries.entries.count().toFloat() * 100F
return dex.entries.map { it.value }.count { dexManager.getKnowledgeForSpecies(it.speciesId) != PokedexEntryProgress.NONE }.toFloat() / dex.entries.size * 100F
}
}
@@ -71,11 +77,13 @@ object SeenPercent : PokedexValueCalculator<Float>, GlobalPokedexValueCalculator
* Calculates the caught percentage globally or relative to a particular dex
*/
object CaughtPercent : PokedexValueCalculator<Float>, GlobalPokedexValueCalculator<Float> {
override val outputIsPercentage = true
override fun calculate(dexManager: AbstractPokedexManager): Float {
return dexManager.speciesRecords.values.count { it.getKnowledge() == PokedexEntryProgress.CAUGHT }.toFloat() / DexEntries.entries.count().toFloat().toFloat() * 100F
return dexManager.speciesRecords.values.count { it.getKnowledge() == PokedexEntryProgress.CAUGHT }.toFloat() / DexEntries.entries.values.map { it.speciesId }.toSet().size * 100F
}
override fun calculate(dexManager: AbstractPokedexManager, dex: Map<ResourceLocation, PokedexEntry>): Float {
return dex.entries.map { it.value }.count { dexManager.getKnowledgeForSpecies(it.speciesId) == PokedexEntryProgress.CAUGHT }.toFloat() / DexEntries.entries.count().toFloat().toFloat() * 100F
return dex.entries.map { it.value }.count { dexManager.getKnowledgeForSpecies(it.speciesId) == PokedexEntryProgress.CAUGHT }.toFloat() / dex.entries.size * 100F
}
}
@@ -340,6 +340,7 @@ open class PokemonProperties {
var originalTrainer: String? = null // Original Trainer by Username or UUID
var moves: List<String>? = null
var heldItem: String? = null
var cosmeticItem: String? = null
var ivs: IVs? = null
var evs: EVs? = null
@@ -0,0 +1,27 @@
/*
* 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.pokemon.aspect
import com.cobblemon.mod.common.api.spawning.influence.SaccharineLogSlatheredInfluence
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import net.minecraft.core.particles.ParticleOptions
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.resources.ResourceLocation
/**
* Used by [PokemonClientDelegate.spawnAspectParticle] to spawn associated particles for aspects
*/
val aspectParticleMap: Map<String, ParticleData> = mapOf(
SaccharineLogSlatheredInfluence.HONEY_DRENCHED_ASPECT to ParticleData.MinecraftParticle(ParticleTypes.FALLING_HONEY, 0.075, 1)
)
sealed class ParticleData {
data class SnowstormParticle(val particle: ResourceLocation, val chance: Double, val amount: Int, val locators: List<String>): ParticleData()
data class MinecraftParticle(val particle: ParticleOptions, val chance: Double, val amount: Int): ParticleData()
}
@@ -43,6 +43,8 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
val tmInterpreter = Interpreter.parseFromPrefixIntoList("tm") { it.tmMoves }
val eggInterpreter = Interpreter.parseFromPrefixIntoList("egg") { it.eggMoves }
val tutorInterpreter = Interpreter.parseFromPrefixIntoList("tutor") { it.tutorMoves }
val legacyInterpreter = Interpreter.parseFromPrefixIntoList("legacy") { it.legacyMoves }
val specialInterpreter = Interpreter.parseFromPrefixIntoList("special") { it.specialMoves }
val formChangeInterpreter = Interpreter.parseFromPrefixIntoList("form_change") { it.formChangeMoves }
val levelUpInterpreter = Interpreter { element, learnset ->
val str = element.takeIf { it.isJsonPrimitive }?.asString ?: return@Interpreter false
@@ -70,6 +72,8 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
tmInterpreter,
eggInterpreter,
tutorInterpreter,
legacyInterpreter,
specialInterpreter,
levelUpInterpreter,
formChangeInterpreter
)
@@ -78,6 +82,8 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
val levelUpMoves = mutableMapOf<Int, MutableList<MoveTemplate>>()
val eggMoves = mutableListOf<MoveTemplate>()
val tutorMoves = mutableListOf<MoveTemplate>()
val legacyMoves = mutableListOf<MoveTemplate>()
val specialMoves = mutableListOf<MoveTemplate>()
val tmMoves = mutableListOf<MoveTemplate>()
/**
* Moves the species/form will have learnt when evolving into itself.
@@ -93,6 +99,15 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
.flatMap { it.value }
.toSet()
fun getAllLegalMoves(): Set<MoveTemplate> {
return levelUpMoves.values.flatten().toSet() +
eggMoves +
tutorMoves +
tmMoves +
formChangeMoves +
evolutionMoves
}
// We only sync level up moves atm
override fun shouldSynchronize(other: Learnset) = other.levelUpMoves != this.levelUpMoves
@@ -24,6 +24,17 @@ fun interface LearnsetQuery {
companion object {
val ANY = LearnsetQuery { move, learnset ->
learnset.levelUpMoves.values.any { it.contains(move) }
|| learnset.eggMoves.contains(move)
|| learnset.tutorMoves.contains(move)
|| learnset.tmMoves.contains(move)
|| learnset.formChangeMoves.contains(move)
|| learnset.evolutionMoves.contains(move)
|| learnset.legacyMoves.contains(move)
|| learnset.specialMoves.contains(move)
}
val LEGAL = LearnsetQuery { move, learnset ->
learnset.levelUpMoves.values.any { it.contains(move) }
|| learnset.eggMoves.contains(move)
|| learnset.tutorMoves.contains(move)
@@ -46,6 +57,9 @@ fun interface LearnsetQuery {
val EVOLUTION = LearnsetQuery { move, learnset -> learnset.evolutionMoves.contains(move) }
val LEGACY_MOVES = LearnsetQuery { move, learnset -> learnset.legacyMoves.contains(move) }
val SPECIAL_MOVES = LearnsetQuery { move, learnset -> learnset.specialMoves.contains(move) }
}
}
@@ -13,18 +13,29 @@ import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawnCause
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.effect.SaccharineLogBlockParticlesPacket
import com.cobblemon.mod.common.util.toVec3d
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.world.entity.Entity
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.RotatedPillarBlock
import org.joml.Vector3d
class SaccharineLogSlatheredInfluence(val pos: BlockPos? = null) : SpawningInfluence {
companion object {
const val HONEY_DRENCHED_ASPECT = "honey_drenched"
const val SACCHARINE_LOG_SLATHERED_MARKER = "saccharine_log_slathered"
const val HIDDEN_ABILITY_CHANCE = 0.05
const val SAFE_BLOCK_SEARCH_DISTANCE = 10
}
var used = false
val chanceForHA = 0.05
var activated = false
override fun affectSpawnablePosition(spawnablePosition: SpawnablePosition) {
spawnablePosition.markers.add(SACCHARINE_LOG_SLATHERED_MARKER)
@@ -32,23 +43,93 @@ class SaccharineLogSlatheredInfluence(val pos: BlockPos? = null) : SpawningInflu
override fun affectSpawn(action: SpawnAction<*>, entity: Entity) {
if (entity is PokemonEntity) {
if (Math.random() <= chanceForHA) {
if (Math.random() <= HIDDEN_ABILITY_CHANCE) {
FishingSpawnCause.alterHAAttempt(entity)
}
if (!used) {
if (!activated) {
val logPos = pos
val level = action.spawnablePosition.world.level
if (logPos != null) {
val blockState = level.getBlockState(logPos)
if (blockState.block == CobblemonBlocks.SACCHARINE_LOG_SLATHERED) {
val direction = blockState.getValue(HorizontalDirectionalBlock.FACING)
val safePos = attemptSafeMove(level, entity, logPos, direction)
if (safePos != null) entity.moveTo(safePos.toVec3d())
SaccharineLogBlockParticlesPacket(logPos, safePos).sendToPlayersAround(
logPos.x.toDouble(),
logPos.y.toDouble(),
logPos.z.toDouble(),
64.0,
level.dimension()
)
level.playSound(null, logPos, SoundEvents.PLAYER_BURP, SoundSource.NEUTRAL)
val axis = blockState.getValue(RotatedPillarBlock.AXIS)
val newState = CobblemonBlocks.SACCHARINE_LOG.defaultBlockState().setValue(RotatedPillarBlock.AXIS, axis)
level.setBlock(logPos, newState, 3)
}
}
used = true
entity.pokemon.forcedAspects += HONEY_DRENCHED_ASPECT
activated = true
}
}
}
fun attemptSafeMove(level: ServerLevel, entity: PokemonEntity, startPos: BlockPos, direction: Direction): BlockPos? {
val inWater = entity.isInWater
val originalPos = startPos.relative(direction)
val maxRadius = SAFE_BLOCK_SEARCH_DISTANCE
// For each radius from 0 up to max, increase radius after each full downward traversal
for (radius in 0..maxRadius) {
var currentPos = originalPos
repeat(SAFE_BLOCK_SEARCH_DISTANCE) {
val safeSpot = searchInHalfCircle(level, entity, currentPos, direction, radius, inWater)
if (safeSpot != null) {
return safeSpot
}
currentPos = currentPos.below()
}
}
return null
}
fun searchInHalfCircle(level: ServerLevel, entity: PokemonEntity, centerPos: BlockPos, direction: Direction, searchRadius: Int, allowWater: Boolean): BlockPos? {
for (dx in -searchRadius..searchRadius) {
for (dz in -searchRadius..searchRadius) {
if (dx * dx + dz * dz <= searchRadius * searchRadius) {
val candidatePos = centerPos.offset(dx, 0, dz)
val candidateVec = Vector3d(dx.toDouble(), 0.0, dz.toDouble())
val directionVec = Vector3d(direction.stepX.toDouble(), direction.stepY.toDouble(), direction.stepZ.toDouble())
// Check if position is in front of the direction
val dot = candidateVec.dot(directionVec)
if (dot >= 0) {
// Check collision at candidate position
val currentBox = entity.boundingBox.move(
candidatePos.x - entity.x,
candidatePos.y - entity.y,
candidatePos.z - entity.z
)
if (level.noCollision(entity, currentBox)) {
// Check block below
val blockBelowPos = candidatePos.below()
val blockState = level.getBlockState(blockBelowPos)
val isWater = blockState.block == Blocks.WATER
if (allowWater) {
if (isWater) return candidatePos
} else {
if (!blockState.isAir) return candidatePos
}
}
}
}
}
}
return null
}
}
@@ -30,6 +30,12 @@ object CobblemonBiomeTags {
val IS_TEMPERATE = create("is_temperate")
@JvmField
val IS_WINTER = create("has_season/winter")
@JvmField
val IS_SWAMP = create("is_swamp")
@JvmField
val IS_PLAINS = create("is_plains")
@JvmField
val IS_BEACH = create("is_beach")
// Has Feature tags
@JvmField
@@ -40,6 +46,8 @@ object CobblemonBiomeTags {
val HAS_APRICORNS_SPARSE = create("has_feature/apricorns_sparse")
@JvmField
val HAS_REVIVAL_HERBS = create("has_feature/revival_herbs")
@JvmField
val ALLOWED_BIOMES_SACCHARINE_TREE = create("has_feature/saccharine_tree")
// Has Ore tags
@JvmField
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
@@ -323,6 +324,28 @@ object BattleBuilder {
val npcParty = npcEntity.getPartyForChallenge(listOf(player))
val errors = ErroredBattleStart()
val adjustLevel = battleFormat.adjustLevel
val playerPartyStores = mutableListOf<PlayerPartyStore>()
val npcPartyStores = mutableListOf<NPCPartyStore>()
if (adjustLevel > 0) {
val tempStorePlayer = PlayerPartyStore(player.uuid)
playerTeam.forEachIndexed { index, battlePokemon ->
battlePokemon.effectedPokemon.level = adjustLevel
battlePokemon.effectedPokemon.heal()
tempStorePlayer.set(index, battlePokemon.effectedPokemon)
}
playerPartyStores.add(tempStorePlayer)
val tempStoreNpc = NPCPartyStore(npcEntity)
npcParty!!.forEachIndexed { index, battlePokemon ->
battlePokemon.level = adjustLevel
battlePokemon.heal()
tempStoreNpc.set(index, battlePokemon)
}
npcPartyStores.add(npcParty)
}
if (playerActor.pokemonList.size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[playerActor] += BattleStartError.insufficientPokemon(
actorEntity = player,
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.net.IntSize
import com.cobblemon.mod.common.util.getStringOrNull
import com.cobblemon.mod.common.util.readSizedInt
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeSizedInt
@@ -29,19 +30,44 @@ data class BattleFormat(
var adjustLevel: Int = -1, // Stop gap rule before a more general system for rules enforced by Cobblemon is implemented.
) {
companion object {
fun setBattleRules(
battleFormat: BattleFormat,
rules: Set<String>? = null
): BattleFormat {
val filteredRules = rules?.filter { it.isNotBlank() }?.toSet()
val ruleValues = filteredRules?.mapNotNull { ruleName ->
BattleRules::class.members
.filterIsInstance<kotlin.reflect.KProperty1<BattleRules, String>>()
.find { it.name == ruleName }
?.getter
?.call()
}.orEmpty()
return battleFormat.copy(
ruleSet = filteredRules ?: (battleFormat.ruleSet + ruleValues)
)
}
fun fromFormatIdentifier(id: String): BattleFormat = when (id) {
"single_battle", "single", "singles" -> BattleFormat.GEN_9_SINGLES
"double_battle", "double", "doubles" -> BattleFormat.GEN_9_DOUBLES
"triple_battle", "triple", "triples" -> BattleFormat.GEN_9_TRIPLES
else -> BattleFormat.GEN_9_SINGLES
}
val GEN_9_SINGLES = BattleFormat(
battleType = BattleTypes.SINGLES,
ruleSet = setOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
ruleSet = mutableSetOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
)
val GEN_9_DOUBLES = BattleFormat(
battleType = BattleTypes.DOUBLES,
ruleSet = setOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
ruleSet = mutableSetOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
)
val GEN_9_TRIPLES = BattleFormat(
battleType = BattleTypes.TRIPLES,
ruleSet = setOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
ruleSet = mutableSetOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
)
val GEN_9_MULTI = BattleFormat(
@@ -8,8 +8,10 @@
package com.cobblemon.mod.common.battles.ai
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.battles.DefaultActionResponse
import com.cobblemon.mod.common.battles.MoveActionResponse
import com.cobblemon.mod.common.battles.PassActionResponse
@@ -26,6 +28,8 @@ import com.cobblemon.mod.common.battles.SwitchActionResponse
class RandomBattleAI : BattleAI {
override fun choose(
activeBattlePokemon: ActiveBattlePokemon,
battle: PokemonBattle,
aiSide: BattleSide,
moveset: ShowdownMoveset?,
forceSwitch: Boolean
): ShowdownActionResponse {
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,295 @@
/*
* 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.battles.ai.strongBattleAI
import com.cobblemon.mod.common.api.abilities.Ability
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
/**
* Everything in this class will be eventually getting replaced by a dynamic datadriven approach
* when moves/abilities/types become data-packable or at least queryable from showdown
*/
object AIUtility {
fun getDamageMultiplier(attackerType: ElementalType, defenderType: ElementalType): Double {
return typeEffectiveness[attackerType]?.get(defenderType) ?: 1.0
}
val typeEffectiveness: Map<ElementalType, Map<ElementalType, Double>> = mapOf(
ElementalTypes.NORMAL to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 0.5, ElementalTypes.GHOST to 0.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.FIRE to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 0.5, ElementalTypes.WATER to 0.5, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 2.0,
ElementalTypes.ICE to 2.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 2.0, ElementalTypes.ROCK to 0.5, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 0.5,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 2.0, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.WATER to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 2.0, ElementalTypes.WATER to 0.5, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 0.5,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 2.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 2.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 0.5,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 1.0, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.ELECTRIC to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 2.0, ElementalTypes.ELECTRIC to 0.5, ElementalTypes.GRASS to 0.5,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 0.0, ElementalTypes.FLYING to 2.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 0.5,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 1.0, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.GRASS to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 0.5, ElementalTypes.WATER to 2.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 0.5,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 0.5, ElementalTypes.GROUND to 2.0, ElementalTypes.FLYING to 0.5,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 0.5, ElementalTypes.ROCK to 2.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 0.5,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.ICE to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 0.5, ElementalTypes.WATER to 0.5, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 2.0,
ElementalTypes.ICE to 0.5, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 2.0, ElementalTypes.FLYING to 2.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 2.0,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.FIGHTING to mapOf(
ElementalTypes.NORMAL to 2.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 2.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 0.5, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 0.5,
ElementalTypes.PSYCHIC to 0.5, ElementalTypes.BUG to 0.5, ElementalTypes.ROCK to 2.0, ElementalTypes.GHOST to 0.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 2.0, ElementalTypes.STEEL to 2.0, ElementalTypes.FAIRY to 0.5
),
ElementalTypes.POISON to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 2.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 0.5, ElementalTypes.GROUND to 0.5, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 0.5, ElementalTypes.GHOST to 0.5, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 0.0, ElementalTypes.FAIRY to 2.0
),
ElementalTypes.GROUND to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 2.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 2.0, ElementalTypes.GRASS to 0.5,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 2.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 0.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 0.5, ElementalTypes.ROCK to 2.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 2.0, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.FLYING to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 0.5, ElementalTypes.GRASS to 2.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 2.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 2.0, ElementalTypes.ROCK to 0.5, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.PSYCHIC to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 2.0, ElementalTypes.POISON to 2.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 0.5, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 0.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.BUG to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 0.5, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 2.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 0.5, ElementalTypes.POISON to 0.5, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 0.5,
ElementalTypes.PSYCHIC to 2.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 0.5, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 2.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 0.5
),
ElementalTypes.ROCK to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 2.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 2.0, ElementalTypes.FIGHTING to 0.5, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 0.5, ElementalTypes.FLYING to 2.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 2.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.GHOST to mapOf(
ElementalTypes.NORMAL to 0.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 2.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 2.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 0.5, ElementalTypes.STEEL to 1.0, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.DRAGON to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 2.0,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 0.0
),
ElementalTypes.DARK to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 0.5, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 2.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 2.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 0.5, ElementalTypes.STEEL to 1.0, ElementalTypes.FAIRY to 0.5
),
ElementalTypes.STEEL to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 0.5, ElementalTypes.WATER to 0.5, ElementalTypes.ELECTRIC to 0.5, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 2.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 2.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 2.0
),
ElementalTypes.FAIRY to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 0.5, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 2.0, ElementalTypes.POISON to 0.5, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 2.0,
ElementalTypes.DARK to 2.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 1.0
)
)
val multiHitMoves: Map<String, Pair<Int, Int>> = mapOf(
// 2 - 5 hit moves
"armthrust" to Pair(2, 5),
"barrage" to Pair(2, 5),
"bonerush" to Pair(2, 5),
"bulletseed" to Pair(2, 5),
"cometpunch" to Pair(2, 5),
"doubleslap" to Pair(2, 5),
"furyattack" to Pair(2, 5),
"furyswipes" to Pair(2, 5),
"iciclespear" to Pair(2, 5),
"pinmissile" to Pair(2, 5),
"rockblast" to Pair(2, 5),
"scaleshot" to Pair(2, 5),
"spikecannon" to Pair(2, 5),
"tailslap" to Pair(2, 5),
"watershuriken" to Pair(2, 5),
// fixed hit count
"bonemerang" to Pair(2, 2),
"doublehit" to Pair(2, 2),
"doubleironbash" to Pair(2, 2),
"doublekick" to Pair(2, 2),
"dragondarts" to Pair(2, 2),
"dualchop" to Pair(2, 2),
"dualwingbeat" to Pair(2, 2),
"geargrind" to Pair(2, 2),
"twinbeam" to Pair(2, 2),
"twineedle" to Pair(2, 2),
"suringstrikes" to Pair(3, 3),
"tripledive" to Pair(3, 3),
"watershuriken" to Pair(3, 3),
// accuracy based multi-hit moves
"tripleaxel" to Pair(1, 3),
"triplekick" to Pair(1, 3),
"populationbomb" to Pair(1, 10)
)
val statusMoves: Map<String, String> = mapOf(
"willowisp" to Statuses.BURN.showdownName,
"scald" to Statuses.BURN.showdownName,
"scorchingsands" to Statuses.BURN.showdownName,
"glare" to Statuses.PARALYSIS.showdownName,
"nuzzle" to Statuses.PARALYSIS.showdownName,
"stunspore" to Statuses.PARALYSIS.showdownName,
"thunderwave" to Statuses.PARALYSIS.showdownName,
"darkvoid" to Statuses.SLEEP.showdownName,
"hypnosis" to Statuses.SLEEP.showdownName,
"lovelykiss" to Statuses.SLEEP.showdownName,
"relicsong" to Statuses.SLEEP.showdownName,
"sing" to Statuses.SLEEP.showdownName,
"sleeppower" to Statuses.SLEEP.showdownName,
"spore" to Statuses.SLEEP.showdownName,
"yawn" to Statuses.SLEEP.showdownName,
"chatter" to Statuses.CONFUSE.showdownName,
"confuseray" to Statuses.CONFUSE.showdownName,
"dynamicpunch" to Statuses.CONFUSE.showdownName,
"flatter" to Statuses.CONFUSE.showdownName,
"supersonic" to Statuses.CONFUSE.showdownName,
"swagger" to Statuses.CONFUSE.showdownName,
"sweetkiss" to Statuses.CONFUSE.showdownName,
"teeterdance" to Statuses.CONFUSE.showdownName,
"poisongas" to Statuses.POISON.showdownName,
"poisonpowder" to Statuses.POISON.showdownName,
"toxic" to Statuses.POISON_BADLY.showdownName,
"toxicthread" to Statuses.POISON.showdownName,
"curse" to "cursed",
"leechseed" to "leech"
)
val boostFromMoves: Map<String, Map<Stat, Int>> = mapOf(
"bellydrum" to mapOf(Stats.ATTACK to 6),
"bulkup" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1),
"clangoroussoul" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1, Stats.SPEED to 1),
"coil" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.ACCURACY to 1),
"dragondance" to mapOf(Stats.ATTACK to 1, Stats.SPEED to 1),
"extremeevoboost" to mapOf(Stats.ATTACK to 2, Stats.DEFENCE to 2, Stats.SPECIAL_ATTACK to 2, Stats.SPECIAL_DEFENCE to 2, Stats.SPEED to 2),
"clangoroussoulblaze" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1, Stats.SPEED to 1),
"filletaway" to mapOf(Stats.ATTACK to 2, Stats.SPECIAL_ATTACK to 2, Stats.SPEED to 2),
"honeclaws" to mapOf(Stats.ATTACK to 1, Stats.ACCURACY to 1),
"noretreat" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1, Stats.SPEED to 1),
"shellsmash" to mapOf(Stats.ATTACK to 2, Stats.DEFENCE to -1, Stats.SPECIAL_ATTACK to 2, Stats.SPECIAL_DEFENCE to -1, Stats.SPEED to 2),
"shiftgear" to mapOf(Stats.ATTACK to 1, Stats.SPEED to 2),
"swordsdance" to mapOf(Stats.ATTACK to 2),
"tidyup" to mapOf(Stats.ATTACK to 1, Stats.SPEED to 1),
"victorydance" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.SPEED to 1),
"acidarmor" to mapOf(Stats.DEFENCE to 2),
"barrier" to mapOf(Stats.DEFENCE to 2),
"cottonguard" to mapOf(Stats.DEFENCE to 3),
"defensecurl" to mapOf(Stats.DEFENCE to 1),
"irondefense" to mapOf(Stats.DEFENCE to 2),
"shelter" to mapOf(Stats.DEFENCE to 2, Stats.EVASION to 1),
"stockpile" to mapOf(Stats.DEFENCE to 1, Stats.SPECIAL_DEFENCE to 1),
"stuffcheeks" to mapOf(Stats.DEFENCE to 2),
"amnesia" to mapOf(Stats.SPECIAL_DEFENCE to 2),
"calmmind" to mapOf(Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1),
"geomancy" to mapOf(Stats.SPECIAL_ATTACK to 2, Stats.SPECIAL_DEFENCE to 2, Stats.SPEED to 2),
"nastyplot" to mapOf(Stats.SPECIAL_ATTACK to 2),
"quiverdance" to mapOf(Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1, Stats.SPEED to 1),
"tailglow" to mapOf(Stats.SPECIAL_ATTACK to 3),
"takeheart" to mapOf(Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1),
"agility" to mapOf(Stats.SPEED to 2),
"autotomize" to mapOf(Stats.SPEED to 2),
"rockpolish" to mapOf(Stats.SPEED to 2),
"curse" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.SPEED to -1),
"minimize" to mapOf(Stats.EVASION to 2)
)
val entryHazards = listOf("spikes", "stealthrock", "stickyweb", "toxicspikes")
val antiHazardsMoves = listOf("rapidspin", "defog", "tidyup")
val antiBoostMoves = listOf("slearsmog","haze")
val pivotMoves = listOf("uturn", "flipturn", "partingshot", "batonpass", "shedtail", "voltswitch", "teleport")
val setupMoves = setOf("tailwind", "trickroom", "auroraveil", "lightscreen", "reflect")
val selfRecoveryMoves = listOf("healorder", "milkdrink", "recover", "rest", "roost", "slackoff", "softboiled")
val weatherSetupMoves = mapOf(
"chillyreception" to "Snow",
"hail" to "Hail",
"raindance" to "RainDance",
"sandstorm" to "Sandstorm",
"snowscape" to "Snow",
"sunnyday" to "SunnyDay"
)
val accuracyLoweringMoves = setOf("flash", "kinesis", "leaftornado", "mirrorshot", "mudbomb", "mudslap", "muddywater", "nightgaze", "octazooka", "sandattack", "secretpower", "smokescreen")
val protectMoves = setOf("protect", "banefulbunker", "obstruct", "craftyshield", "detect", "quickguard", "spikyshield", "silktrap")
val statusImmunityMap = mapOf(
Statuses.BURN.showdownName to (setOf("fire") to setOf("waterbubble", "waterveil", "flareboost", "guts", "magicguard")),
Statuses.PARALYSIS.showdownName to (setOf("electric") to setOf("limber", "guts")),
Statuses.SLEEP.showdownName to (setOf("grass") to setOf("insomnia", "sweetveil")),
Statuses.CONFUSE.showdownName to (setOf("fire") to setOf("owntempo", "oblivious")),
Statuses.POISON.showdownName to (setOf("poison", "steel") to setOf("immunity", "poisonheal", "guts", "magicguard")),
Statuses.POISON_BADLY.showdownName to (setOf("poison", "steel") to setOf("immunity", "poisonheal", "guts", "magicguard")),
"cursed" to (setOf("ghost") to setOf("magicguard")),
"leech" to (setOf("grass") to setOf("liquidooze", "magicguard")),
)
fun canAffectWithStatus(status: String, types: Iterable<ElementalType>, ability: Ability? = null): Boolean {
val (typing, abilities) = statusImmunityMap[status] ?: return true // custom status? *shrug*
return types.none { it.name in typing } && ability?.name !in abilities
}
val typeImmuneAbilities = mapOf(
"lightingrod" to ElementalTypes.ELECTRIC,
"flashfire" to ElementalTypes.FIRE,
"levitate" to ElementalTypes.GROUND,
"sapsipper" to ElementalTypes.GRASS,
"motordrive" to ElementalTypes.ELECTRIC,
"stormdrain" to ElementalTypes.WATER,
"voltabsorb" to ElementalTypes.ELECTRIC,
"waterabsorb" to ElementalTypes.WATER,
"immunity" to ElementalTypes.POISON,
"eartheater" to ElementalTypes.GROUND
)
}
@@ -0,0 +1,221 @@
/*
* 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.battles.ai.strongBattleAI
import com.cobblemon.mod.common.api.abilities.Ability
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.moves.Move
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.Species
import java.util.UUID
import kotlin.collections.firstOrNull
import kotlin.collections.getOrDefault
import kotlin.collections.groupingBy
import kotlin.collections.lastOrNull
import kotlin.collections.map
class ActiveTracker {
var currentRoom: String? = null
var currentTerrain: String? = null
var currentWeather: String? = null
val alliedSide: TrackerSide = TrackerSide()
val opponentSide: TrackerSide = TrackerSide()
val isInitialized: Boolean
get() = alliedSide.actors.isNotEmpty() && opponentSide.actors.isNotEmpty()
fun initialize(alliedSide: BattleSide) {
this.alliedSide.actors.clear()
this.alliedSide.actors.addAll(alliedSide.actors.map { TrackerActor.initializeAlly(it) })
this.opponentSide.actors.clear()
this.opponentSide.actors.addAll(alliedSide.getOppositeSide().actors.map { TrackerActor.initializeOpponent(it) })
}
fun updateActiveState(alliedSide: BattleSide) {
this.alliedSide.updateAlliedActiveState(alliedSide)
this.opponentSide.updateOpponentActiveState(alliedSide.getOppositeSide())
}
}
class TrackerSide {
var screenCondition: String? = null
var tailwindCondition: String? = null
var sideHazards: List<String> = emptyList()
val actors : MutableList<TrackerActor> = mutableListOf()
val activePokemon: MutableList<TrackerPokemon>
get() = actors.flatMap { it.activePokemon }.toMutableList()
fun updateAlliedActiveState(side: BattleSide) {
actors.associateWith { side.actors.first { actor -> actor.uuid == it.uuid} }
.forEach { (trackerActor, actor) -> trackerActor.updateAlliedActivePokemon(actor)}
val activePokemon = side.activePokemon.mapNotNull { it.battlePokemon }
for (pokemon in activePokemon) {
val trackMon = this.activePokemon.first { it.id == pokemon.uuid }
val boostPerStat = pokemon.contextManager.get(BattleContext.Type.BOOST)?.groupingBy { it.id }
?.eachCount()?.mapKeys { Stats.getStat(it.key) } ?: emptyMap()
val unboostPerStat = pokemon.contextManager.get(BattleContext.Type.UNBOOST)?.groupingBy { it.id }
?.eachCount()?.mapKeys { Stats.getStat(it.key) } ?: emptyMap()
trackMon.boosts.putAll(Stats.ALL.filterNot {it == Stats.HP}
.associateWith { stat -> boostPerStat.getOrDefault(stat, 0) - unboostPerStat.getOrDefault(stat, 0) })
trackMon.currentStatus = pokemon.effectedPokemon.status?.status?.showdownName ?: pokemon.contextManager.get(BattleContext.Type.STATUS)?.lastOrNull()?.id
trackMon.currentVolatile = pokemon.contextManager.get(BattleContext.Type.VOLATILE)?.lastOrNull()?.id
trackMon.pokemon = pokemon.effectedPokemon
trackMon.species = pokemon.effectedPokemon.species
trackMon.form = pokemon.effectedPokemon.form
trackMon.currentHp = pokemon.maxHealth
trackMon.currentHpPercent = pokemon.health.toDouble() / pokemon.maxHealth.toDouble()
trackMon.currentAbility = pokemon.effectedPokemon.ability
trackMon.moves = pokemon.effectedPokemon.moveSet.toList()
}
//TODO count turns where applicable?
sideHazards = side.contextManager.get(BattleContext.Type.HAZARD)?.map { it.id } ?: emptyList()
tailwindCondition = side.contextManager.get(BattleContext.Type.TAILWIND)?.firstOrNull()?.id
screenCondition = side.contextManager.get(BattleContext.Type.SCREEN)?.firstOrNull()?.id
}
fun updateOpponentActiveState(side: BattleSide) {
actors.associateWith { side.actors.first { actor -> actor.uuid == it.uuid} }
.forEach { (trackerActor, actor) -> trackerActor.updateOpponentActivePokemon(actor)}
val activePokemon = side.activePokemon.mapNotNull { it.battlePokemon }
for (pokemon in activePokemon) {
val trackMon = this.activePokemon.first { it.id == pokemon.uuid }
val boostPerStat = pokemon.contextManager.get(BattleContext.Type.BOOST)?.groupingBy { it.id }
?.eachCount()?.mapKeys { Stats.getStat(it.key) } ?: emptyMap()
val unboostPerStat = pokemon.contextManager.get(BattleContext.Type.UNBOOST)?.groupingBy { it.id }
?.eachCount()?.mapKeys { Stats.getStat(it.key) } ?: emptyMap()
trackMon.boosts.putAll(Stats.ALL.filterNot {it == Stats.HP}
.associateWith { stat -> boostPerStat.getOrDefault(stat, 0) - unboostPerStat.getOrDefault(stat, 0) })
trackMon.currentStatus = pokemon.effectedPokemon.status?.status?.showdownName ?: pokemon.contextManager.get(BattleContext.Type.STATUS)?.lastOrNull()?.id
trackMon.currentVolatile = pokemon.contextManager.get(BattleContext.Type.VOLATILE)?.lastOrNull()?.id
val resetAbility = trackMon.species != (pokemon.entity?.exposedSpecies ?: pokemon.effectedPokemon.species) ||
trackMon.form != (pokemon.entity?.exposedForm ?: pokemon.effectedPokemon.form)
trackMon.species = pokemon.entity?.exposedSpecies ?: pokemon.effectedPokemon.species
trackMon.form = pokemon.entity?.exposedForm ?: pokemon.effectedPokemon.form
if (trackMon.currentAbility != null || resetAbility) {
trackMon.currentAbility = (trackMon.form?.abilities ?: trackMon.species!!.abilities).first().template.create() //educated guess, abilities of a pokemon are known
}
trackMon.currentHpPercent = pokemon.health.toDouble() / pokemon.maxHealth.toDouble()
}
//TODO count turns where applicable?
sideHazards = side.contextManager.get(BattleContext.Type.HAZARD)?.map { it.id } ?: emptyList()
tailwindCondition = side.contextManager.get(BattleContext.Type.TAILWIND)?.firstOrNull()?.id
screenCondition = side.contextManager.get(BattleContext.Type.SCREEN)?.firstOrNull()?.id
}
}
// Tracker Actor with a Party of Tracker Pokemon in a Party
class TrackerActor(val uuid: UUID) {
val party: MutableList<TrackerPokemon> = mutableListOf()
val activePokemon: MutableList<TrackerPokemon> = mutableListOf()
val remainingMons: Int
get() = (party + activePokemon).count { (it.currentHp?.toDouble() ?: it.currentHpPercent) > 0 }
fun updateAlliedActivePokemon(actor: BattleActor) {
actor.activePokemon.filter { newActive -> activePokemon.none { it.id == newActive.battlePokemon?.uuid } }.forEach {
val active = party.firstOrNull { trackMon -> trackMon.id == it.battlePokemon?.uuid } ?: return@forEach
party.remove(active)
activePokemon.add(active)
}
val swappedOut = activePokemon.filter { trackMon -> actor.activePokemon.none { it.battlePokemon?.uuid == trackMon.id } }
swappedOut.forEach {
activePokemon.remove(it)
party.add(it)
}
}
fun updateOpponentActivePokemon(actor: BattleActor) {
actor.activePokemon.filter { newActive -> activePokemon.none { it.id == newActive.battlePokemon?.uuid } }.forEach {
val active = party.firstOrNull { trackMon -> trackMon.id == it.battlePokemon?.uuid } ?: return@forEach
party.remove(active)
activePokemon.add(active)
}
val swappedOut = activePokemon.filter { trackMon -> actor.activePokemon.none { it.battlePokemon?.uuid == trackMon.id } }
swappedOut.forEach {
activePokemon.remove(it)
party.add(it)
}
}
companion object {
fun initializeAlly(actor: BattleActor) : TrackerActor {
val trackerActor = TrackerActor(actor.uuid)
trackerActor.party.clear()
//we assume that AI knows it all about our allies
trackerActor.party.addAll(actor.pokemonList.map { createAllied(it) })
actor.activePokemon.forEach {
val active = trackerActor.party.first { trackMon -> trackMon.id == it.battlePokemon!!.uuid }
trackerActor.party.remove(active)
trackerActor.activePokemon.add(active)
}
return trackerActor
}
fun initializeOpponent(actor: BattleActor) : TrackerActor {
val trackerActor = TrackerActor(actor.uuid)
trackerActor.party.clear()
//only know basics of what we see right now
trackerActor.party.addAll(actor.pokemonList.map { createOpposing(it) })
actor.activePokemon.forEach {
val active = trackerActor.party.first { trackMon -> trackMon.id == it.battlePokemon!!.uuid }
trackerActor.party.remove(active)
trackerActor.activePokemon.add(active)
}
return trackerActor
}
}
}
// Tracker Pokemon within the Party
data class TrackerPokemon(
var id: UUID? = null,
var pokemon: Pokemon? = null,
var species: Species? = null,
var form: FormData? = null,
var currentHp: Int? = null,
var currentHpPercent: Double = 0.0,
var boosts: MutableMap<Stat, Int> = mutableMapOf(),
var currentVolatile: String? = null,
var currentStatus: String? = null,
var currentAbility: Ability? = null, //TODO PICK UP FROM BATTLE ACTIVATION
var moves: List<Move> = listOf(), //TODO PICK UP FROM BATTLE USEAGE
var firstTurn: Boolean = true,
var protectCount: Int = 0 //TODO MAYBE PICK UP FROM BATTLE USAGE
)
fun createAllied(pokemon: BattlePokemon) : TrackerPokemon {
val trackerMon = TrackerPokemon()
trackerMon.id = pokemon.uuid
trackerMon.pokemon = pokemon.effectedPokemon
trackerMon.species = pokemon.effectedPokemon.species
trackerMon.form = pokemon.effectedPokemon.form
trackerMon.currentHp = pokemon.maxHealth
trackerMon.currentHpPercent = pokemon.health.toDouble() / pokemon.maxHealth.toDouble()
trackerMon.currentAbility = pokemon.effectedPokemon.ability
trackerMon.moves = pokemon.effectedPokemon.moveSet.toList()
return trackerMon
}
fun createOpposing(pokemon: BattlePokemon) : TrackerPokemon {
val trackerMon = TrackerPokemon()
trackerMon.id = pokemon.uuid //mainly used as unique reference
//everything else gets set on a "seen" basis
return trackerMon
}
@@ -60,10 +60,10 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
)
val AGE_TO_SHAPE_TOP = arrayOf(
box(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), // Stage 0
box(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), // Stage 1
box(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), // Stage 2
box(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), // Stage 3
box(1.0, 0.0, 1.0, 15.0, 1.0, 15.0), // Stage 0
box(1.0, 0.0, 1.0, 15.0, 1.0, 15.0), // Stage 1
box(1.0, 0.0, 1.0, 15.0, 1.0, 15.0), // Stage 2
box(1.0, 0.0, 1.0, 15.0, 1.0, 15.0), // Stage 3
box(0.0, 0.0, 0.0, 16.0, 4.0, 16.0), // Stage 4
box(0.0, 0.0, 0.0, 16.0, 11.0, 16.0), // Stage 5
box(0.0, 0.0, 0.0, 16.0, 15.0, 16.0), // Stage 6
@@ -233,17 +233,6 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
}
}
private fun isWaterNearby(level: LevelReader, pos: BlockPos): Boolean {
val floorBlock = pos.below()
for (i in -1..1) {
for (j in -1..1) {
val fluidState = level.getFluidState(floorBlock.offset(i, 0, j))
if (fluidState.`is`(FluidTags.WATER) && fluidState.isSource) return true
}
}
return false
}
private fun getLowerHalf(level: LevelReader, pos: BlockPos, state: BlockState): Pair<BlockPos, BlockState>? {
if (isLower(state)) {
return Pair(pos, state)
@@ -21,6 +21,7 @@ import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.Rotation
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
@@ -79,7 +80,12 @@ class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
pos: BlockPos,
collisionContext: CollisionContext
): VoxelShape {
return HITBOX
return when (state.getValue(HorizontalDirectionalBlock.FACING)) {
Direction.WEST -> HITBOX_WEST
Direction.EAST -> HITBOX_EAST
Direction.SOUTH -> HITBOX_SOUTH
else -> HITBOX_NORTH
}
}
@Deprecated("Deprecated in Java")
@@ -97,13 +103,34 @@ class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
//0 is off
val SCREEN = EnumProperty.create("screen", MonitorScreen::class.java)
val HITBOX = Shapes.or(
val HITBOX_SOUTH = Shapes.or(
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375),
Shapes.box(0.0625, 0.875, 0.0625, 0.9375, 1.0, 0.9375),
Shapes.box(0.8125, 0.375, 0.0625, 0.9375, 0.875, 0.9375),
Shapes.box(0.1875, 0.375, 0.125, 0.8125, 0.875, 0.9375),
Shapes.box(0.0625, 0.375, 0.0625, 0.1875, 0.875, 0.9375)
)
val HITBOX_NORTH = Shapes.or(
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375),
Shapes.box(0.0625, 0.875, 0.0625, 0.9375, 1.0, 0.9375),
Shapes.box(0.0625, 0.375, 0.0625, 0.1875, 0.875, 0.9375),
Shapes.box(0.1875, 0.375, 0.0625, 0.8125, 0.875, 0.875),
Shapes.box(0.8125, 0.375, 0.0625, 0.9375, 0.875, 0.9375)
)
val HITBOX_EAST = Shapes.or(
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375),
Shapes.box(0.0625, 0.875, 0.0625, 0.9375, 1.0, 0.9375),
Shapes.box(0.0625, 0.375, 0.0625, 0.9375, 0.875, 0.1875),
Shapes.box(0.125, 0.375, 0.1875, 0.9375, 0.875, 0.8125),
Shapes.box(0.0625, 0.375, 0.8125, 0.9375, 0.875, 0.9375)
)
val HITBOX_WEST = Shapes.or(
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375),
Shapes.box(0.0625, 0.875, 0.0625, 0.9375, 1.0, 0.9375),
Shapes.box(0.0625, 0.375, 0.8125, 0.9375, 0.875, 0.9375),
Shapes.box(0.0625, 0.375, 0.1875, 0.875, 0.875, 0.8125),
Shapes.box(0.0625, 0.375, 0.0625, 0.9375, 0.875, 0.1875)
)
}
enum class MonitorScreen : StringRepresentable {
OFF,
@@ -55,7 +55,7 @@ class RevivalHerbBlock(settings: Properties) : CropBlock(settings), Mulchable {
val floor = world.getBlockState(pos.below())
val block = world.getBlockState(pos)
// A bit of a copy pasta but we don't have access to the BlockState being attempted to be placed above on the canPlantOnTop
return (block.`is`(BlockTags.REPLACEABLE_BY_TREES) || block.`is`(Blocks.AIR) || block.`is`(this)) && ((state.getValue(IS_WILD) && floor.`is`(BlockTags.DIRT)) || this.mayPlaceOn(floor, world, pos))
return (block.`is`(BlockTags.REPLACEABLE_BY_TREES) || block.`is`(BlockTags.AIR) || block.`is`(this)) && ((state.getValue(IS_WILD) && floor.`is`(BlockTags.DIRT)) || this.mayPlaceOn(floor, world, pos))
}
override fun getShape(
@@ -161,19 +161,19 @@ class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
val particleCount = random.nextInt(3)
if (state.getValue(AGE) == 2) {
if (state.getValue(AGE) == 1) {
repeat(particleCount) {
spawnHoneyParticles(level, pos, state, 0.75F)
spawnHoneyParticles(level, pos, state, 0.025F)
}
} else if (state.getValue(AGE) == 1) {
} else if (state.getValue(AGE) == 2) {
repeat(particleCount) {
spawnHoneyParticles(level, pos, state, 0.9F)
spawnHoneyParticles(level, pos, state, 0.05F)
}
}
}
private fun spawnHoneyParticles(level: Level, pos: BlockPos, state: BlockState, rate: Float) {
if (state.fluidState.isEmpty && !(level.random.nextFloat() < rate)) {
if (state.fluidState.isEmpty && (level.random.nextFloat() < rate)) {
val voxelShape = state.getCollisionShape(level, pos)
val d = voxelShape.max(Direction.Axis.Y)
if (d >= 1.0 && !state.`is`(BlockTags.IMPERMEABLE)) {
@@ -161,13 +161,16 @@ object CobblemonClient {
if (event.client.isPaused) {
return@subscribe
}
val nearbyShinies = player.level().getEntities(
val nearbyPokemon = player.level().getEntities(
player,
AABB.ofSize(player.position(), 16.0, 16.0, 16.0)
) { it is PokemonEntity && it.pokemon.shiny && !it.isSilent }
nearbyShinies?.firstOrNull { player.isLookingAt(it) && !player.isSpectator }.let {
if (it is PokemonEntity) {
it.delegate.spawnShinyParticle(player)
) { it is PokemonEntity }
nearbyPokemon?.forEach { entity ->
if (entity is PokemonEntity && !entity.isSilent) {
if (player.isLookingAt(entity) && !player.isSpectator && entity.pokemon.shiny) entity.delegate.spawnShinyParticle(player)
entity.delegate.spawnAspectParticle()
}
}
}
@@ -17,7 +17,9 @@ import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.entity.PokemonSideDelegate
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.aspect.aspectParticleMap
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.aspect.ParticleData
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.api.scheduling.afterOnClient
@@ -48,7 +50,6 @@ import net.minecraft.sounds.SoundSource
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.Entity.MoveFunction
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
@@ -442,6 +443,37 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
}
}
override fun spawnAspectParticle() {
currentAspects = currentEntity.entityData.get(PokemonEntity.ASPECTS)
currentAspects.forEach { aspect ->
aspectParticleMap[aspect]?.let { particleData ->
val random = currentEntity.level().random
when (particleData) {
is ParticleData.SnowstormParticle -> {
val locator = particleData.locators.firstOrNull { this.locatorStates[it] != null } ?: "root"
if (particleData.chance > random.nextDouble()) {
repeat(particleData.amount) {
runtime.resolve("q.particle('${particleData.particle}', '$locator')".asExpressionLike())
}
}
}
is ParticleData.MinecraftParticle -> {
if (particleData.chance > random.nextDouble()) {
val box = currentEntity.boundingBox
repeat(particleData.amount) {
val x = box.minX + (box.maxX - box.minX) * random.nextDouble()
val y = box.minY + (box.maxY - box.minY) * random.nextDouble()
val z = box.minZ + (box.maxZ - box.minZ) * random.nextDouble()
currentEntity.level().addParticle(particleData.particle, x, y, z, 0.0, 0.0, 0.0)
}
}
}
}
}
}
}
fun cry() {
val model = currentModel ?: return
if (cryAnimation != null && (cryAnimation in activeAnimations || cryAnimation == primaryAnimation)) {
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.pokemon.PokemonSortMode
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.storage.pc.search.Search
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.italicise
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
@@ -602,9 +603,11 @@ class PCGUI(
scale = SCALE
)
val iv = pokemon.ivs.get(stats[i])
val effectiveIv = pokemon.ivs.getEffectiveBattleIV(stats[i])
drawScaledText(
context = context,
text = pokemon.ivs.get(stats[i]).toString().text(),
text = if (iv != effectiveIv) effectiveIv.toString().text().italicise() else iv.toString().text(),
x = x + 65,
y = y + 139 + (10 * i),
centered = true,
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.effect
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.block.SaccharineLogSlatheredBlock
import com.cobblemon.mod.common.net.messages.client.effect.SaccharineLogBlockParticlesPacket
import net.minecraft.client.Minecraft
import net.minecraft.core.particles.BlockParticleOption
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.HorizontalDirectionalBlock
object SaccharineLogBlockParticlesHandler : ClientNetworkPacketHandler<SaccharineLogBlockParticlesPacket> {
override fun handle(packet: SaccharineLogBlockParticlesPacket, client: Minecraft) {
val level = Minecraft.getInstance().level ?: return
val blockState = level.getBlockState(packet.blockPos)
val block = blockState.block
if (block is SaccharineLogSlatheredBlock) {
val direction = blockState.getValue(HorizontalDirectionalBlock.FACING)
block.spawnParticlesAtBlockFace(BlockParticleOption(ParticleTypes.BLOCK, Blocks.HONEY_BLOCK.defaultBlockState()), level, packet.blockPos, direction, 15)
block.spawnParticlesAtBlockFace(ParticleTypes.FALLING_HONEY, level, packet.blockPos, direction, 15)
}
val entityPos = packet.entityPos
if (entityPos != null) {
val random = level.random
repeat(10) {
level.addParticle(
ParticleTypes.POOF,
entityPos.x + random.nextDouble(),
entityPos.y + random.nextDouble(),
entityPos.z + random.nextDouble(),
0.0,
0.0,
0.0
)
}
}
}
}
@@ -11,12 +11,7 @@ package com.cobblemon.mod.common.command
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
import com.cobblemon.mod.common.api.pokedex.CaughtCount
import com.cobblemon.mod.common.api.pokedex.CaughtPercent
import com.cobblemon.mod.common.api.pokedex.Dexes
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.SeenCount
import com.cobblemon.mod.common.api.pokedex.SeenPercent
import com.cobblemon.mod.common.api.pokedex.*
import com.cobblemon.mod.common.api.pokedex.def.PokedexDef
import com.cobblemon.mod.common.api.pokedex.def.SimplePokedexDef
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
@@ -36,6 +31,7 @@ import net.minecraft.commands.Commands
import net.minecraft.commands.arguments.EntityArgument
import net.minecraft.commands.arguments.selector.EntitySelector
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
object PokedexCommand {
@@ -47,9 +43,8 @@ object PokedexCommand {
val grantCommandBuilder = Commands.literal(GRANT_NAME).then(
Commands.argument("player", EntityArgument.player())
.then(Commands.literal("all").then(
Commands.argument("dex", DexArgumentType.dex()).executes(::executeGrantAll)
)
)
Commands.argument("dex", DexArgumentType.dex()).executes(::executeGrantAll)
))
.then(Commands.literal("only").then(
Commands.argument("species", SpeciesArgumentType.species()).then(
Commands.argument("form", FormArgumentType.form()).executes(::executeGrantOnly)
@@ -60,13 +55,12 @@ object PokedexCommand {
Commands.argument("player", EntityArgument.player())
.then(Commands.literal("all").then(
Commands.argument("dex", DexArgumentType.dex()).executes(::executeRemoveAll)
)
)
.then(Commands.literal("only").then(
Commands.argument("species", SpeciesArgumentType.species()).then(
Commands.argument("form", FormArgumentType.form()).executes(::executeRemoveOnly)
)
))
))
.then(Commands.literal("only").then(
Commands.argument("species", SpeciesArgumentType.species()).then(
Commands.argument("form", FormArgumentType.form()).executes(::executeRemoveOnly)
)
))
)
commandArgumentBuilder
.permission(CobblemonPermissions.POKEDEX)
@@ -74,7 +68,8 @@ object PokedexCommand {
.then(revokeCommandBuilder)
.then(Commands.literal("printcalculations")
.then(Commands.argument("player", EntityArgument.player())
.executes(::printValuesGlobal)
.executes(::printNationalDexValues)
.then(Commands.literal("all").executes(::printValuesGlobal))
.then(Commands.argument("dex", DexArgumentType.dex())
.executes(::printValues))))
@@ -181,7 +176,19 @@ object PokedexCommand {
var calculators = listOf(SeenCount, CaughtCount, SeenPercent, CaughtPercent)
calculators.forEach {
var value = dex.getGlobalCalculatedValue(it)
context.source.sendSystemMessage(Component.literal("${it.javaClass.simpleName} result: $value"))
val percentString = if (it.outputIsPercentage) "%" else ""
context.source.sendSystemMessage(Component.literal("${it.javaClass.simpleName} result: $value$percentString"))
}
Dexes.dexEntryMap.forEach { (dexId, dexDef) ->
val seenCompletion = dex.getDexCalculatedValue(dexId, SeenPercent)
val caughtCompletion = dex.getDexCalculatedValue(dexId, CaughtPercent)
// Send system message with completion percentages for each Dex
context.source.sendSystemMessage(
Component.literal("Completion for ${dexId}: Seen: ${seenCompletion}% Caught: ${caughtCompletion}%")
)
}
return 1
@@ -194,7 +201,24 @@ object PokedexCommand {
var calculators = listOf(SeenCount, CaughtCount, SeenPercent, CaughtPercent)
calculators.forEach {
var value = dex.getDexCalculatedValue(dexDef.id, it)
context.source.sendSystemMessage(Component.literal("${it.javaClass.simpleName} result: $value"))
val percentString = if (it.outputIsPercentage) "%" else ""
context.source.sendSystemMessage(Component.literal("${it.javaClass.simpleName} result for ${dexDef.id}: $value$percentString"))
}
return 1
}
private fun printNationalDexValues (context: CommandContext<CommandSourceStack>) : Int {
val player = context.getArgument("player", EntitySelector::class.java).findSinglePlayer(context.source)
val dex = Cobblemon.playerDataManager.getPokedexData(player)
val dexDef = Dexes.dexEntryMap[ResourceLocation("cobblemon", "national")] ?: throw IllegalStateException("No National Dex")
var calculators = listOf(SeenCount, CaughtCount, SeenPercent, CaughtPercent)
calculators.forEach {
var value = dex.getDexCalculatedValue(dexDef.id, it)
val percentString = if (it.outputIsPercentage) "%" else ""
context.source.sendSystemMessage(Component.literal("${it.javaClass.simpleName} result for ${dexDef.id}: $value$percentString"))
}
return 1
@@ -59,6 +59,10 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
var navigationContext = NavigationContext()
companion object {
val verticallyPreciseNodeTypes = setOf(PathType.WATER, PathType.OPEN, PathType.LAVA)
}
override fun createPathFinder(range: Int): PathFinder {
this.nodeEvaluator = OmniPathNodeMaker()
nodeEvaluator.setCanOpenDoors(false)
@@ -126,7 +130,9 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
val d = abs(mob.x - targetVec3d.x)
val e = abs(mob.y - targetVec3d.y)
val f = abs(mob.z - targetVec3d.z)
val closeEnough = d < maxDistanceToWaypoint.toDouble() && f < this.maxDistanceToWaypoint.toDouble() && e < 1.0
val closeEnough = d < maxDistanceToWaypoint.toDouble()
&& f < this.maxDistanceToWaypoint.toDouble()
&& e < (if (currentNode.type in verticallyPreciseNodeTypes) maxDistanceToWaypoint else 1.0).toDouble()
// Corner cutting is commented out because it makes pokemon and NPCs 'cut' the corner and fall into water or lava
if (closeEnough) {// || mob.navigation.canCutCorner(path!!.nextNode.type) && shouldTargetNextNodeInDirection(vec3d)) {
@@ -218,6 +224,10 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
override fun getGroundY(vec: Vec3): Double {
val blockGetter: BlockGetter = level
val blockPos = BlockPos.containing(vec)
if (pather.isFlying() && world.getBlockState(blockPos).isPathfindable(PathComputationType.AIR)) {
// If we can fly and we're airborne, return the current Y position
return vec.y
}
return if ((canFloat()) && blockGetter.getFluidState(blockPos).`is`(FluidTags.WATER)) vec.y + 0.5 else WalkNodeEvaluator.getFloorLevel(blockGetter, blockPos)
}
@@ -8,6 +8,8 @@
package com.cobblemon.mod.common.entity.npc
import com.cobblemon.mod.common.CobblemonBehaviours
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.config.ApplyBehaviours
import com.cobblemon.mod.common.api.ai.config.BehaviourConfig
@@ -16,12 +18,14 @@ import com.mojang.serialization.Dynamic
object NPCBrain {
fun configure(npcEntity: NPCEntity, npcClass: NPCClass, dynamic: Dynamic<*>) {
var behaviourConfigurations: List<BehaviourConfig> = npcClass.behaviours
var behaviourConfigurations: List<BehaviourConfig> = CobblemonBehaviours.autoPokemonBehaviours.flatMap { it.configurations } + npcClass.behaviours
if (npcEntity.behavioursAreCustom) {
behaviourConfigurations = listOf(ApplyBehaviours().apply { behaviours.addAll(npcEntity.behaviours) })
}
val ctx = BehaviourConfigurationContext()
ctx.addMemories(CobblemonMemories.DIALOGUES)
ctx.addMemories(CobblemonMemories.NPC_BATTLING)
ctx.apply(npcEntity, behaviourConfigurations, dynamic)
npcEntity.behaviours.clear()
npcEntity.behaviours.addAll(ctx.appliedBehaviours)
@@ -15,9 +15,7 @@ import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonEntities
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.CobblemonSensors
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.entity.PokemonSender
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
@@ -55,7 +53,6 @@ import com.cobblemon.mod.common.util.getBattleState
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.makeEmptyBrainDynamic
import com.cobblemon.mod.common.util.withNPCValue
import com.google.common.collect.ImmutableList
import com.mojang.authlib.GameProfile
import com.mojang.authlib.ProfileLookupCallback
import com.mojang.serialization.Dynamic
@@ -22,9 +22,13 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.BattleBuilder
import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.asUUID
import com.cobblemon.mod.common.util.getBooleanOrNull
import com.cobblemon.mod.common.util.getIntOrNull
import com.cobblemon.mod.common.util.getStringOrNull
import net.minecraft.world.entity.LivingEntity
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.ai.memory.MemoryModuleType
@@ -77,9 +81,34 @@ class NPCServerDelegate : NPCSideDelegate {
entity.server!!.playerList.getPlayerByName(paramString) ?: return@addFunction DoubleValue.ZERO
}
}
val format = (params.getStringOrNull(1)
?.let(BattleFormat::fromFormatIdentifier)
?: BattleFormat.GEN_9_SINGLES)
val setLevel = params.getIntOrNull(2) ?: -1
format.adjustLevel = setLevel
val rules = params.getStringOrNull(5)
?.split(",")
?.toSet()
?: emptySet()
val modifiedBattleFormat = BattleFormat.setBattleRules(
battleFormat = format,
rules = rules
)
val cloneParties = (setLevel != -1) || (params.getBooleanOrNull(3) ?: false)
val healFirst = params.getBooleanOrNull(4) ?: false
println("Cobblemon: Starting battle with format: $modifiedBattleFormat, level: ${modifiedBattleFormat.adjustLevel}")
val battleStartResult = BattleBuilder.pvn(
player = opponent,
npcEntity = entity
npcEntity = entity,
battleFormat = modifiedBattleFormat,
cloneParties = cloneParties,
healFirst = healFirst
)
var returnValue: MoValue = DoubleValue.ZERO
@@ -60,6 +60,7 @@ import com.cobblemon.mod.common.api.scheduling.afterOnServer
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.BagItems
import com.cobblemon.mod.common.battles.BattleBuilder
import com.cobblemon.mod.common.battles.BattleRegistry
@@ -131,6 +132,7 @@ import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.damagesource.DamageTypes
import net.minecraft.world.effect.MobEffectInstance
import net.minecraft.world.effect.MobEffects
import net.minecraft.world.entity.*
import net.minecraft.world.entity.ai.Brain
@@ -519,6 +521,23 @@ open class PokemonEntity(
super.removePassenger(passenger)
}
override fun thunderHit(level: ServerLevel, lightning: LightningBolt) {
// Deals with special cases in which Pokemon should either be immune or buffed by lightning strikes.
when (pokemon.ability.name) {
"lightningrod" -> {
this.addEffect(MobEffectInstance(MobEffects.DAMAGE_BOOST, 1200, 1))
}
"motordrive" -> {
this.addEffect(MobEffectInstance(MobEffects.MOVEMENT_SPEED, 1200, 0))
}
"voltabsorb" -> {
this.addEffect(MobEffectInstance(MobEffects.HEAL, 1, 1))
}
// Ground types shouldn't take lightning damage
else -> if (this.pokemon.types.none { it == ElementalTypes.GROUND }) super.thunderHit(level, lightning)
}
}
override fun tick() {
/* Addresses watchdog hanging that is completely bloody inexplicable. */
yBodyRot = Mth.wrapDegrees(yBodyRot)
@@ -1165,8 +1184,7 @@ open class PokemonEntity(
this.getUUID(),
canSitOnShoulder() && pokemon in player.party(),
!(pokemon.heldItemNoCopy().isEmpty && itemStack.isEmpty),
(!pokemon.cosmeticItem.isEmpty && itemStack.isEmpty) || cosmeticItemDefinition != null,
canRide
(!pokemon.cosmeticItem.isEmpty && itemStack.isEmpty) || cosmeticItemDefinition != null, canRide
).sendToPlayer(player)
}
else {
@@ -1196,7 +1214,8 @@ open class PokemonEntity(
if (this.health == 0F) {
pokemon.currentHealth = 0
} else {
} else if (this.ownerUUID != null) {
// Only touch battle HP for non-wild Pokémon so that quick ball et. al. aren't owerpowered.
pokemon.currentHealth = (pokemon.maxHealth * (this.health / this.maxHealth)).toInt()
}
true
@@ -50,6 +50,8 @@ import net.minecraft.world.level.pathfinder.PathType
import org.joml.Matrix3f
import org.joml.Vector3f
import java.util.*
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.round
@@ -155,11 +157,16 @@ class PokemonServerDelegate : PokemonSideDelegate {
fun updateHealth(pokemon: Pokemon) {
if (acknowledgedHPValue != pokemon.currentHealth) {
acknowledgedHPValue = pokemon.currentHealth
val currentHPRatio: Float = pokemon.currentHealth / pokemon.maxHealth.toFloat()
if (currentHPRatio.isFinite()) {
// Entity Health is stored as float but only ever in-/decreased by whole numbers.
entity.health = round(entity.maxHealth * currentHPRatio)
if (pokemon.currentHealth <= 0) {
entity.health = 0.0f
} else {
val currentHPRatio: Float = pokemon.currentHealth / pokemon.maxHealth.toFloat()
if (currentHPRatio.isFinite()) {
// Entity Health is stored as float but only ever in-/decreased by whole numbers.
entity.health = ceil(entity.maxHealth * currentHPRatio)
}
}
}
}
@@ -0,0 +1,99 @@
/*
* 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.entity.pokemon.ai.sensors
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.block.SaccharineLeafBlock
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.ai.sensing.Sensor
import net.minecraft.world.level.block.BeehiveBlock
import net.minecraft.world.level.block.Blocks
class BeeHiveSensor : Sensor<PokemonEntity>(300) {
override fun requires() = setOf(CobblemonMemories.HIVE_LOCATION)
override fun doTick(world: ServerLevel, entity: PokemonEntity) {
val brain = entity.brain
val currentHive = brain.getMemory(CobblemonMemories.HIVE_LOCATION).orElse(null)
if (currentHive != null) {
val state = world.getBlockState(currentHive)
if (!isValidHiveOrLeaf(state) || !hasReachableAdjacentSide(world, entity, currentHive)) {
brain.eraseMemory(CobblemonMemories.HIVE_LOCATION)
} else if (isAtMaxHoney(state)) {
brain.eraseMemory(CobblemonMemories.HIVE_LOCATION)
} else {
return // We already have a valid hive, no need to search for another
}
}
// Search for nearest hive/nest
val searchRadius = 16 // is this too big for us to use?
val centerPos = entity.blockPosition()
var closestHivePos: BlockPos? = null
var closestDistance = Double.MAX_VALUE
BlockPos.betweenClosedStream(
centerPos.offset(-searchRadius, -2, -searchRadius),
centerPos.offset(searchRadius, 2, searchRadius)
).forEach { pos ->
val state = world.getBlockState(pos)
if (isValidHiveOrLeaf(state) && !isAtMaxHoney(state) && hasReachableAdjacentSide(world, entity, pos)) {
val distance = pos.distToCenterSqr(centerPos.x + 0.5, centerPos.y + 0.5, centerPos.z + 0.5)
if (distance < closestDistance) {
closestDistance = distance
closestHivePos = pos.immutable()
}
}
}
if (closestHivePos != null) {
brain.setMemory(CobblemonMemories.HIVE_LOCATION, closestHivePos)
}
}
private fun isAtMaxHoney(state: net.minecraft.world.level.block.state.BlockState): Boolean {
return when {
isHiveBlock(state) -> state.getValue(BeehiveBlock.HONEY_LEVEL) == BeehiveBlock.MAX_HONEY_LEVELS
isSaccharineLeafBlock(state) -> state.getValue(SaccharineLeafBlock.AGE) == SaccharineLeafBlock.MAX_AGE
else -> true // Unknown block type
}
}
private fun isValidHiveOrLeaf(state: net.minecraft.world.level.block.state.BlockState): Boolean {
return isHiveBlock(state) || isSaccharineLeafBlock(state)
}
private fun isSaccharineLeafBlock(state: net.minecraft.world.level.block.state.BlockState): Boolean {
return state.block is SaccharineLeafBlock
}
private fun isHiveBlock(state: net.minecraft.world.level.block.state.BlockState): Boolean {
return state.`is`(Blocks.BEEHIVE) || state.`is`(Blocks.BEE_NEST)
}
private fun hasReachableAdjacentSide(world: ServerLevel, entity: PokemonEntity, pos: BlockPos): Boolean {
for (dir in Direction.entries) {
val adjacentPos = pos.relative(dir)
if (world.isEmptyBlock(adjacentPos)) {
val nav = entity.navigation
val path = nav.createPath(adjacentPos, 0)
if (path != null && path.canReach()) {
return true
}
}
}
return false
}
}
@@ -0,0 +1,55 @@
/*
* 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.entity.pokemon.ai.sensors
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.BlockTags
import net.minecraft.world.entity.ai.sensing.Sensor
import net.minecraft.world.level.block.state.BlockState
class FlowerSensor : Sensor<PokemonEntity>(120) {
override fun requires() = setOf(CobblemonMemories.NEARBY_FLOWERS)
override fun doTick(world: ServerLevel, entity: PokemonEntity) {
val brain = entity.brain
val searchRadius = 32
val centerPos = entity.blockPosition()
val flowers = mutableListOf<BlockPos>()
BlockPos.betweenClosedStream(
centerPos.offset(-searchRadius, -2, -searchRadius),
centerPos.offset(searchRadius, 2, searchRadius)
).forEach { pos ->
val state = world.getBlockState(pos)
if (isFlower(state) && isPathfindableTo(entity, pos)) {
flowers += pos.immutable()
}
}
if (!flowers.isEmpty()) {
brain.setMemory(CobblemonMemories.NEARBY_FLOWERS, flowers)
} else {
brain.eraseMemory(CobblemonMemories.NEARBY_FLOWERS)
}
}
private fun isFlower(state: BlockState): Boolean {
return state.`is`(BlockTags.FLOWERS)
}
private fun isPathfindableTo(entity: PokemonEntity, pos: BlockPos): Boolean {
val nav = entity.navigation
val path = nav.createPath(pos, 0)
return path != null && path.canReach()
}
}
@@ -0,0 +1,77 @@
package com.cobblemon.mod.common.entity.pokemon.ai.sensors
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.ai.sensing.Sensor
import net.minecraft.world.level.block.RotatedPillarBlock
class SaccharineLogSensor : Sensor<PokemonEntity>(200) {
override fun requires() = setOf(CobblemonMemories.SACCHARINE_LOG_LOCATION, CobblemonMemories.HAS_HONEY)
override fun doTick(world: ServerLevel, entity: PokemonEntity) {
val brain = entity.brain
// Only search if we're carrying honey
if (!brain.hasMemoryValue(CobblemonMemories.HAS_HONEY)) {
brain.eraseMemory(CobblemonMemories.SACCHARINE_LOG_LOCATION)
return
}
val current = brain.getMemory(CobblemonMemories.SACCHARINE_LOG_LOCATION).orElse(null)
if (current != null && isValidLog(world, entity, current)) return
if (current != null) brain.eraseMemory(CobblemonMemories.SACCHARINE_LOG_LOCATION)
val searchRadius = 16
val center = entity.blockPosition()
var best: BlockPos? = null
var bestDist = Double.MAX_VALUE
BlockPos.betweenClosedStream(
center.offset(-searchRadius, -2, -searchRadius),
center.offset(searchRadius, 2, searchRadius)
).forEach { pos ->
val state = world.getBlockState(pos)
if (state.block == CobblemonBlocks.SACCHARINE_LOG && // plain log only
state.getValue(RotatedPillarBlock.AXIS) == Direction.Axis.Y &&
hasReachableFace(world, entity, pos)
) {
val d = pos.distToCenterSqr(center.x + 0.5, center.y + 0.5, center.z + 0.5)
if (d < bestDist) {
bestDist = d
best = pos.immutable()
}
}
}
if (best != null) brain.setMemory(CobblemonMemories.SACCHARINE_LOG_LOCATION, best)
}
private fun isValidLog(world: ServerLevel, entity: PokemonEntity, pos: BlockPos): Boolean {
val state = world.getBlockState(pos)
return state.block == CobblemonBlocks.SACCHARINE_LOG &&
state.getValue(RotatedPillarBlock.AXIS) == Direction.Axis.Y &&
hasReachableFace(world, entity, pos)
}
private fun hasReachableFace(world: ServerLevel, entity: PokemonEntity, pos: BlockPos): Boolean {
for (dir in Direction.Plane.HORIZONTAL) {
val at = pos.relative(dir)
if (world.isEmptyBlock(at)) {
val path = entity.navigation.createPath(at, 0)
if (path != null && path.canReach()) return true
}
}
for (dir in arrayOf(Direction.UP, Direction.DOWN)) {
val at = pos.relative(dir)
if (world.isEmptyBlock(at)) {
val path = entity.navigation.createPath(at, 0)
if (path != null && path.canReach()) return true
}
}
return false
}
}
@@ -0,0 +1,85 @@
/*
* 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.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.OneShot
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.Vec3
object PathToBeeHiveTask {
fun create(): OneShot<in LivingEntity> {
return BehaviorBuilder.create {
it.group(
it.registered(MemoryModuleType.LOOK_TARGET),
it.absent(MemoryModuleType.WALK_TARGET),
it.registered(CobblemonMemories.POLLINATED),
it.registered(CobblemonMemories.HAS_HONEY),
it.present(CobblemonMemories.HIVE_LOCATION),
it.absent(CobblemonMemories.HIVE_COOLDOWN)
).apply(it) { lookTarget, walkTarget, pollinated, hasHoney, hiveMemory, hiveCooldown ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive) {
return@Trigger false
}
val brain = entity.brain
val hasPollinated = brain.hasMemoryValue(CobblemonMemories.POLLINATED)
val hasHoneyMem = brain.hasMemoryValue(CobblemonMemories.HAS_HONEY)
if (!hasPollinated && !hasHoneyMem) {
return@Trigger false
}
val hiveLocation = it.get(hiveMemory)
val sidesByClosest = listOf(
hiveLocation.north(),
hiveLocation.south(),
hiveLocation.east(),
hiveLocation.west()
).sortedBy { it.distSqr(entity.blockPosition()) }
var openSide = BlockPos.ZERO
for (side in sidesByClosest) {
if (world.getBlockState(side).isAir) {
openSide = side
break
}
}
if (openSide == BlockPos.ZERO) {
if (world.getBlockState(hiveLocation.above()).isAir) {
// If the above position is also air, we can use it
openSide = hiveLocation.above() // Fallback to above if no open sides found
} else if (world.getBlockState(hiveLocation.below()).isAir) {
// If the below position is also air, we can use it
openSide = hiveLocation.below() // Fallback to below if no open sides found
} else {
// If no open sides or above/below positions are found, we cannot proceed
return@Trigger false
}
}
val targetVec = Vec3.atCenterOf(openSide)
// Set path target toward hive
walkTarget.set(WalkTarget(targetVec, 0.35F, 0))
lookTarget.set(BlockPosTracker(targetVec.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
return@Trigger true
}
}
}
}
}
@@ -0,0 +1,59 @@
/*
* 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.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.OneShot
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.Vec3
object PathToFlowerTask {
fun create(): OneShot<in LivingEntity> {
return BehaviorBuilder.create {
it.group(
it.registered(MemoryModuleType.LOOK_TARGET),
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.NEARBY_FLOWERS),
it.absent(CobblemonMemories.POLLINATED),
it.absent(CobblemonMemories.HIVE_COOLDOWN)
).apply(it) { lookTarget, walkTarget, flowerMemory, pollinated, hiveCooldown ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
val flowerLocations = it.get(flowerMemory).map(Vec3::atCenterOf)
if (flowerLocations.isEmpty()) {
return@Trigger false
}
val sortedByDistance = flowerLocations.sortedBy { it.distanceTo(entity.position()) }
val closestHalf = sortedByDistance.subList(0, flowerLocations.size / 2 + 1)
if (closestHalf.isEmpty()) {
return@Trigger false
}
val targetVec = closestHalf.random()
// if we're really close to one then forget it, pollination is gonna occur
if (flowerLocations.any { it.distanceToSqr(entity.x, entity.y, entity.z) <= 1.0 }) {
return@Trigger false
}
walkTarget.set(WalkTarget(targetVec, 0.3F, 0))
lookTarget.set(BlockPosTracker(targetVec.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
return@Trigger true
}
}
}
}
}
@@ -0,0 +1,36 @@
package com.cobblemon.mod.common.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.OneShot
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.Vec3
object PathToSaccharineLogTask {
fun create(): OneShot<in LivingEntity> = BehaviorBuilder.create {
it.group(
it.registered(MemoryModuleType.LOOK_TARGET),
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.SACCHARINE_LOG_LOCATION),
it.present(CobblemonMemories.HAS_HONEY)
).apply(it) { lookTarget, walkTarget, logMem, hasHoney ->
Trigger { world, entity, _ ->
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
val log = it.get(logMem)
val target = Vec3.atCenterOf(log)
// if already close, let the slather task handle it
if (entity.distanceToSqr(target) <= 2.0) return@Trigger false
walkTarget.set(WalkTarget(target, 0.35F, 0))
lookTarget.set(BlockPosTracker(target.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
true
}
}
}
}
@@ -0,0 +1,68 @@
/*
* 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.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.block.SaccharineLeafBlock
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.OneShot
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.level.block.BeehiveBlock
import net.minecraft.world.phys.Vec3
object PlaceHoneyInHiveTask {
fun create(): OneShot<in LivingEntity> {
return BehaviorBuilder.create {
it.group(
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.POLLINATED),
it.present(CobblemonMemories.HIVE_LOCATION),
it.absent(CobblemonMemories.HIVE_COOLDOWN)
).apply(it) { walkTarget, pollinated, hiveMemory, hiveCooldown ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob) {
return@Trigger false
}
val hiveCooldown = 1200L
val hiveLocation = it.get(hiveMemory)
val targetVec = Vec3.atCenterOf(hiveLocation)
// if we are not close to it then end early
if (entity.distanceToSqr(targetVec) > 2.0) {
return@Trigger false
}
val state = world.getBlockState(hiveLocation)
val block = state.block
if (block is BeehiveBlock) {
val currentLevel = state.getValue(BeehiveBlock.HONEY_LEVEL)
if (currentLevel < BeehiveBlock.MAX_HONEY_LEVELS) {
world.setBlock(hiveLocation, state.setValue(BeehiveBlock.HONEY_LEVEL, currentLevel + 1), 3)
entity.brain.setMemoryWithExpiry(CobblemonMemories.HIVE_COOLDOWN, true, hiveCooldown)
entity.brain.eraseMemory(CobblemonMemories.POLLINATED)
}
} else if (block is SaccharineLeafBlock) {
val currentAge = state.getValue(SaccharineLeafBlock.AGE)
if (currentAge < SaccharineLeafBlock.MAX_AGE) {
world.setBlock(hiveLocation, state.setValue(SaccharineLeafBlock.AGE, currentAge + 1), 3)
entity.brain.setMemoryWithExpiry(CobblemonMemories.HIVE_COOLDOWN, true, hiveCooldown)
entity.brain.eraseMemory(CobblemonMemories.POLLINATED)
}
}
return@Trigger true
}
}
}
}
}
@@ -0,0 +1,46 @@
/*
* 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.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.OneShot
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.phys.Vec3
object PollinateFlowerTask {
fun create(): OneShot<in LivingEntity> {
return BehaviorBuilder.create {
it.group(
it.present(CobblemonMemories.NEARBY_FLOWERS),
it.absent(MemoryModuleType.WALK_TARGET),
it.absent(CobblemonMemories.POLLINATED),
it.absent(CobblemonMemories.HIVE_COOLDOWN)
).apply(it) { flowerMemory, walkTarget, pollinated, hiveCooldown ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
val flowerLocations = it.get(flowerMemory)
if (flowerLocations.any { it.distSqr(entity.blockPosition()) < 1 }) {
entity.brain.setMemoryWithExpiry(CobblemonMemories.POLLINATED, true, 20 * 20L) // 20 seconds to dump the pollen
if (entity is PokemonEntity) {
entity.pokemon.updateAspects()
}
}
return@Trigger true
}
}
}
}
}
@@ -0,0 +1,54 @@
package com.cobblemon.mod.common.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.block.SaccharineLogSlatheredBlock
import net.minecraft.core.Direction
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.OneShot
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.level.block.RotatedPillarBlock
import net.minecraft.world.phys.Vec3
import kotlin.random.Random
object SlatherHoneyOnLogTask {
fun create(): OneShot<in LivingEntity> = BehaviorBuilder.create {
it.group(
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.SACCHARINE_LOG_LOCATION),
it.present(CobblemonMemories.HAS_HONEY)
).apply(it) { walkTarget, logMem, hasHoney ->
Trigger { world, entity, _ ->
if (entity !is PathfinderMob) return@Trigger false
val pos = it.get(logMem)
val target = Vec3.atCenterOf(pos)
if (entity.distanceToSqr(target) > 2.0) return@Trigger false
val state = world.getBlockState(pos)
if (state.block != CobblemonBlocks.SACCHARINE_LOG) return@Trigger false
if (state.getValue(RotatedPillarBlock.AXIS) != Direction.Axis.Y) return@Trigger false
// pick a horizontal face with air in front
val face = Direction.Plane.HORIZONTAL.find {
world.getBlockState(pos.relative(it)).isAir
} ?: return@Trigger false
val newState = CobblemonBlocks.SACCHARINE_LOG_SLATHERED.defaultBlockState()
.setValue(RotatedPillarBlock.AXIS, Direction.Axis.Y)
.setValue(SaccharineLogSlatheredBlock.HONEY_TYPE, Random.nextInt(0, SaccharineLogSlatheredBlock.HONEY_TYPE_MAX + 1))
world.setBlock(pos, newState, 3)
world.playSound(null, pos, SoundEvents.HONEY_BLOCK_PLACE, SoundSource.BLOCKS, 1f, 1f)
entity.brain.eraseMemory(CobblemonMemories.SACCHARINE_LOG_LOCATION)
entity.brain.eraseMemory(CobblemonMemories.HAS_HONEY)
true
}
}
}
}
@@ -0,0 +1,42 @@
package com.cobblemon.mod.common.entity.pokemon.ai.tasks
import com.cobblemon.mod.common.CobblemonMemories
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.OneShot
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.level.block.BeehiveBlock
import net.minecraft.world.phys.Vec3
object StealHoneyFromHiveTask {
fun create(): OneShot<in LivingEntity> = BehaviorBuilder.create {
it.group(
it.absent(MemoryModuleType.WALK_TARGET),
it.present(CobblemonMemories.HIVE_LOCATION),
it.absent(CobblemonMemories.HAS_HONEY),
it.absent(CobblemonMemories.HIVE_COOLDOWN)
).apply(it) { walkTarget, hiveMem, hasHoney, cooldown ->
Trigger { world, entity, _ ->
if (entity !is PathfinderMob) return@Trigger false
val hive = it.get(hiveMem)
val center = Vec3.atCenterOf(hive)
if (entity.distanceToSqr(center) > 2.0) return@Trigger false
val state = world.getBlockState(hive)
val block = state.block
if (block is BeehiveBlock) {
val level = state.getValue(BeehiveBlock.HONEY_LEVEL)
if (level > 0) {
world.setBlock(hive, state.setValue(BeehiveBlock.HONEY_LEVEL, level - 1), 3)
entity.brain.setMemory(CobblemonMemories.HAS_HONEY, true)
entity.brain.setMemoryWithExpiry(CobblemonMemories.HIVE_COOLDOWN, true, 1200L)
return@Trigger true
}
}
false
}
}
}
}
@@ -36,16 +36,29 @@ object FlowHandler {
CobblemonEvents.BATTLE_STARTED_PRE.subscribe { CobblemonFlows.run(cobblemonResource("battle_started_pre"), it.context, it.functions) }
CobblemonEvents.BATTLE_STARTED_POST.subscribe { CobblemonFlows.run(cobblemonResource("battle_started_post"), it.context) }
CobblemonEvents.APRICORN_HARVESTED.subscribe { CobblemonFlows.run(cobblemonResource("apricorn_harvested"), it.context) }
CobblemonEvents.BERRY_HARVEST.subscribe { CobblemonFlows.run(cobblemonResource("berry_harvested"), it.context) }
CobblemonEvents.THROWN_POKEBALL_HIT.subscribe { CobblemonFlows.run(cobblemonResource("thrown_pokeball_hit"), it.context) }
CobblemonEvents.POKEMON_HEALED.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_healed"), it.context, it.functions) }
CobblemonEvents.POKEMON_ASPECTS_CHANGED.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_aspects_changed"), it.context) }
CobblemonEvents.LEVEL_UP_EVENT.subscribe { CobblemonFlows.run(cobblemonResource("level_up"), it.context, it.functions) }
CobblemonEvents.FRIENDSHIP_UPDATED.subscribe { CobblemonFlows.run(cobblemonResource("friendship_updated"), it.context, it.functions) }
CobblemonEvents.FULLNESS_UPDATED.subscribe { CobblemonFlows.run(cobblemonResource("fullness_updated"), it.context, it.functions) }
CobblemonEvents.HYPER_TRAINED_IV_PRE.subscribe { CobblemonFlows.run(cobblemonResource("hyper_trained_iv_pre"), it.context, it.functions) }
CobblemonEvents.HYPER_TRAINED_IV_POST.subscribe { CobblemonFlows.run(cobblemonResource("hyper_trained_iv_post"), it.context) }
CobblemonEvents.EV_GAINED_EVENT_PRE.subscribe { CobblemonFlows.run(cobblemonResource("ev_gained_pre"), it.context, it.functions) }
CobblemonEvents.EV_GAINED_EVENT_POST.subscribe { CobblemonFlows.run(cobblemonResource("ev_gained_post"), it.context) }
CobblemonEvents.EXPERIENCE_GAINED_EVENT_PRE.subscribe { CobblemonFlows.run(cobblemonResource("experience_gained_pre"), it.context, it.functions) }
CobblemonEvents.EXPERIENCE_GAINED_EVENT_POST.subscribe { CobblemonFlows.run(cobblemonResource("experience_gained_post"), it.context) }
CobblemonEvents.LOOT_DROPPED.subscribe { CobblemonFlows.run(cobblemonResource("loot_dropped"), it.context) }
CobblemonEvents.POKEMON_FAINTED.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_fainted"), it.context) }
CobblemonEvents.POKEMON_GAINED.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_gained"), it.context, it.functions) }
CobblemonEvents.POKEMON_SEEN.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_seen"), it.context, it.functions) }
CobblemonEvents.POKEMON_SCANNED.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_scanned"), it.context) }
CobblemonEvents.POKEMON_SENT_PRE.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_sent_pre"), it.context, it.functions) }
CobblemonEvents.POKEMON_SENT_POST.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_sent_post"), it.context) }
CobblemonEvents.POKEMON_RELEASED_EVENT_PRE.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_released_pre"), it.getContext(), it.functions) }
CobblemonEvents.POKEMON_RELEASED_EVENT_POST.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_released_post"), it.getContext()) }
CobblemonEvents.POKEMON_CATCH_RATE.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_catch_rate_calculated"), it.context, it.functions) }
CobblemonEvents.POKE_BALL_CAPTURE_CALCULATED.subscribe { CobblemonFlows.run(cobblemonResource("poke_ball_capture_calculated"), it.context, it.functions) }
CobblemonEvents.EVOLUTION_TESTED.subscribe { CobblemonFlows.run(cobblemonResource("evolution_tested"), it.context, it.functions) }
CobblemonEvents.EVOLUTION_ACCEPTED.subscribe { CobblemonFlows.run(cobblemonResource("evolution_accepted"), it.context, it.functions) }
@@ -53,10 +66,25 @@ object FlowHandler {
CobblemonEvents.POKEMON_NICKNAMED.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_nicknamed"), it.getContext()) }
CobblemonEvents.HELD_ITEM_PRE.subscribe { CobblemonFlows.run(cobblemonResource("held_item_pre"), it.getContext(), it.functions) }
CobblemonEvents.HELD_ITEM_POST.subscribe { CobblemonFlows.run(cobblemonResource("held_item_post"), it.getContext()) }
CobblemonEvents.COSMETIC_ITEM_PRE.subscribe { CobblemonFlows.run(cobblemonResource("cosmetic_item_pre"), it.getContext(), it.functions) }
CobblemonEvents.COSMETIC_ITEM_POST.subscribe { CobblemonFlows.run(cobblemonResource("cosmetic_item_post"), it.getContext()) }
CobblemonEvents.FOSSIL_REVIVED.subscribe { CobblemonFlows.run(cobblemonResource("fossil_revived"), it.context) }
CobblemonEvents.BAIT_SET.subscribe { CobblemonFlows.run(cobblemonResource("bait_set"), it.context, it.functions) }
CobblemonEvents.BAIT_SET_PRE.subscribe { CobblemonFlows.run(cobblemonResource("bait_set_pre"), it.context, it.functions) }
CobblemonEvents.BAIT_CONSUMED.subscribe { CobblemonFlows.run(cobblemonResource("bait_consumed"), it.context) }
CobblemonEvents.POKEROD_CAST_PRE.subscribe { CobblemonFlows.run(cobblemonResource("pokerod_cast_pre"), it.context, it.functions) }
CobblemonEvents.POKEROD_CAST_POST.subscribe { CobblemonFlows.run(cobblemonResource("pokerod_cast_post"), it.context) }
CobblemonEvents.POKEROD_REEL.subscribe { CobblemonFlows.run(cobblemonResource("pokerod_reel"), it.context, it.functions) }
CobblemonEvents.BOBBER_SPAWN_POKEMON_PRE.subscribe { CobblemonFlows.run(cobblemonResource("bobber_spawn_pokemon_pre"), it.context, it.functions) }
CobblemonEvents.BOBBER_SPAWN_POKEMON_POST.subscribe { CobblemonFlows.run(cobblemonResource("bobber_spawn_pokemon_post"), it.context) }
CobblemonEvents.TRADE_COMPLETED.subscribe { CobblemonFlows.run(cobblemonResource("trade_completed"), it.context) }
CobblemonEvents.WALLPAPER_UNLOCKED_EVENT.subscribe { CobblemonFlows.run(cobblemonResource("wallpaper_unlocked"), it.context, it.functions) }
CobblemonEvents.CHANGE_PC_BOX_WALLPAPER_EVENT_PRE.subscribe { CobblemonFlows.run(cobblemonResource("change_pc_box_wallpaper_pre"), it.context, it.functions) }
CobblemonEvents.CHANGE_PC_BOX_WALLPAPER_EVENT_POST.subscribe { CobblemonFlows.run(cobblemonResource("change_pc_box_wallpaper"), it.context) }
CobblemonEvents.COLLECT_EGG.subscribe { CobblemonFlows.run(cobblemonResource("collect_egg"), it.context, it.functions) }
CobblemonEvents.HATCH_EGG_PRE.subscribe { CobblemonFlows.run(cobblemonResource("hatch_egg_pre"), it.context, it.functions) }
CobblemonEvents.HATCH_EGG_POST.subscribe { CobblemonFlows.run(cobblemonResource("hatch_egg_post"), it.context) }
CobblemonEvents.SHOULDER_MOUNT.subscribe { CobblemonFlows.run(cobblemonResource("shoulder_mount"), it.context, it.functions) }
PlatformEvents.SERVER_PLAYER_LOGIN.subscribe(priority = Priority.LOW) { CobblemonFlows.run(cobblemonResource("player_logged_in"), it.context) }
PlatformEvents.SERVER_PLAYER_LOGOUT.subscribe(priority = Priority.HIGH) { CobblemonFlows.run(cobblemonResource("player_logged_out"), it.context) }
@@ -140,7 +140,7 @@ class AprijuiceItem(val type: Apricorn): CobblemonItem(Properties().stacksTo(16)
val hasFlavour = stack.get(CobblemonItemComponents.FLAVOUR)?.flavours?.any { it.value > 0 } == true
if (!hasFlavour && user is Player && !world.isClientSide) {
user.foodData.eat(1, 0.1f)
user.foodData.eat(4, 1.2f)
stack.consume(1, user)
}
@@ -383,7 +383,6 @@ object CobblemonItemGroups {
private fun consumableEntries(displayContext: ItemDisplayParameters, entries: Output) {
entries.accept(CobblemonItems.ROASTED_LEEK)
entries.accept(CobblemonItems.LEEK_AND_POTATO_STEW)
entries.accept(CobblemonItems.BRAISED_VIVICHOKE)
entries.accept(CobblemonItems.VIVICHOKE_DIP)
entries.accept(CobblemonItems.TART_APPLE)
entries.accept(CobblemonItems.SWEET_APPLE)
@@ -399,8 +398,9 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.CANDIED_APPLE)
entries.accept(CobblemonItems.CANDIED_BERRY)
entries.accept(CobblemonItems.FRIED_RICE)
entries.accept(CobblemonItems.POTATO_MOCHI)
entries.accept(CobblemonItems.PONIGIRI)
entries.accept(CobblemonItems.SINISTER_TEA)
entries.accept(CobblemonItems.PEWTER_CRUNCHIES)
entries.accept(CobblemonItems.RAGE_CANDY_BAR)
@@ -409,7 +409,6 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.OLD_GATEAU)
entries.accept(CobblemonItems.CASTELIACONE)
entries.accept(CobblemonItems.LUMIOSE_GALETTE)
entries.accept(CobblemonItems.SHALOUR_SABLE)
entries.accept(CobblemonItems.BIG_MALASADA)
entries.accept(CobblemonItems.SMOKED_TAIL_CURRY)
entries.accept(CobblemonItems.OPEN_FACED_SANDWICH)
@@ -419,6 +418,7 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.FINE_REMEDY)
entries.accept(CobblemonItems.SUPERB_REMEDY)
entries.accept(CobblemonItems.HEAL_POWDER)
entries.accept(CobblemonItems.MOOMOO_MILK)
entries.accept(CobblemonItems.MEDICINAL_BREW)
entries.accept(CobblemonItems.POTION)
@@ -518,10 +518,23 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.JOLLY_MINT)
entries.accept(CobblemonItems.NAIVE_MINT)
entries.accept(CobblemonItems.SERIOUS_MINT)
entries.accept(CobblemonItems.MOOMOO_MILK)
entries.accept(CobblemonItems.ABILITY_CAPSULE)
entries.accept(CobblemonItems.ABILITY_PATCH)
entries.accept(CobblemonItems.POKE_BAIT)
entries.accept(CobblemonItems.LURE_CAKE)
entries.accept(CobblemonItems.POKE_CAKE)
entries.accept(CobblemonItems.POKE_PUFF)
entries.accept(CobblemonItems.APRIJUICE_RED)
entries.accept(CobblemonItems.APRIJUICE_YELLOW)
entries.accept(CobblemonItems.APRIJUICE_GREEN)
entries.accept(CobblemonItems.APRIJUICE_BLUE)
entries.accept(CobblemonItems.APRIJUICE_PINK)
entries.accept(CobblemonItems.APRIJUICE_BLACK)
entries.accept(CobblemonItems.APRIJUICE_WHITE)
}
private fun evolutionItemEntries(displayContext: ItemDisplayParameters, entries: Output) {
@@ -742,7 +755,6 @@ object CobblemonItemGroups {
injector.putAfter(CobblemonItems.MEDICINAL_LEEK, Items.POISONOUS_POTATO)
injector.putAfter(CobblemonItems.ROASTED_LEEK, CobblemonItems.MEDICINAL_LEEK)
injector.putAfter(CobblemonItems.BRAISED_VIVICHOKE, CobblemonItems.ROASTED_LEEK)
injector.putAfter(CobblemonItems.WHIPPED_DREAM, Items.PUMPKIN_PIE)
injector.putAfter(CobblemonItems.STRAWBERRY_SWEET, CobblemonItems.WHIPPED_DREAM)
@@ -755,24 +767,24 @@ object CobblemonItemGroups {
injector.putAfter(CobblemonItems.CANDIED_APPLE, CobblemonItems.RIBBON_SWEET)
injector.putAfter(CobblemonItems.CANDIED_BERRY, CobblemonItems.CANDIED_APPLE)
injector.putAfter(CobblemonItems.FRIED_RICE, CobblemonItems.CANDIED_BERRY)
injector.putAfter(CobblemonItems.POTATO_MOCHI, CobblemonItems.FRIED_RICE)
injector.putAfter(CobblemonItems.POTATO_MOCHI, CobblemonItems.CANDIED_BERRY)
injector.putAfter(CobblemonItems.PONIGIRI, CobblemonItems.POTATO_MOCHI)
injector.putAfter(CobblemonItems.PEWTER_CRUNCHIES, CobblemonItems.POTATO_MOCHI)
injector.putAfter(CobblemonItems.PEWTER_CRUNCHIES, CobblemonItems.PONIGIRI)
injector.putAfter(CobblemonItems.RAGE_CANDY_BAR, CobblemonItems.PEWTER_CRUNCHIES)
injector.putAfter(CobblemonItems.LAVA_COOKIE, CobblemonItems.RAGE_CANDY_BAR)
injector.putAfter(CobblemonItems.JUBILIFE_MUFFIN, CobblemonItems.LAVA_COOKIE)
injector.putAfter(CobblemonItems.OLD_GATEAU, CobblemonItems.JUBILIFE_MUFFIN)
injector.putAfter(CobblemonItems.CASTELIACONE, CobblemonItems.OLD_GATEAU)
injector.putAfter(CobblemonItems.LUMIOSE_GALETTE, CobblemonItems.CASTELIACONE)
injector.putAfter(CobblemonItems.SHALOUR_SABLE, CobblemonItems.LUMIOSE_GALETTE)
injector.putAfter(CobblemonItems.BIG_MALASADA, CobblemonItems.SHALOUR_SABLE)
injector.putAfter(CobblemonItems.BIG_MALASADA, CobblemonItems.LUMIOSE_GALETTE)
injector.putAfter(CobblemonItems.SMOKED_TAIL_CURRY, CobblemonItems.BIG_MALASADA)
injector.putAfter(CobblemonItems.OPEN_FACED_SANDWICH, CobblemonItems.SMOKED_TAIL_CURRY)
injector.putAfter(CobblemonItems.LEEK_AND_POTATO_STEW, Items.RABBIT_STEW)
injector.putAfter(CobblemonItems.VIVICHOKE_DIP, CobblemonItems.LEEK_AND_POTATO_STEW)
injector.putAfter(CobblemonItems.SINISTER_TEA, Items.MILK_BUCKET)
}
private fun toolsAndUtilitiesInjections(injector: Injector) {
@@ -25,8 +25,8 @@ class PonigiriItem : Item(
Properties().stacksTo(16)
.food(FoodProperties.Builder()
.alwaysEdible()
.nutrition(2)
.saturationModifier(0.55f)
.nutrition(4)
.saturationModifier(0.275f)
.build())
) {
override fun getName(stack: ItemStack): Component {
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.client.effect
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.client.net.effect.SaccharineLogBlockParticlesHandler
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.core.BlockPos
import net.minecraft.network.RegistryFriendlyByteBuf
/**
* Handled by [SaccharineLogBlockParticlesHandler].
*/
class SaccharineLogBlockParticlesPacket(
val blockPos: BlockPos,
val entityPos: BlockPos?
) : NetworkPacket<SaccharineLogBlockParticlesPacket> {
override val id = ID
companion object {
val ID = cobblemonResource("saccharine_log_block_particles")
fun decode(buffer: RegistryFriendlyByteBuf): SaccharineLogBlockParticlesPacket =
SaccharineLogBlockParticlesPacket(
buffer.readBlockPos(),
buffer.readNullable { buffer.readBlockPos() }
)
}
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeBlockPos(blockPos)
buffer.writeNullable(entityPos) { _, v -> buffer.writeBlockPos(v) }
}
}
@@ -1155,7 +1155,11 @@ open class Pokemon : ShowdownIdentifiable {
*/
fun swapHeldItem(stack: ItemStack, decrement: Boolean = true): ItemStack {
val existing = this.heldItem()
CobblemonEvents.HELD_ITEM_PRE.postThen(HeldItemEvent.Pre(this, stack, existing, decrement), ifSucceeded = { event ->
val event = HeldItemEvent.Pre(this, stack, existing, decrement)
if (!isClient) {
CobblemonEvents.HELD_ITEM_PRE.post(event)
}
if (!event.isCanceled) {
val giving = event.receiving.copy().apply { count = 1 }
if (event.decrement) {
event.receiving.shrink(1)
@@ -1166,13 +1170,37 @@ open class Pokemon : ShowdownIdentifiable {
StashHandler.giveHeldItem(it)
}
return event.returning
})
}
return stack
}
/**
* Returns a copy of the cosmetic item.
* In order to change the [ItemStack] use [swapCosmeticItem].
*
* @return A copy of the [ItemStack] cosmetic item held by this Pokémon.
*/
fun cosmeticItem(): ItemStack = this.cosmeticItem.copy()
/**
* Swaps out the current [cosmeticItem] for the given [stack].
* The assigned [cosmeticItem] will always have the [ItemStack.count] of 1.
*
* The behavior of this method may be modified by third party, please see the [HeldItemEvent].
*
* @param stack The new [ItemStack] being set as the cosmetic item.
* @param decrement If the given [stack] should have [ItemStack.decrement] invoked with the parameter of 1. Default is true.
* @return The existing [ItemStack] being held or the [stack] if [HeldItemEvent.Pre] is canceled.
*
* @see [HeldItemEvent]
*/
fun swapCosmeticItem(stack: ItemStack, decrement: Boolean = true): ItemStack {
val existing = this.cosmeticItem.copy()
CobblemonEvents.COSMETIC_ITEM_PRE.postThen(HeldItemEvent.Pre(this, stack, existing, decrement), ifSucceeded = { event ->
val event = HeldItemEvent.Pre(this, stack, existing, decrement)
if (!isClient) {
CobblemonEvents.COSMETIC_ITEM_PRE.post(event)
}
if (!event.isCanceled) {
val giving = event.receiving.copy().apply { count = 1 }
if (event.decrement) {
event.receiving.shrink(1)
@@ -1180,7 +1208,7 @@ open class Pokemon : ShowdownIdentifiable {
this.cosmeticItem = giving
CobblemonEvents.COSMETIC_ITEM_POST.post(HeldItemEvent.Post(this, this.cosmeticItem.copy(), event.returning.copy(), event.decrement))
return event.returning
})
}
return stack
}
@@ -1191,6 +1219,13 @@ open class Pokemon : ShowdownIdentifiable {
*/
fun removeHeldItem(): ItemStack = this.swapHeldItem(ItemStack.EMPTY)
/**
* Swaps out the current [cosmeticItem] for an [ItemStack.EMPTY].
*
* @return The existing [ItemStack] being held.
*/
fun removeCosmeticItem(): ItemStack = this.swapCosmeticItem(ItemStack.EMPTY)
fun addPotentialMark(mark: Mark) {
potentialMarks.add(mark)
if (!isClient) onChange(MarkPotentialAddUpdatePacket({ this }, mark))
@@ -1671,6 +1706,58 @@ open class Pokemon : ShowdownIdentifiable {
return ability
}
/**
* Teaches all moves that are potentially available to this Pokémon.
*
* TO-DO: Implement Legacy source moves.
* @param includeLegacy If moves that were only learnable in previous versions should be considered valid and taught.
*/
fun teachLearnableMoves(includeLegacy: Boolean = true) {
// Get all learnable moves
val possibleMoves = form.moves.getAllLegalMoves()
// Add all possible moves to the moveset
val possibleMovesSet = HashSet<BenchedMove>()
val query = if (includeLegacy) LearnsetQuery.ANY else LearnsetQuery.LEGAL
for (move in possibleMoves) {
if (query.canLearn(move, this.form.moves) && moveSet.none { it.template == move }) {
possibleMovesSet.add(BenchedMove(move, 0))
}
}
this.benchedMoves.addAll(possibleMovesSet)
this.benchedMoves.update()
moveSet.update()
}
/**
* Validates the moveset of this Pokémon, removing any invalid moves.
* This will also remove any benched moves that cannot be learned by the current form.
*
* TO-DO: Implement Legacy source moves.
* @param includeLegacy If moves that were only learnable in previous versions should be considered valid.
*/
fun validateMoveset(includeLegacy: Boolean = true) {
// Validate the moveset, removing any invalid moves
val query = if (includeLegacy) LearnsetQuery.ANY else LearnsetQuery.LEGAL
moveSet.doWithoutEmitting {
benchedMoves.doWithoutEmitting {
for (i in 0 until MoveSet.MOVE_COUNT) {
val move = this.moveSet[i]
if (move != null && !query.canLearn(move.template, this.form.moves)) {
this.moveSet.setMove(i, null)
}
}
val benchedIterator = this.benchedMoves.iterator()
while (benchedIterator.hasNext()) {
val benchedMove = benchedIterator.next()
if (!query.canLearn(benchedMove.moveTemplate, this.form.moves)) {
benchedIterator.remove()
}
}
}
}
moveSet.update()
}
fun initializeMoveset(preferLatest: Boolean = true) {
val possibleMoves = form.moves.getLevelUpMovesUpTo(level).toMutableList()
moveSet.doWithoutEmitting {
@@ -1700,6 +1787,30 @@ open class Pokemon : ShowdownIdentifiable {
moveSet.update()
}
/**
* Removes the specified [move] from the Pokémon's current move set if it exists.
*/
fun unlearnMove(move: MoveTemplate) {
moveSet.doWithoutEmitting {
for (i in 0 until MoveSet.MOVE_COUNT) {
val currentMove = this.moveSet[i]
if (currentMove != null && currentMove.template == move) {
this.moveSet.setMove(i, null)
}
}
this.benchedMoves.doWithoutEmitting {
val benchedIterator = this.benchedMoves.iterator()
while (benchedIterator.hasNext()) {
val benchedMove = benchedIterator.next()
if (benchedMove.moveTemplate == move) {
benchedIterator.remove()
}
}
}
}
moveSet.update()
}
fun getMaxRideBoost(stat: RidingStat): Int {
return form.riding.stats[stat]?.ranges?.maxOf { it.value.endInclusive } ?: 0
}
@@ -1924,6 +2035,7 @@ open class Pokemon : ShowdownIdentifiable {
if (packet != null && storeCoordinates.get() != null) {
notify(packet)
}
changeObservable.emit(this)
}
@@ -68,7 +68,10 @@ object PokemonBrain {
CobblemonSensors.POKEMON_DROWSY,
CobblemonSensors.POKEMON_ADULT,
SensorType.IS_IN_WATER,
CobblemonSensors.NEARBY_GROWABLE_CROPS
CobblemonSensors.NEARBY_GROWABLE_CROPS,
CobblemonSensors.NEARBY_BEE_HIVE,
CobblemonSensors.NEARBY_FLOWER,
CobblemonSensors.NEARBY_SACCHARINE_LOG
// CobblemonSensors.BATTLING_POKEMON,
// CobblemonSensors.NPC_BATTLING
@@ -184,9 +187,15 @@ object PokemonBrain {
MemoryModuleType.AVOID_TARGET,
CobblemonMemories.POKEMON_SLEEPING,
CobblemonMemories.RECENTLY_ATE_GRASS,
CobblemonMemories.HIVE_LOCATION,
CobblemonMemories.HIVE_COOLDOWN,
CobblemonMemories.NEARBY_FLOWERS,
CobblemonMemories.POLLINATED,
CobblemonMemories.RECENTLY_ATE_GRASS,
CobblemonMemories.HERD_LEADER,
CobblemonMemories.HERD_SIZE,
CobblemonMemories.ATTACK_TARGET_DATA
CobblemonMemories.ATTACK_TARGET_DATA,
CobblemonMemories.SACCHARINE_LOG_LOCATION
)
private fun coreTasks(pokemon: Pokemon) = buildList<Pair<Int, BehaviorControl<in PokemonEntity>>> {
@@ -9,11 +9,13 @@
package com.cobblemon.mod.common.pokemon.aspects
import com.cobblemon.mod.common.CobblemonCosmeticItems
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
import com.cobblemon.mod.common.api.pokemon.aspect.SingleConditionalAspectProvider
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.getMemorySafely
import com.cobblemon.mod.common.util.server
val SHINY_ASPECT = object : SingleConditionalAspectProvider {
@@ -38,6 +40,9 @@ val GENDER_ASPECT = object : AspectProvider {
val COSMETIC_SLOT_ASPECT = object : AspectProvider {
override fun provide(pokemon: Pokemon): Set<String> {
val server = server() ?: return emptySet()
if (pokemon.cosmeticItem.isEmpty) {
return emptySet()
}
val cosmeticItem = CobblemonCosmeticItems.findValidCosmeticForPokemonAndItem(server.registryAccess(), pokemon, pokemon.cosmeticItem)
return cosmeticItem?.aspects?.toSet() ?: emptySet()
}

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