Compare commits

..
Author SHA1 Message Date
MeAlam1 58cdf2e756 Added Debug Logging to hopefully find the issue 2026-08-06 18:26:43 +02:00
5823 changed files with 65521 additions and 450068 deletions
-3
View File
@@ -157,9 +157,6 @@ build:
paths:
- neoforge/build/libs
- fabric/build/libs
exclude:
- neoforge/build/libs/*-dev-shadow.jar
- fabric/build/libs/*-dev-shadow.jar
publish:
stage: deploy
+493 -6
View File
@@ -1,31 +1,518 @@
# Changelog
## [1.8.x (MONTH xth, 2026)](#1-8-x)
## [1.8.0 (MONTH Xth, 2026)](#1-8-0)
### Additions
- Added config options to award experience to fainted Pokémon for opponents they helped defeat (`awardExperienceToFaintedPokemon`) and to still award experience for defeated enemies even after battle loss/forfeit (`awardExperienceOnBattleLoss`)
- Party Overlay Exp Gained Animation will now display the amount of EXP the Pokémon gained
- Added a toast to notify players of how they can scroll through their party. Did you know you can hold R and scroll instead of using arrow keys??!
- Added a new layer property that makes a layer scroll in a direction. "scrolling": {"speedU": 0.1, "speedV": 0.1}
- Added missing crops to Botany Pots integration
- Added a way to obtain Porygon by inserting the Upgrade or Dubious Disc into a data monitor
- Added a new `cobblemon:structure`, which is a feature without any restrictions allowing the Builders to place certain structures in much more specific manner.
- Added Configs for Infinite Use TMs and Unlocking all TMs
- Added Type Gem blocks
- Added Type Gem Clusters
- Added TMs
- Added TM Machine
- Added Deepslate Core Block
- Added /givetm Command
- Added advancement triggers for learning TMs: `cobblemon:has_learn_specific_tm` (per-TM), `cobblemon:has_learn_all_tm` (all TMs learned), and `cobblemon:tms_learned` (count-based), backed by a new `totalTMLearnedCount` on player advancement data.
- Added `cobblemon:catch_alpha_pokemon` advancement trigger and `totalAlphaCaptureCount` tracking for capturing Alpha Pokémon.
- Added Move Dex to Pokédex
- Added the Habitat Block, a spawner block for servers and adventure maps that controls spawning in an area.
- Added Disk Rack for storing disks and being an audio sequencer for Noteblocks
- Added intrinsic scaling to all Pokémon (+-5%). Some rendering cases, such as shoulder mounting, revert to 1.0 scaling to preserve animation accuracy.
- Added scaling to "baby" Pokémon (smaller (-20%) at level 1 → normally sized at level 10)
- Added conditional seats for riding. Seats can be gated by molang such as "is_alpha".
- Added Alpha Pokémon
- Added alpha scaling (alphas are larger than their normal counterparts)
- Alphas will always lead herds and will spawn in herds
- Alphas spawn with two random TM moves from their learnset
- Alpha Pokémon will always defend themselves from players that attack them
- Added alpha level matching and stat boost (based on level)
- Added pitch-down and reverb to alpha cries
- Added alpha Pokémon bait effect
- Added alpha eye rendering including eye overlay, bloom, eye trail, and particles.
- Added cobweb block slowness immunity for spider Pokémon
- Added 49 new habitat structures. 34 as typical Minecraft structures and 15 as structures that get placed as biome features to allow for more natural placement in generation. Structures placed as biome features will not be able to be located with the `/locate structure` command.
- Vanilla village house template pools have been adjusted to account for the new abandoned village structure habitat. Weights have changed within the JSON file for every piece to ensure too many habitats don't generate in a single village.
- Added 10 new ruin structures.
- rooted_arch_ruins & crumbling_arch_ruins have been transferred over to biome features, allowing for better placement in generation.
- All ruin structures that did not generate with a gimmighoul/gilded chest before will now generate with one.
- All ruin structures gilded chest loot tables have been updated to include a guaranteed TM not learned naturally by any Pokémon through their level-up moveset. Each ruin gets its own specific TM, so you can now go hunting ruins to complete your move dex!
- All fossil structures have been transferred over to biome features, allowing for better placement in generation.
- The prehistoric_underwater_fissure & prehistoric_submerged_impact fossil structures now use the biome tag `#cobblemon:is_temperate_ocean`.
- Gimmighoul towers now generate at ground level rather than sinking one block into the ground.
- A new shipwreck cove structure called the "magma_shipwreck_cove" has been added.
- Shipwreck coves now generate slightly more frequently.
- Added equipment loot tables for shipwreck cove enemies.
- Maps from abandoned fishing boats leading to shipwreck coves now will skip explored chunks, allowing the player to find more shipwreck coves easier.
- The lush shipwreck cove's phantom mob spawner has been replaced with breezes.
- The lush shipwreck cove structure itself has been slightly cleaned up to be less noisy with the blocks it is built out of.
- All shipwreck coves' spawners have been adjusted to give compatible mobs enchanted iron or diamond armour and weapons while in its ominous state. The armour given to the mobs will also use the automaton trim. We upped the difficulty because it was too easy now that your Pokémon can fight for you.
- One TM of the elemental hyper beams has been added as guaranteed loot to the main treasure chest in every shipwreck cove. (Magma cove gets blast burn, lush cove gets frenzy plant, and submerged cove gets hydro cannon)
- Added 7 new advancements
- Pressing the inventory key now closes the Pokémon interact wheel GUI
- Pokémon cries now play when clicking Pokémon in the summary screen.
- Pressing the Summary key while either the starter selection or summary screen are open now closes those screens
- Ice type Pokémon are now immune to Packed Snow
- Held item visibility can now be controlled by the held_item_visible command argument.
### New rideable Pokémon
- Crobat
- Skarmory
- Milotic
- Archeops
- Goodra
- Goodra (Hisuian Form)
- Drampa
- Duraludon
- Archaludon
### Pokémon Added
#### Generation 3
- Wurmple
- Silcoon
- Beautifly
- Cascoon
- Dustox
- Skitty
- Delcatty
- Seviper
- Zangoose
#### Generation 4
- Cherubi
- Cherrim
- Stunky
- Skuntank
#### Generation 5
- Audino
- Pawniard
- Bisharp
#### Generation 6
- Pancham (+ Cosmetic with Black Glasses)
- Pangoro (+ Cosmetic with Black Glasses)
#### Generation 7
- Rockruff
- Lycanroc
#### Generation 8
- Rolycoly
- Carkol
- Coalossal
- Runerigus
- Snom
- Frosmoth
- Indeedee
- Duraludon
- Applin
- Flapple
- Appletun
#### Generation 9
- Nymble
- Lokix
- Kingambit
- Dipplin
- Archaludon
- Hydrapple
#### Regional Forms
- Sandshrew (Alolan Form)
- Sandslash (Alolan Form)
- Yamask (Galarian Form)
- Avalugg (Hisuian Form)
### Model updates for the following Pokémon
- Sandshrew
- Sandslash
- Nidoking
- Zubat
- Golbat
- Crobat
- Ditto
- Porygon
- Porygon2
- Porygon-Z
- Skarmory
- Smoochum
- Jynx
- Popplio
- Brionne
- Primarina
- Fletchling
- Fletchinder
- Talonflame
- Sableye
- Mawile
- Goomy
- Sliggoo
- Goodra
- Nacli
- Naclstack
- Garganacl
- Bidoof
- Cranidos
- Chatot
- Yamask
- Cofagrigus
- Tyranitar
- Turtonator
- Golett
- Corsola
- Arbok (New Patterns: Angry, Cartoon, Clown, Comedic, Crow, Grin, Ink, Modern, Rain, Scribble, Silence, Sloth, Suspicious, Will)
- ... and Substitute
#### Model changes to the regional bias resource pack:
- Added Mime Jr. (Galar Bias)
- Added Bergmite (Hisui Bias)
- Changed models for every existing regional bias
### Animation updates for the following Pokémon
- Kakuna
- Grimer
- Nidoran♀ (+ New shoulder mount)
- Nidorina
- Nidoran♂ (+ New shoulder mount)
- Sandshrew
- Diglett
- Dugtrio
- Graveler
- Happiny
- Chansey
- Blissey
- Poliwhirl
- Poliwrath
- Zubat
- Vulpix
- Drowzee
- Hypno
- Noctowl
- Skarmory
- Skitty
- Flygon
- Shellos
- Riolu
- Lucario
- Purrloin
- Liepard
- Joltik
- Galvantula
- Nidoking
- Fletchling
- Fletchinder
- Talonflame
- Archeops
- Drampa
- Sewaddle
- Swadloon
- Leavanny
- Goomy
- Sliggoo
- Goodra
- Shedinja
- Skwovet
- Greedent
- Bidoof
- Cranidos
- Chatot
- Yamask
- Cofagrigus
- Luvdisc
- Staryu
- Starmie
- Togetic
- Rellor
- Rabsca
- Yungoos
- Gumshoos
- Cottonee
- Whimsicott
- Corsola
#### Added sounds to the following moves
- Absorb
- Aurora Beam
- Double Team
- Draco Meteor
- Fire Punch
- Fire Spin
- Flame Charge
- Flame Wheel
- Giga Drain
- Ice Punch
- Leech Life
- Magical Leaf
- Mega Drain
- Minimize
- Poison Gas
- Rock Throw
- Scary Face
- Seed bomb
- Smokescreen
- String Shot
- Tail Whip
### Changes
- Reworked spawn presets and added structure tags for spawns
- Stat increase and decrease particles have been changed, with the addition of alpha boost particles.
- Dragonite's mailbag cosmetic can now also be obtained with a chest in the cosmetic slot
- Adjusted Croagunk's walkspeed
- Adjusted Aerodactyl's walkspeed
- Sharp Beak can now be crafted
- Metal Alloy can now be crafted
- Ability Capsules can now be brewed
- Using an Ability Capsule will now return an Empty Bottle
- The following items can now be obtained by brushing the following Pokémon:
- Pink Wool - Wigglytuff
- White Wool - Swablu, Altaria
- Shed Shell - Ekans, Arbok, Dratini, Dragonair, Dunsparce, Treecko, Grovyle, Sceptile, Seviper, Scraggy, Scrafty, Helioptile, Heliolisk, Salandit, Salazzle, Sobble, Drizzile, Inteleon, Silicoblra, Sandaconda, Toxel, Toxtricity, Orthworm, Dudunsparce
- Phantom Membrane - Dreepy, Drakloak, Dragapult
- The following items can now be obtained by evolving the following Pokémon into any of their evolutions:
- Turtle Scute - Squirtle, Wartortle, Turtwig, Grotle, Tirtouga, Chewtle
- Shed Shell - Metapod, Kakuna, Ekans, Paras, Venonat, Krabby, Scyther, Kabuto, Dratini, Dragonair, Ledyba, Spinarak, Yanma, Pineco, Dunsparce, GLigar, Pupitar, Treecko, Grovyle, Silcoon, Cascoon, Surskit, Nincada, Trapinch, Vibrava, Corphish, Anorith, Bagon, Shelgon, Kricketot, Burmy, Combee, Skorupi, Swadloon, Whirlipede, Dwebble, Scraggy, Karrablast, Joltik, Larvesta, Spewpa, Clauncher, Helioptile, Charjabug, Crabrawler, Cutiefly, Dewpider, Salandit, Wimpod, Poipole, Sobble, Drizzile, Dottler, Silicobra, Toxel, Sizzlipede, Snom, Tarountula, Nymble, Rellor
- Saccharine Leaves now drop Tart, Sweet, or Syrupy Apples, depending on their honey coating stage, instead of vanilla Apples
- Fixed the height of the Tumblestone Bud items when held in hand, placed on head, viewed in a GUI, and placed in an Item Frame
- Refactored all advancements, removing several deemed unnecessary. Due to this refactor, all previously obtained advancements will be reset besides the root (for picking a starter Pokémon). We are sorry for the inconvience, but it makes the tab cleaner and simplifies future advancements.
- Restructured Botany Pots integration folder structure
- Decrease the default pastured mon wander radius to 32.
- Factor in the weight of Pokémon when calculating pushing forces between hitboxes.
- Remade the Starter Selection Screen with new assets
- Refactored berry trees to be less impactful on TPS (more performant)
- Changed the bait effects of the following items: (Vanilla) Sweet & Glow Berries, Golden & Enchanted Golden Apples (Cobblemon) Custap, Eggant, Hopo & Micle Berries, Sweet Hearts
- Wild Pokémon now reliably have at least one offensive move if they can have offensive moves at that level.
- Data Monitor can accept Disks (TMs, Music Disks, Upgrade, Dubious Disk, etc.) to change what's being displayed on the screen
- Rider positioning is now client-driven (Remove poseOffsets and offset per seat/pose) in favour of sending real-time passenger position.
- Cleaned up seat data. Removed all now unused fields from seats in all species files. Also made locator seat names explicit in the species files (however, this doesn't break the legacy format)
- Marks can now provide aspects.
- Added new shiny and Alpha chance configs
- Apricorns now make a new sound when harvested
- Lowered the volume of harvesting berries
- Added alternate versions of the basic PC Wallpapers based on the original design of Cobblemon's PC, before public release
- Improved compatibility with various mods
- Farmer's Delight's cutting board can now be used to strip Apricorn and Saccharine's logs, or salvage their planks from various blocks. It can also separate their boats from chests
- Farmer's Delight's cutting board can be used to get two white dye from Pep-Up Flowers and two magenta dye from Medicinal Leeks
- Farmer's Delight's cutting board can separate Tumblestone Blocks back into Tumblestones
- Farmer's Delight's cutting board can salvage Type Gems from their Gem Blocks
- Farmer's Delight's Onions, Tomatoes, and Cabbages can be used to cook Open-Faced Sandwiches. Nature's Spirit's Olives also can be used
- Farmer's Delight's cutting board can be used on Hearty Grains to obtain rice and straw, similarly to Farmer's Delight's Rice Panicles
- Farmer's Delight's Apple Pies and Apple Ciders can now be made using Applin Apples, alongside the Vanilla counterpart
- Amendments allows for cooking Leek and Potato Stew in a Cauldron
- Any mods that utilise Moonlight's Soft Fluid system can store Leek and Potato Stew, Medicinal Brew, Antidote, Awakening, Burn Heal, Ice Heal, Paralyze Heal, Full Heal, Potion, Super Potion, Hyper Potion, Max Potion, Full Restore, Ability Capsules, Moomoo Milk, and Berry Juice in liquid form (such as Suplementaries' Jar or Goblet, or Cauldrons while using Amendments)
- Supplementaries' Hourglass can now use Bright Powder, Quick Powder, Metal Powder, Heal Powder, Silver Powder, and Remedy
- Berry Trees are now compatible with the Planter properly
- Saccharine Way Signs (added by Supplementaries automagically) now use a premade texture to prevent odd visual issues
- Supplementaries' Jar can now store various Cobblemon cookies, candies, and similar items
- Some of Supplementaries' items have been added to the `#cobblemon:held/container_held_items` item tag, including sacks, safes, and lunch baskets
- Experience Nuggets can be used in place of Sculk for making the EXP Candies if using Create
- Rage Candy Bar has a Create Mixing recipe
- All Cobblemon ores can now be crushed in Create's Crushing Wheels
- All Gem and Tumblestone Clusters can be crushed for automatic, low return, gems
- Hearty Grains can be milled like Farmer's Delight Rice Panicles if both Farmer's Delight and Create are installed
- Big Roots and Revival Herbs can be milled to their respective powders
- Pep-Up Flowers, Medicinal Leeks, and all 70 berries can be milled in Create's Millstone for additional dye drops
- Auspicious Armour and Upgrades can be Haunted using Create's crafting systems
- Applin Apples can be used to make Honeyed Apples in Create
- Golden Apples can now be crafted using the Applin Apples
- Prismarine Crystals and Amethyst Shards can now be used to craft Pokedexes
- Updated Pokédex Screens item tag to support tagged variants of Redstone and Glowstone Dust
- Sounds for interacting with various Cobblemon blocks now play from the centre of the block, rather than the corner
- The `/pc` command no longer plays PC activation sounds
### Fixes
- Fixed blank Cooking Pot recipe book entries and missing tabs on Fabric.
- Fixed Minior's "Shields Down" ability not working as intended
- Fixed Blitzle's base scale being `1` rather than the intended `0.8`
- Fixed Magnezone's emissive layer for the shiny having the wrong name, thus displaying wrongly
- Fixed Litleo not blinking
- Fixed Omanyte and Omastar incorrectly using water animations on land.
- Fixed Pokédex layout for genderless variations
- Fixed odd stacking behaviour with the poke_bait item.
- Fixed a compatibility issue with World Game Rules which caused some log spam in specific cases.
- Fixed improper validation of Pokémon when performing a trade.
- Fixed an issue where particle like beam can disappear when you don't see the beginning of the beam, or you look away
- Fixed Bedrock particle materials (alpha/opaque/blend/add) sharing one render batch, which caused incorrect culling on mixed batches; each material now batches and draws with its own blend and cull state, and the alpha material renders as a true cutout
- Fixed an occasional crash on retrieving revived fossils
- Fixed brushing not working on Neoforge
- Fixed braking not working for submarines (whoops).
- Fixed strafing not having the same friction as forward movement (funny whoops).
- Fixed submarine's surface-water animations not playing when supposed to.
- Fixed submarine camera freaking out when looking up or down on the surface.
- Fixed pastured mons despawning and running away.
- Fixed Apricorn trees crashing when other mods bonemeal it
- Fixed the issue that caused Hover Behaviour rides without a ground style to be rendered useless upon touching the ground on a server.
- Removed the "inAir" penalty for block destruction speed when on a hover mount.
- Fixed horizontal and vertical collisions with hover mounts so they no longer accumulate or retain momentum
- Fixed NeoForge brewing stand unexpectedly crashing users when shift clicking items within its menu
- Fixed NPCs switching Pokémon very often during battles
- Fixed Campfire pot causing crashes under certain circumstances
- Fixed Type Gems not being consumed upon use
- Fixed Ancient Pokéball's to their respective modifiers
- Fixed a Server warning when recalling Pokémon
- Fixed sliding particles incorrectly handling block collision
- Fixed Full Heal not curing status conditions in battle
- Fixed a rare issue with battles locking up when you are forced to switch, with the switch menu appearing instantly.
- Fixed the combined effects of Pledge moves having broken Showdown message handling and localisation
- Fixed rare Pokémon being far too likely to spawn multiple times in a single spawn cycle. The dev told me that the exact bug is too complicated to explain.
- Resolved the niche issue where Structures would spawn in an incorrect location, creating very funky world gen.
- Fixed Galarian Ponyta missing its cry.
- Fixed various log warnings caused by redundant files and sound events.
- Traded Pokémon will now retain the original friendship level when traded back to its OT
- Clusters now display correctly on maps
- Fixed removing mulch from berry trees with a shovel not working.
- Fixed collecting honey from Saccharine Leaf blocks deleting Honey Bottles when the inventory was full
- Fixed Stripped Saccharine Wood recipe being missing
- Fixed CancelableSoundInstance causing lag by attempting to create sounds every tick when out of range of hearing
- Fixed Stat Reducing Aprijuice not being possible to apply on a max out Pokémon, and the possibility of going into negatives
- Fixed Exploit where you could take out the bait or drop your Pokérod with bait to avoid consuming bait, now the bobber will just disappear if you do so
- Fixed Apricorn and Saccharine boats incorrectly using chest boat models
- Fixed wild and trainer battle AI refusing to select moves with the `adjacentAlly` target type, even when it's their only option
- Fixed gilded chests being more accessible than intended
- Fixed fished up Pokémon not being added as "seen" to the Pokedex
- Fixed surprised Gimmighoul encounters not being added as "seen" to the Pokedex
- Fixed a crash involving berry trees that have been world edited.
- Fixed visual desync in multiplayer for players mounting Pokémon outside other players' loaded chunks
- Fixed Pokémon entities leveled by rare candy not getting stronger as Minecraft entities unless recalled.
- Fixed a freeze during game load that occurred when multiple mods tried to load Cobblemon API classes at the same time.
- Fixed random tick crashes from berry trees
- Lowered the volume of Blitzle and Zebstrika's cry
- Regional Foods will now prioritise being eaten instead of used for healing Pokémon, shift clicking will use it as healing
- Resolved Z-Fighting on Pokédex Models
- Restored Ninjask's ability to do barrel rolls
- Fixed Raichu (male) and Alolan Raichu (both genders) not displaying the correct Partner Cap when Pewter Crunchies are in the cosmetic slot.
- Fixed a crash caused by adding enchanted items to the Resurrection Machine
- Fixed a crash that could occur when starting a PvN battle with only fainted Pokémon.
- Fixed the Remember Riding Camera config not being respected when dismounting and resolved the incompatibility with the Auto Third Person mod
- Fixed Full Heal block not dropping Full Heals
- Fixed status cure items not returning an empty bottle when used
- Fixed Panpour consuming its held item when evolving with a Water Stone
- Fixed Smoked Tail Curry returning a Wooden Bowl when consumed despite it not being included in the recipe
- Fixed `/giveallpokemon` not respecting the min and max parameters
- Fixed the choose starter toast not disappearing when capturing a Pokémon instead of opening the starter selection screen
- Fixed an issue where certain Sinister Tea seasonings were only applying as instant effects and would not apply over time
- Long config option labels now scroll instead of overflowing
### Developer
- Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters
- Added `FishingSpawnerFactory` which allows influence registration on fishing encounters
- Added `BattleFleeAttemptEvent` fired from `PokemonBattle.checkFleeAttempt`, allowing developers to intercept and control flee attempts
- Tweaked the spawning selector interface to take a bucket function rather than a bucket directly.
- Added a new `cobblemon:height_range` processor type to allow developers to restrict structure-spawning ranges.
- Separated all the functions of MoLangFunctions.kt into their own files.
- Spawn buckets and their base weights are now provided by the spawner, with different base weights for world, fishing, snack, and habitat spawn buckets.
- Removed "SpawnBucket" class in favour of just storing maps of bucket names to weights.
- Fixed nicknames not being applied to Pokémon spawned from `spawn_pool_world` files.
- Added support for `profilePokedexScale`, `profilePokedexTranslation`, `profileSummaryScale`, and `profileSummaryTranslation` in poser files for finer control over profile displays.
- Fixed riding camera offsets defined on Pokémon forms not being applied
### Molang & Datapacks
- Added `q.target_fainted` function to move action effects that checks if any opponents fainted from the move.
- Added `moveset_builders` datapack folder for different styles of movesets.
- Added `party_pools` datapack folder for defining groups of Pokémon for complex NPC party definitions.
- Added `party_compositions` datapack folder for arranging parties on NPCs that rely on party pools.
- Added `composed_pool` NPC party provider type which combines a party pool with a party composition to create more complex NPC parties.
- Added basic chatter NPC Behaviour
- Added `q.item.is_enchanted()`
- Added `q.item.has_enchantment(minecraft:sharpness, 3)`
- Added `has_chosen_starter` & `get_starter_uuid` as available Molang functions
- Added `has_tm_move_unlocked`, `unlock_tm_move`, & `lock_tm_move` as available Molang functions for Players
- Added `marks`, `has_mark`, & `remove_marks` as available Molang functions for Pokémon
- Added Data Component support for species drops, eg `"components": {"minecraft:custom_model_data": 123}`
- Added the new `sortOrder` field to mark entries, (defaults to 0 if not specified) which controls the order in which marks are rendered on the HUD, with higher numbers being rendered on top of lower numbers
- Added optional `order` field to starter categories. It will allow explicitly sorting categories in the starter selection screen
- SpeciesAdditions append vs override now matches wiki documentation
- Added `weighted_choice` as a new feature type. It requires setting weights for each choice and allows developers to simplify the process of assigning random aspect choice based on marble chance
- Added `q.world.spawn_loot_table_items(loot_table_id, x, y, z)` which spawns the items from the loot table at the position. (Use type: "minecraft:chest" for the loot table.)
- Added `q.pokemon.entity` to access the entity of Pokémon.
- Flamethrower and Seismic Toss had their action effect JSON cleaned up
- Ride sounds can now be set as exclusive to passengers
- Ride sounds no longer play when submerged unless specified with a new setting
- NPC Trainers can now call a `command` animation when issuing a command in battle.
- Added a q.failed function to move action effects.
- Herd spawned Pokémon have their levels controlled differently. The `levelRange` property now acts as a clamp instead of a filter, with a new `herdLevelRange` property to do the filter behaviour.
- `q.sound` now accepts three parameters (sound event, volume and pitch) instead of just the sound event and pitch.
### Particles
#### New Move Animations
- Blizzard
- Disarming Voice
- Draco Meteor
- Dragon Breath
- Dragon Rage
- Drain Punch
- Fairy Wind
- Fire Fang
- Gust
- Horn Drill
- Hydro Pump
- Ice Fang
- Leafage
- Leaf Storm
- Light Screen
- Night Shade
- Play Rough
- Pyro Ball
- Reflect
- Rock Slide
- Sonic Boom
- Sucker Punch
- Supersonic
- Thunder Fang
#### Tweaks and Fixes (Moves)
- Confusion
- Dragon Claw
- Eruption
- Fire Punch
- Flame Burst
- Flame Charge
- Flamethrower
- Frost Breath
- Hypnosis
- Ice Punch
- Icy Wind
- Mud Sport
- Poison Fang
- Protect
- Psychic
- Quick Attack
- Rage Powder
- Razor Leaf
- Sand Attack
- Scratch
- Shadow Claw
- Sing
- Spore
- String Shot
- Tackle
- Tail Whip
- Thunder
- Thunder Punch
- Thunder Wave
- Water Gun
- Water Pulse
- Water Sport
#### Miscellaneous Particles
+ Added Wild Might particles for Alpha Pokémon
+ Updated stat increase and decrease particles
### Localization
- Updated translations for:
- Updated translations for:
- Japanese
- German
- Spanish
- Ecuadorian Spanish
- Portuguese
- Brazilian Portuguese
- Simplified Chinese
- Traditional Chinese
- Korean
+9
View File
@@ -0,0 +1,9 @@
- Some kind of particle effect for them? they're meant to have glowing eyes, so maybe some red particles on the eye locator
--- global posers? stacked posers?
--- red texture seems to be what some people wanted.
/- shoulder mounts part of it
- omni boost
- roar when player comes within reach, adjust levels
- Riding settings to control what the min scale is for a pokemon to be ridable.
+2 -5
View File
@@ -1,6 +1,3 @@
import utilities.gitBranch
import utilities.gitShortCommit
plugins {
id("cobblemon.root-conventions")
}
@@ -9,6 +6,6 @@ version = "${project.property("mod_version")}+${project.property("mc_version")}"
val isSnapshot = project.property("snapshot")?.equals("true") == true
if (isSnapshot) {
val fixedBranchName = gitBranch().substringAfter("/")
version = "$version-${fixedBranchName}-${gitShortCommit()}"
val fixedBranchName = versioning.info.branch.substringAfter("/")
version = "$version-${fixedBranchName}-${versioning.info.build}"
}
-532
View File
@@ -1,532 +0,0 @@
# Changelog
## [1.8.0 - Make Your Move Update (September 6th, 2026)](#1-8-0)
#### I think the red eyes mean they're friendly!
### Additions
- Added Alpha Pokémon
- Alpha Pokémon are bigger, stronger, and more aggressive than regular Pokémon.
- They are also scarier.
- They always know a TM move and will adapt to be stronger than your party.
- Alphas will always lead herds and spawn in herds.
- Added alpha Pokémon attraction bait effect to Hopo Berries.
- Added intrinsic scaling to all Pokémon (anywhere from 5% smaller to 5% bigger than normal). Some cases, such as shoulder mounting, will not scale so that the shoulder animations don't look silly.
- Added scaling to "baby" Pokémon (10% smaller at level 1 → normally sized at level 10).
- Added TMs.
- Added TM Machine. TMs are made from elemental type gems, and their recipes are unlocked by having Pokémon that know the move.
- Added config options for making TMs infinite-use and instant unlocking of all TM recipes.
- Added Type Gem blocks as well as crystal clusters and deepslate core blocks that are used for growing elemental type gems.
- Added a new Starter Selection menu that looks way better.
- Added a "Move Dex" to the Pokédex to help identify what moves a Pokémon can learn and what Pokémon can get a move.
- Added the Habitat Block, a spawner block for servers and adventure maps that controls spawning in an area.
- Added 49 new habitat structures. In these habitats you will find rarer Pokémon and sometimes different Pokémon will appear on different days.
- Added a toast to notify players of how they can scroll through their party. Did you know you can hold R and scroll instead of using arrow keys? It's true!!!
- Added a way to obtain Porygon by inserting an Upgrade or Dubious Disc into a data monitor.
- Added Disk Rack for storing disks and being an audio sequencer for Noteblocks. What does that have to do with Pokémon? How about you mind your own business!
- Added missing crops to Botany Pots integration.
- Added conditional seats for riding. Seats can be gated by Molang such as "is_alpha".
- Added 10 new ruin structures.
- Added a new shipwreck cove structure called the Magma Shipwreck Cove.
- Added 7 new advancements.
- Pokémon cries now play when clicking Pokémon in the summary screen.
- Held item visibility can now be controlled by the `held_item_visible` command argument.
- Added config option to disable Pokémon spawning on a per-world basis.
- Added config options to award experience to fainted Pokémon for opponents they helped defeat (`awardExperienceToFaintedPokemon`) and to still award experience for defeated enemies even after battle loss/forfeit (`awardExperienceOnBattleLoss`).
- Added `/givetm` command.
- Added advancement triggers for learning TMs: `cobblemon:has_learn_specific_tm` (per-TM), `cobblemon:has_learn_all_tm` (all TMs learned), and `cobblemon:tms_learned` (count-based), backed by a new `totalTMLearnedCount` on player advancement data.
- Added `cobblemon:catch_alpha_pokemon` advancement trigger and `totalAlphaCaptureCount` stat for captured Alpha Pokémon.
### New rideable Pokémon
- Pidgeot
- Crobat
- Skarmory
- Milotic
- Archeops
- Goodra
- Goodra (Hisuian Form)
- Drampa
- Duraludon
- Archaludon
### Pokémon Added
#### Generation 3
- Wurmple
- Silcoon
- Beautifly
- Cascoon
- Dustox
- Skitty
- Delcatty
- Seviper
- Zangoose
#### Generation 4
- Cherubi
- Cherrim
- Stunky
- Skuntank
#### Generation 5
- Audino
- Pawniard
- Bisharp
#### Generation 6
- Pancham (+ Cosmetic with Black Glasses)
- Pangoro (+ Cosmetic with Black Glasses)
#### Generation 7
- Rockruff
- Lycanroc
#### Generation 8
- Rolycoly
- Carkol
- Coalossal
- Runerigus
- Snom
- Frosmoth
- Indeedee
- Duraludon
- Applin
- Flapple
- Appletun
#### Generation 9
- Nymble
- Lokix
- Kingambit
- Dipplin
- Archaludon
- Hydrapple
#### Regional Forms
- Sandshrew (Alolan Form)
- Sandslash (Alolan Form)
- Yamask (Galarian Form)
- Avalugg (Hisuian Form)
### Model updates for the following Pokémon
- Pidgey
- Pidgeotto
- Pidgeot
- Sandshrew
- Sandslash
- Nidoking
- Zubat
- Golbat
- Crobat
- Ditto
- Porygon
- Porygon2
- Porygon-Z
- Skarmory
- Smoochum
- Jynx
- Popplio
- Brionne
- Primarina
- Fletchling
- Fletchinder
- Talonflame
- Sableye
- Mawile
- Goomy
- Sliggoo
- Goodra
- Nacli
- Naclstack
- Garganacl
- Bidoof
- Cranidos
- Chatot
- Yamask
- Cofagrigus
- Tyranitar
- Turtonator
- Golett
- Corsola
- Arbok (New Patterns: Angry, Cartoon, Clown, Comedic, Crow, Grin, Ink, Modern, Rain, Scribble, Silence, Sloth, Suspicious, Will)
- ... and Substitute
#### Model changes to the regional bias resource pack:
- Added Mime Jr. (Galar Bias)
- Added Bergmite (Hisui Bias)
- Changed models for every existing regional bias
### Animation updates for the following Pokémon
- Kakuna
- Grimer
- Nidoran♀ (+ New shoulder mount)
- Nidorina
- Nidoran♂ (+ New shoulder mount)
- Sandshrew
- Diglett
- Dugtrio
- Graveler
- Happiny
- Chansey
- Blissey
- Poliwhirl
- Poliwrath
- Zubat
- Vulpix
- Drowzee
- Hypno
- Noctowl
- Skarmory
- Skitty
- Flygon
- Shellos
- Riolu
- Lucario
- Purrloin
- Liepard
- Joltik
- Galvantula
- Nidoking
- Fletchling
- Fletchinder
- Talonflame
- Archeops
- Drampa
- Sewaddle
- Swadloon
- Leavanny
- Goomy
- Sliggoo
- Goodra
- Shedinja
- Skwovet
- Greedent
- Bidoof
- Cranidos
- Chatot
- Yamask
- Cofagrigus
- Luvdisc
- Staryu
- Starmie
- Togetic
- Rellor
- Rabsca
- Yungoos
- Gumshoos
- Cottonee
- Whimsicott
- Corsola
#### Added sounds to the following moves
- Absorb
- Aurora Beam
- Double Team
- Draco Meteor
- Fire Punch
- Fire Spin
- Flame Charge
- Flame Wheel
- Giga Drain
- Ice Punch
- Leech Life
- Magical Leaf
- Mega Drain
- Minimize
- Poison Gas
- Rock Throw
- Scary Face
- Seed bomb
- Smokescreen
- String Shot
- Tail Whip
### Changes
- Removed the penalty for block destruction speed when on a hover mount, making them better for digging.
- Golden Apples can now be crafted using the Applin Apples.
- Prismarine Crystals and Amethyst Shards can now be used to craft Pokedexes.
- Updated Pokédex Screens item tag to support tagged variants of Redstone and Glowstone Dust.
- Sharp Beak can now be crafted using bones and Flying Gems.
- Metal Alloy can now be crafted using iron and Thunder Stones.
- Ability Capsules can now be brewed from Medicinal Brew and Dragon Breath.
- Using an Ability Capsule will now return an Empty Bottle.
- Decreased the default pastured wander radius to 32 blocks.
- Added alternate versions of the basic PC Wallpapers based on the original design of Cobblemon's PC, before public release.
- Rooted Arch Ruins, Crumbling Arch Ruins, and all fossil structures have been improved so that they generate more naturally in terrain.
- Gimmighoul towers now generate at ground-level rather than sinking one block into the ground.
- All ruin structures that did not generate with a gimmighoul/gilded chest before will now generate with one.
- All ruin structures gilded chest loot tables have been updated to include a guaranteed TM not learned naturally by any Pokémon through their level-up moveset. Each ruin gets its own specific TM, so you can now go hunting ruins to complete your move dex!
- The Prehistoric Underwater Fissure and Prehistoric Submerged Impact fossil structures now use the biome tag `#cobblemon:is_temperate_ocean`.
- The lush shipwreck cove's phantom mob spawner has been replaced with breezes.
- Shipwreck coves now generate slightly more frequently.
- Added equipment loot tables for shipwreck cove enemies.
- Maps from abandoned fishing boats leading to shipwreck coves now will skip explored chunks, allowing the player to find more shipwreck coves more easily.
- All shipwreck coves' spawners have been adjusted to give compatible mobs enchanted iron or diamond armour and weapons while in its ominous state. The armour given to the mobs will also use the automaton trim. We upped the difficulty because it was too easy now that your Pokémon can fight for you.
- One TM of the elemental hyper beams has been added as guaranteed loot to the main treasure chest in every shipwreck cove. (Magma Cove gets Blast Burn, Lush Cove gets Frenzy Plant, and Submerged Cove gets Hydro Cannon.)
- The following items can now be obtained by brushing the following Pokémon:
- Pink Wool - Wigglytuff
- White Wool - Swablu, Altaria
- Shed Shell - Ekans, Arbok, Dratini, Dragonair, Dunsparce, Treecko, Grovyle, Sceptile, Seviper, Scraggy, Scrafty, Helioptile, Heliolisk, Salandit, Salazzle, Sobble, Drizzile, Inteleon, Silicoblra, Sandaconda, Toxel, Toxtricity, Orthworm, Dudunsparce
- Phantom Membrane - Dreepy, Drakloak, Dragapult
- The following items can now be obtained by evolving the following Pokémon into any of their evolutions:
- Turtle Scute - Squirtle, Wartortle, Turtwig, Grotle, Tirtouga, Chewtle
- Shed Shell - Metapod, Kakuna, Ekans, Paras, Venonat, Krabby, Scyther, Kabuto, Dratini, Dragonair, Ledyba, Spinarak, Yanma, Pineco, Dunsparce, GLigar, Pupitar, Treecko, Grovyle, Silcoon, Cascoon, Surskit, Nincada, Trapinch, Vibrava, Corphish, Anorith, Bagon, Shelgon, Kricketot, Burmy, Combee, Skorupi, Swadloon, Whirlipede, Dwebble, Scraggy, Karrablast, Joltik, Larvesta, Spewpa, Clauncher, Helioptile, Charjabug, Crabrawler, Cutiefly, Dewpider, Salandit, Wimpod, Poipole, Sobble, Drizzile, Dottler, Silicobra, Toxel, Sizzlipede, Snom, Tarountula, Nymble, Rellor
- Saccharine Leaves now drop Tart, Sweet, or Syrupy Apples, depending on their honey coating stage, instead of vanilla Apples.
- Fixed the height of the Tumblestone Bud items when held in hand, placed on head, viewed in a GUI, and placed in an Item Frame.
- Refactored all advancements, removing several deemed unnecessary. Due to this cleanup, all previously obtained advancements will be reset besides the root (for picking a starter Pokémon). This sucks, and we're sorry. We had to do this eventually so better now than later.
- Forces between colliding hitboxes now factors in the weight of Pokémon.
- Improved berry tree performance on singleplayer and servers.
- Updated stat increase and decrease particle effects with the addition of alpha boost particles.
- Improved the performance of pasture blocks on singleplayer and servers.
- Changed the bait effects of the following items: (Vanilla) Sweet & Glow Berries, Golden & Enchanted Golden Apples (Cobblemon) Custap, Eggant, Hopo & Micle Berries, Sweet Hearts.
- Wild Pokémon now reliably have at least one offensive move if they can have offensive moves at that level. No more free lunches with Pokémon that can't even hurt you.
- Data Monitor can accept Disks (TMs, Music Disks, Upgrade, Dubious Disk, etc.) to change what's being displayed on the screen.
- The party overlay's experience-gained animation now displays the amount of EXP the Pokémon gained.
- Pressing the inventory key now closes the Pokémon interact wheel GUI.
- Updated the sound for harvesting Apricorns.
- Lowered the volume of harvesting berries.
- Improved compatibility with various mods
- Farmer's Delight's cutting board can now be used to strip Apricorn and Saccharine's logs, or salvage their planks from various blocks. It can also separate their boats from chests.
- Farmer's Delight's cutting board can be used to get two white dye from Pep-Up Flowers and two magenta dye from Medicinal Leeks.
- Farmer's Delight's cutting board can separate Tumblestone Blocks back into Tumblestones.
- Farmer's Delight's cutting board can salvage Type Gems from their Gem Blocks.
- Farmer's Delight's Onions, Tomatoes, and Cabbages can be used to cook Open-Faced Sandwiches. Nature's Spirit's Olives also can be used.
- Farmer's Delight's cutting board can be used on Hearty Grains to obtain rice and straw, similarly to Farmer's Delight's Rice Panicles.
- Farmer's Delight's Apple Pies and Apple Ciders can now be made using Applin Apples, alongside the Vanilla counterpart.
- Amendments allows for cooking Leek and Potato Stew in a Cauldron.
- Any mods that utilise Moonlight's Soft Fluid system can store Leek and Potato Stew, Medicinal Brew, Antidote, Awakening, Burn Heal, Ice Heal, Paralyze Heal, Full Heal, Potion, Super Potion, Hyper Potion, Max Potion, Full Restore, Ability Capsules, Moomoo Milk, and Berry Juice in liquid form (such as Suplementaries' Jar or Goblet, or Cauldrons while using Amendments).
- Supplementaries' Hourglass can now use Bright Powder, Quick Powder, Metal Powder, Heal Powder, Silver Powder, and Remedy.
- Berry Trees are now compatible with the Planter properly
- Saccharine Way Signs (added by Supplementaries automagically) now use a premade texture to prevent odd visual issues.
- Supplementaries' Jar can now store various Cobblemon cookies, candies, and similar items.
- Some of Supplementaries' items have been added to the `#cobblemon:held/container_held_items` item tag, including sacks, safes, and lunch baskets.
- Experience Nuggets can be used in place of Sculk for making the EXP Candies if using Create.
- Rage Candy Bar has a Create Mixing recipe.
- All Cobblemon ores can now be crushed in Create's Crushing Wheels. All Gem and Tumblestone Clusters can be crushed for automatic, low return, gems.
- Hearty Grains can be milled like Farmer's Delight Rice Panicles if both Farmer's Delight and Create are installed.
- Big Roots and Revival Herbs can be milled to their respective powders.
- Pep-Up Flowers, Medicinal Leeks, and all 70 berries can be milled in Create's Millstone for additional dye drops.
- Auspicious Armour and Upgrades can be Haunted using Create's crafting systems.
- Applin Apples can be used to make Honeyed Apples in Create.
- Sounds for interacting with various Cobblemon blocks now play from the centre of the block, rather than the corner. You probably didn't notice.
- Dragonite's mailbag cosmetic can now also be obtained with a chest in the cosmetic slot.
- Adjusted the walk speed for Croagunk and Aerodactyl.
- The `/pc` command no longer plays PC activation sounds
- Pressing the Summary key while either the starter selection or summary screen are open now closes those screens.
- Ice type Pokémon are now immune to Packed Snow.
- Added cobweb block slowness immunity for spider Pokémon
### Fixes
- Fixed rare Pokémon being far too likely to spawn multiple times in a single spawn cycle. The dev told me that the exact bug is too complicated to explain.
- Fixed Omanyte and Omastar incorrectly using water animations on land.
- Fixed Pokédex layout for genderless variations.
- Fixed odd stacking behaviour with Poké Baits.
- Fixed a compatibility issue with World Game Rules which caused some log spam in specific cases.
- Fixed improper validation of Pokémon when performing a trade.
- Fixed an issue where particles like beams can disappear when you don't see the beginning of the beam, or you look away.
- Fixed Bedrock particle materials sometimes being incorrectly invisible from some sides.
- Fixed an occasional crash on retrieving revived fossils.
- Fixed brushing not working on Neoforge. Again.
- Fixed braking not working for submarine-style mounts (whoops).
- Fixed strafing not having the same friction as forward movement (funny whoops).
- Fixed submarine-style mounts' surface-water animations not playing when supposed to.
- Fixed submarine-style mounts' camera freaking out when looking up or down on the surface.
- Fixed pastured Pokémon despawning and running away.
- Fixed Apricorn trees crashing when other mods bonemeal them.
- Fixed the issue that caused hover-style mounts without a ground style to be rendered useless upon touching the ground on a server.
- Fixed horizontal and vertical collisions with hover-style mounts so they no longer accumulate or retain momentum.
- Fixed NeoForge brewing stand unexpectedly crashing users when shift clicking items within its menu. Well, I suppose all crashes are unexpected. You know what I mean.
- Fixed NPCs switching Pokémon very often during battles.
- Fixed Campfire Pot causing crashes under certain circumstances.
- Fixed Type Gems not being consumed upon use in battles.
- Fixed several Ancient Pokéball's not using the correct capture multiplier.
- Fixed a server warning that could occur when recalling Pokémon.
- Fixed sliding particles incorrectly handling block collision.
- Fixed Full Heal not curing status conditions in battle. Literally it's one job.
- Fixed a rare issue with battles locking up when you are forced to switch, with the switch menu appearing instantly.
- Fixed the combined effects of Pledge moves having broken Showdown message handling and localisation.
- Fixed a very niche issue where structures would spawn in an incorrect location, creating very funky world gen.
- Fixed Galarian Ponyta missing its cry.
- Fixed various log warnings caused by redundant files and sound events.
- Traded Pokémon will now retain the original friendship level when traded back to its OT.
- Tumblestone Clusters now display correctly on maps.
- Fixed removing mulch from berry trees with a shovel not working. That was meant to be a thing.
- Fixed collecting honey from Saccharine Leaf blocks deleting Honey Bottles when the inventory was full.
- Fixed Stripped Saccharine Wood recipe being... not there.
- Fixed some performance issues with a kind of sound event causing lag by attempting to create sounds every tick when out of range of hearing.
- Fixed stat-reducing Aprijuice not being possible to apply on a maxed out Pokémon. Also fixed the possibility of going into negatives.
- Fixed exploit where you could take out the bait or drop your Pokérod with bait to avoid consuming bait, now the bobber will just disappear if you do so.
- Fixed Apricorn and Saccharine boats incorrectly using chest boat models.
- Fixed wild and trainer battle AI refusing to select moves with the `adjacentAlly` target type, even when it's their only option.
- Fixed gilded chests being more accessible than intended.
- Fixed fished up Pokémon not being added as "seen" to the Pokédex.
- Fixed surprised Gimmighoul encounters not being added as "seen" to the Pokédex.
- Fixed a crash involving berry trees that have been world edited.
- Fixed visual desync in multiplayer for players mounting Pokémon outside other players' loaded chunks.
- Fixed Pokémon entities leveled by rare candy not getting stronger as Minecraft entities unless recalled.
- Fixed a freeze during game load that occurred when multiple mods tried to load Cobblemon API classes at the same time.
- Lowered the volume of Blitzle and Zebstrika's cry (shut up!).
- Regional Foods will now prioritise being eaten instead of used for healing Pokémon - shift clicking will use it as healing.
- Resolved z-fighting (texture flickering) on Pokédex Models.
- Restored Ninjask's ability to do barrel rolls.
- Fixed Raichu (male) and Alolan Raichu (both genders) not displaying the correct Partner Cap when Pewter Crunchies are in the cosmetic slot.
- Fixed a crash caused by adding enchanted items to the Resurrection Machine.
- Fixed a crash that could occur when starting a PvN battle with only fainted Pokémon.
- Fixed the Remember Riding Camera config not being respected when dismounting and resolved the incompatibility with the Auto Third Person mod.
- Fixed Full Heal block not dropping Full Heals. Lmao.
- Fixed status cure items not returning an empty bottle when used.
- Fixed Panpour consuming its held item when evolving with a Water Stone.
- Fixed Smoked Tail Curry returning a Wooden Bowl when consumed despite it not being included in the recipe. Free bowls!
- Fixed `/giveallpokemon` not respecting the min and max arguments.
- Fixed the choose starter toast not disappearing when capturing a Pokémon instead of opening the starter selection screen.
- Fixed an issue where certain Sinister Tea seasonings were only applying as instant effects and would not apply over time.
- Long config option labels now scroll instead of overflowing in the configuration menu.
- Fixed some z-fighting and clipping on Dewott and Hisui-Bias Dewott.
- Eggant Berries can now be composted properly on NeoForge.
- Composting chances are now the same on NeoForge as on Fabric.
- Fixed problem where furnaces which had smelted Revival Herbs into Heal Powder in older versions would glitch out on trying to get the experience out of the block.
- Fixed the Battle AI not recognizing Surging Strikes as a multistrike move.
- Fixed Tentacool not using its swim animation when on water surface.
- Fixed Eevee using its left shoulder pose when in battle.
- Fixed Magikarp and Gyarados Jump Pattern resourcepacks replacing shiny textures of addons targeting Magikarp or Gyarados.
- Fixed Combee's pollinated texture overwriting the shiny and female textures.
- Fixed a colour mistake with Dracovish's shiny.
- Fixed Togedemaru's back spikes z-fighting.
- Fixed Sliggoo (+ Hisuian) evolving in the Nether and End where it can't actually rain.
- Corrected the scales, hitbox sizes, and eye heights for Ariados, Cetoddle, Durant, Galvantula, Klink, Lanturn, Lumineon, Mantine, Mantyke, Mudbray, Numel, Staravia, and Torkoal.
- Fixed Torkoal's walk speed not matching its animation. He will no longer move faster than Bunnelby and Buneary. This tortoise would've won.
- Fixed Lanturn T-posing due to missing ground walk and sleep animations.
- Fixed Oshawott and Dewott using their sleep animation in unintended scenarios.
- Fixed Dudunsparce and Maushold always having the same form instead of having the 1 in 100 chance of being different.
- Fixed bird mounts rapidly switching between their flying and gliding animations when flying at low speed.
- Fixed some battles being startable with fainted Pokémon in the initial sendout.
- Fixed random gender selection for forms with strict gender divides (such as Meowstic and Oinkologne) never choosing female (that's sexist).
- Fixed the "Press R to battle" prompt not disappearing after you finish your first battle, at least until you relogged.
- Fixed the composter block being listed as "composer" in the cosmetic items species feature
- Fixed Castform's forms being tagged as introduced in generation 4 instead of 3.
- Fixed Blitzle's base scale being `1` rather than the intended `0.8`.
- Fixed Magnezone's emissive layer for the shiny having the wrong name, thus displaying wrongly.
- Fixed Litleo not blinking.
### Developer
- Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters.
- Added `FishingSpawnerFactory` which allows influence registration on fishing encounters.
- Added `BattleFleeAttemptEvent` fired from `PokemonBattle.checkFleeAttempt`, allowing developers to intercept and control flee attempts.
- Tweaked the spawning selector interface to take a bucket function rather than a bucket directly.
- Added a new `cobblemon:height_range` processor type to allow developers to restrict structure-spawning ranges.
- Separated all the functions of MoLangFunctions.kt into their own files.
- Spawn buckets and their base weights are now provided by the spawner, with different base weights for world, fishing, snack, and habitat spawn buckets.
- Removed "SpawnBucket" class in favour of just storing maps of bucket names to weights.
- Fixed nicknames not being applied to Pokémon spawned from `spawn_pool_world` files.
- Added support for `profilePokedexScale`, `profilePokedexTranslation`, `profileSummaryScale`, and `profileSummaryTranslation` in poser files for finer control over profile displays.
- Fixed riding camera offsets defined on Pokémon forms not being applied.
- Marks can now provide aspects.
### Molang & Datapacks
- Added `moveset_builders` datapack folder for different styles of movesets.
- Added `party_pools` datapack folder for defining groups of Pokémon for complex NPC party definitions.
- Added `party_compositions` datapack folder for arranging parties on NPCs that rely on party pools.
- Added `composed_pool` NPC party provider type which combines a party pool with a party composition to create more complex NPC parties.
- Added basic chatter NPC Behaviour.
- Added `q.item.is_enchanted()`.
- Added `q.item.has_enchantment(minecraft:sharpness, 3)`.
- Added `has_chosen_starter` and `get_starter_uuid` as available Molang functions.
- Added `has_tm_move_unlocked`, `unlock_tm_move`, and `lock_tm_move` as available Molang functions for Players.
- Added `marks`, `has_mark`, and `remove_marks` as available Molang functions for Pokémon.
- Added Data Component support for species drops, eg `"components": {"minecraft:custom_model_data": 123}`
- Added the new `sortOrder` field to mark entries, (defaults to 0 if not specified) which controls the order in which marks are rendered on the HUD, with higher numbers being rendered on top of lower numbers.
- Added optional `order` field to starter categories. It will allow explicitly sorting categories in the starter selection screen.
- SpeciesAdditions append vs override now matches the wiki documentation.
- Added `weighted_choice` as a new feature type. It requires setting weights for each choice and allows developers to simplify the process of assigning random aspect choice based on marble chance.
- Added `q.world.spawn_loot_table_items(loot_table_id, x, y, z)` which spawns the items from the loot table at the position. Use type: `minecraft:chest` for the loot table.
- Added `q.pokemon.entity` to access the entity of Pokémon.
- Flamethrower and Seismic Toss had their action effect JSON cleaned up.
- Ride sounds can now be set as exclusive to passengers.
- Ride sounds no longer play when submerged unless specified with a new setting.
- NPC Trainers can now call a `command` animation when issuing a command in battle.
- Added a q.failed function to move action effects.
- Herd spawned Pokémon have their levels controlled differently. The `levelRange` property now acts as a pre-offset clamp instead of a filter, with a new `herdLevelRange` property to do the filter behaviour.
- `q.sound` now accepts three parameters (sound event, volume, and pitch) instead of just the sound event and pitch.
- Added a new layer property that makes a layer scroll in a direction. "scrolling": {"speedU": 0.1, "speedV": 0.1}
- Cleaned up seat data. Removed all now unused fields from seats in all species files. Also made locator seat names explicit in the species files (however, this doesn't break the legacy format)
### Particles
#### New Move Animations
- Blizzard
- Disarming Voice
- Draco Meteor
- Dragon Breath
- Dragon Rage
- Drain Punch
- Fairy Wind
- Fire Fang
- Gust
- Horn Drill
- Hydro Pump
- Ice Fang
- Leafage
- Leaf Storm
- Light Screen
- Night Shade
- Play Rough
- Pyro Ball
- Reflect
- Rock Slide
- Sonic Boom
- Sucker Punch
- Supersonic
- Thunder Fang
#### Tweaks and Fixes (Moves)
- Confusion
- Dragon Claw
- Eruption
- Fire Punch
- Flame Burst
- Flame Charge
- Flamethrower
- Frost Breath
- Hypnosis
- Ice Punch
- Icy Wind
- Mud Sport
- Poison Fang
- Protect
- Psychic
- Quick Attack
- Rage Powder
- Razor Leaf
- Sand Attack
- Scratch
- Shadow Claw
- Sing
- Spore
- String Shot
- Tackle
- Tail Whip
- Thunder
- Thunder Punch
- Thunder Wave
- Water Gun
- Water Pulse
- Water Sport
#### Miscellaneous Particles
- Added Wild Might particles for Alpha Pokémon.
- Updated stat increase and decrease particles.
### Localization
- Updated translations for:
- Japanese
- German
- Spanish
- Ecuadorian Spanish
- Portuguese
- Brazilian Portuguese
- Simplified Chinese
- Traditional Chinese
- Korean
- Russian
-41
View File
@@ -1,41 +0,0 @@
# Changelog
## [1.8.1 (September 13th, 2026)](#1-8-1)
### Animation updates for the following Pokémon
- Flapple, Appletun, Dipplin, Hydrapple (placeholder animations only).
### Fixes
- Fixed a potential world generation stall caused by Type Gem clusters.
- Fixed Pokémon on shoulders not animating if they are visibly holding a held item.
- Fixed NPCs getting their own TM progress file.
- Fixed odd NeoForge crash due to a dependency being outdated. It will now correctly flag the dependency for being outdated.
- Fixed the TM Machine refusing move selections that the `unlockAllMoveDexMovesByDefault` config had made visible, leaving the move slot blank and the craft button unresponsive.
- Fixed the Beast Ball not applying its 0.1× penalty to Pokémon that are not Ultra Beasts.
- Fixed the Moon Ball reading the world's total elapsed time instead of the time of day, so its bonus applied during the day instead.
- Fixed the Love Ball never awarding its 2.5× opposite gender bonus, and matching species by display name instead of identifier.
- Fixed the Timer Ball being weaker than a regular Poké Ball on early turns and only reaching 4× on turn 14 instead of turn 10.
- Fixed the Luxury Ball only doubling Friendship gains of 1, leaving level up and berry gains unaffected.
- Fixed Rufflet, Braviary, and Hisuian Braviary flooding logs with errors about missing sound events.
- Fixed Pokémon property typos in Pangoro and Dedenne herd spawn files.
- Fixed unintentional watermelon cosmetic for Hisuian Electrode.
- Fixed Blastoise having an unintentional passenger seat.
- Fixed form typo in Rockruff-Dusk spawn, preventing it from being able to spawn.
- Fixed the `stat_compare` and `stat_equal` evolution requirements crashing the game when given `special_attack` or `special_defence`, and made them treat any non-permanent stat as a failed requirement instead of crashing.
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait.
- Fixed Ampharos' eyes not emitting particles.
- Fixed Alolan Raticate's cry not playing.
- Fixed several incorrect light-based spawning conditions for ghosts and bugs.
- Removed unused, placeholder habitat pools.
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to having an expression assigned as a recoil animation.
- Fixed Lucario's aura particles not emitting.
- Fixed Lycanroc's head movement.
- Added head movement to Archaludon.
- Fixed Duraludon line not having their sleep animations.
- Fixed Diglett line not having their faint animations.
- Fixed Kingambit moving its legs while walking.
### Localization
- Updated translations for:
- Brazilian Portuguese
- Simplified Chinese
- Japanese
-211
View File
@@ -1,211 +0,0 @@
# Changelog
## [1.8.2 (October 11th, 2026)](#1-8-2)
### Additions
- The following items can now be placed on the ground as blocks:
- King's Rock, Galarica Cuff, Unremarkable Teacup, Masterpiece Teacup, Chipped Pot, Cracked Pot, Sachet, Metal Powder, Quick Powder, Bright Powder, Soft Sand, Electirizer, Magmarizer, Icy Rock, Heat Rock, Smooth Rock, Damp Rock, Destiny Knot, Luminous Moss, Never-Melt Ice, Wide Lens, Scope Lens, and Zoom Lens.
- I wasn't finished. Iron Ball, Metal Coat, Throat Spray, Terrain Extender, Medicinal Brew, Moomoo Milk, HP Up, Protein, Iron, Carbos, Calcium, Zinc, PP Up, PP Max, Upgrade, Dubious Disc, and Loaded Dice.
- The following items can now be placed on various surfaces:
- Galarica Wreath, Magnet, Flame Orb, Life Orb, Toxic Orb, Light Ball, and Smoke Ball.
- The following items can now be placed as blocks and have additional functionality:
- Ring Target, Metronome, and Eject Button.
- The following items can now be worn by Players and Pokémon:
- Galarica Wreath, Cleanse Tag, Spell Tag, Sharp Beak, Covert Cloak, Shell Helmet, and Never-Melt Ice.
- Wearable items can now be equipped by a player by holding it and right-clicking (or whatever you've rebound use/interact to).
- Wearable items that can be placed down are exempt from this behaviour.
- Mint Leaves can now be placed on the ground as Leaf Litter, or in Flower Pots.
- The recall animation now renders the ball in hand with the player's arm raised.
- Added Oricorio Flowers.
- Added Terrain Plants, four plants grown from Electric, Grassy, Misty, and Psychic Seeds, which glow under specific environmental conditions and can be farmed for more of their seeds.
- Berries can now be placed in Flower Pots.
- The evolution tree for a Pokémon can now be viewed in the Pokédex, showing the Pokémon's full family line and what's needed to evolve into each stage.
- Added custom sounds to a bunch of items that can be placed as blocks: liquid bottles, pill bottles, powders, and dice.
- Added paper as a new cosmetic item for Pikachu.
- Burmy changes its cloak after a battle it took part in, based on the biome.
- Trash Cloak in the Deep Dark, Nether and End.
- Plant Cloak in lush biomes such as forests, jungles, plains and swamps.
- Sandy Cloak in sandy biomes, beaches and caves.
### Pokémon Added
#### Generation 4
- Snover
- Abomasnow
- Burmy
- Wormadam
- Mothim
#### Generation 5
- Tympole
- Palpitoad
- Seismitoad
#### Generation 6
- Helioptile
- Heliolisk
#### Generation 7
- Grubbin
- Charjabug
- Vikavolt
- Minior
#### Generation 8
- Eternatus
#### Generation 9
- Greavard
- Houndstone
### Model updates for the following Pokémon
- Sudowoodo
- Spheal
- Cacnea
- Cacturne
- Binacle
- Barbaracle
- Glimmet
- Glimmora
- Deerling
- Sawsbuck (All forms)
- Absol
- Poliwag
- Politoed
- Ekans
- Oddish
- Gloom
- Vileplume
- Bellossom
- Shroomish
- Chespin
- Quilladin
- Chesnaught
- Fennekin
- Braixen
- Delphox
- Froakie
- Frogadier
- Greninja
- Shinx
- Luxio
- Luxray
- Slakoth
- Riolu
- Lucario
- Corsola
- Mime Jr.
### Animation updates for the following Pokémon
- Treecko (New Shoulder Mount!)
- Bonsly (New Shoulder Mount!)
- Sudowoodo
- Cacnea
- Cacturne
- Ekans
- Arbok
- Oddish
- Gloom
- Vileplume
- Bellossom
- Pichu
- Pikachu
- Raichu (+ Alolan)
- Greninja
- Slakoth
### Changes
- Slowed the speed of Type Gem growth, and revamped their generation to grow in a more crystal-like shape.
- Terrain Seeds can now be planted.
- Terrain Seeds are now consumed during PvP and NPC battles.
- Glow Item Frames can be crafted using Bright Powder as an alternative to Glow Ink Sacs.
- Bright Powder can be used on Signs and Hanging Signs to make text glow, as an alternative to Glow Ink Sacs.
- Updated various recipes to display in Recipe Groups within the Recipe Book.
- Updated most recipes to display more logically and consistently within the Recipe Book's categories.
- Updated the following recipes
- Never-Melt Ice, Focus Sash, Blunder Policy, Weakness Policy, Blank TM.
- Made ride rotations less jittery in multiplayer.
- Changes Disc Shelf TMs to use textures instead of tinting and overlays to line up with item display.
- Blank TM can now be inserted into the Disc Shelf.
- Added a TM creative tab.
- Made regional and Vivillon split-evo locations mutually exclusive.
- Vivillon's patterns are now viewable as forms with form names and unique Pokédex entries displayed.
- Item evolutions now unlock **all** locked evolutions whose requirements are met instead of stopping after the first.
- Exceptions being "forced" evolutions which stop after the first one.
- Removed Mega Drain as a TM.
- PokeSnacks (and other area spawns) will now start the vertical offset checking from the center rather than base coordinate
- This will help PokeSnacks to work inside caves or tighter spaces better.
### Fixes
- Type Gem blocks now properly generate clusters off of the block.
- Correctly grant "They're Minerals!" advancement when planting a Type Gem.
- Shoulder mounted Pokémon now follow player body animations, improving their positioning with custom player animations and default actions.
- Shoulder mounted Pokémon now sit slightly higher with chest armor equipped for better alignment, with a configurable blacklist.
- Type Gem Blocks now have advancements to give you the recipe in the recipe book (the data for the advancements were inside the recipes somehow??).
- Some other recipes without advancements have also been corrected.
- Corrected the TM Recipes for Charge Beam and Payday.
- Starter Screen Categories will no longer stop rendering if they are bigger than the viewport and you are not viewing the top or bottom edges.
- The Random Starter option no longer runs off into other categories or becomes unaccessible due to scroll limits if it causes a new row.
- Mochi and Feathers no longer drop glass bottles on use.
- Fixed a rare set of concurrency bugs related to reloading packs on NeoForge; fixes a ModernFix compatibility issue.
- Removed unnecessary conditions for Mimikyu's standing pose. Hopefully fixes the rare instance of it using its shoulder animations during battle in multiplayer scenarios.
- Fixed Rockruff line not blinking.
- Fixed Flygon sometimes using its running animation instead of walking.
- Fixed Bramblin always sleeping outside of battle.
- Fixed Dragonair using its battle idle as a normal idle.
- Fixed Lycanroc's head movement.
- Added head movement to Archaludon.
- Fixed Duraludon line not having any sleep animations.
- Fixed Diglett line not having their faint animations.
- Fixed Kingambit moving its legs while walking.
- Fixed Alolan Diglett's cry not playing.
- Fixed shiny alpha Snorlax using the non-shiny texture.
- Fixed Crabominable's head breaking when looking around.
- Fixed Shiny Dipplin and Hydrapple's textures.
- Various structure fixes.
- Fixed shiny Pokémon under certain circumstances forgetting taught TMs.
- Fixed certain Pokémon sounds sometimes being doubled when played at specific times in their animations.
- Fixed ball capture beam origin coming from behind the ball.
- Fixed Safari Ball catch rate outside of battle.
- Fixed alpha eye (and other) particles not respecting the client particle density setting in video options.
- Fixed the TM Machine UI not closing when player is moving away from it.
- Moved the config category for alpha/shiny chance config fields for fossil machine and honey log slathering to Spawning.
- Fixed crashes for addon species with non-Cobblemon namespaces in evolution, fishing bait, and Pokédex lookups.
- Bees will now grow Apricorns and the Galarica Nut Bush, which were in the bee growables tag but never handled, and correctly spend their nectar when they do.
- Saccharine Leaves can now be bone mealed.
- Fixed equipping a stack of wearable items instead of one item of a stack.
- Fixed TM stack consumption in TM Machine screen when a player teaches a move to a Pokémon.
- Fixed every Miltank giving mushroom stew, not only mushroom variants.
- Medicinal Leeks can now be eaten when they aren't being planted, matching their food properties.
- Sweeping attacks no longer set owned Pokémon on fire (e.g. with Fire Aspect) or knock them back. Friendly Fire is now off.
- Fixed Pokémon not losing their previous form's signature moves when changing forms (e.g. Rotom keeping Overheat after leaving its Heat form).
- Form-specific moves gained from a form change are now only granted if the Pokémon is high enough level to learn them, and no longer create duplicates in benched moves.
- Spinda now learns Teeter Dance at level 32 instead of via TM.
- Fixed Oricorio Pa'u form description in the Pokédex.
- Fixed PC playing sound on close when opened via command.
### Developer
- Evolution requirements can now set `pokedexIcon` and `pokedexTooltip` directly in their JSON to control how they're shown on the Pokédex's evolution tree, for example:
```json
{
"variant": "attack_defence_ratio",
"pokedexIcon": "minecraft:diamond_sword",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.attack_defence_ratio.tooltip"
}
```
- Fixed the wrong order of operations in the code flow prior to the `FOSSIL_REVIVED` event causing Pokémon to appear without the entity link.
- Fixed TestPartySlot command not correctly comparing the provided spec with the target Pokémon.
### Molang & Datapacks
- A new `negate` variant for evolution requirements has been added, which will negate (invert) the provided inner condition.
- Added Molang functions for individual Pokédex ids, wherein a subdex is passed in as a string to `dex_caught_count` etc.
- Added a `battle_ended` callback, backed by the new `BATTLE_ENDED` event, which fires whenever a battle ends for any reason, alongside a multitude of flags not previously covered by `BATTLE_VICTORY`.
- Added `battle_pokemon` to battle actor structs and `has_faced_opponents` to battle Pokémon structs.
- Added the `cobblemon:form_change/bagworm_cloak/plant_cloak`, `sandy_cloak` and `trash_cloak` biome tags, which control Burmy's cloak after battle.
### Localization
- Updated translations for:
- Brazilian Portuguese
- Canadian French
- Japanese
- Korean
- Russian
- Ukrainian
-107
View File
@@ -1,107 +0,0 @@
# Changelog
## [1.8.3 (October 25th, 2026)](#1-8-3)
### Additions
- Added compatability with AppleSkin.
### New Rideable Pokémon
- Vikavolt
- Fearow
### Particles
## New Move Animations
- Water Shuriken
### Cries added/updated for the following Pokémon
- Hoppip
- Skiploom
- Jumpluff
- Houndour
- Houndoom
- Wurmple
- Silcoon
- Beautifly
- Cascoon
- Dustox
- Surskit
- Masquerain
- Torkoal
- Starly
- Staravia
- Staraptor
- Combee
- Vespiquen
- Dusknoir
- Helioptile
- Heliolisk
- Grubbin
- Charjabug
- Vikavolt
- Applin (updated)
- Flapple (updated)
- Appletun
- Runerigus
- Dipplin
### Animation updates for the following Pokémon
- Vikavolt
- Magby
- Magmar
- Ninetales (+ Alolan)
- Spearow
- Fearow
- Squawkabilly
- Mudkip
- Marshtomp
- Swampert
### Changes
- Changed the Misc icon in the Campfire Pot Recipe Book to Health Candy.
- Moved EXP Candies from the Medicine recipe book category to the Misc category.
- Removed Dynamic Lights data from the Magmarizer item for consistency with the block.
- Added `doPokemonPredation` game rule, which controls aggressiveness of Pokémon with the `hate_entity` brain task defined.
- Added `predation_chance` server config value, which controls the possibility of attacking the hated entity found in `hate_entity` brain tasks.
- Prehistoric Eroded Pillar generates slightly more frequently.
- Togekiss was changed from a jet-style mount to bird-style.
### Fixes
- Fixed datapacks with item interaction evolutions that don't specify an item crashing the world on load.
- Fixed the Pokédex evolution tree crashing when a datapack's dex entry is missing an evolution's form.
- Fixed the Pokédex evolution tree crashing with a stack overflow on evolutions that loop back on themselves.
- Fixed the Pokédex evolution tree crashing when looking at a dex that is not part of the "National Dex" aggregate.
- Fixed some display names in the Pokédex evolution tree having unintended font colours.
- Fixed evolution trees not building properly when a Pokémon has several Pokédex entries for different regions.
- Fixed form-changing evolutions sometimes resolving to the source form in the Pokédex evolution tree.
- Broken datapack evolutions are now logged and skipped in the Pokédex evolution tree instead of crashing.
- Fixed the `alpha` property having no tab completion in commands such as `/pokespawn`, `/pokegive` and `/pokeedit`, and added the missing `dmax_level`, `gmax_factor` and `tradable` aliases to tab completion.
- Fixed friendship booster items not being recognised as such.
- Fixed conflicts with other mods that would make use of recipe books in certain ways (Farmer's Delight Cooking Pot).
- Fixed Pokémon battles stalling when a sidemod cancels the `THROWN_POKEBALL_HIT` hit event.
- Fixed choice feature tab completion in `spawnpokemon/pokegive/pokeedit` commands.
- Fixed MoLang that's targeting players intermittedly failing when they have died, requiring them to relog.
- Fixed a NeoForge bug where none of the Cobblemon plants/berries could be placed into pots.
- Fixed party overlay not hiding when the player opens the Pokédex scanner.
- Fixed `hate_entity` task focusing on its own entity, therefore enabling deadly warfare between Zangoose and Seviper.
- Prehistoric Frozen Pond, Prehistoric Frozen Spike, and Prehistoric Lush Den now actually generate (oops).
- Fixed Frogadier floating in its `battle_idle` animation.
- Fixed absence or incorrect mergers in spawning conditions, causing the max height/depth of conditions to use the min value instead.
- Added stricter riding checks on server side to prevent possible packet forgery when a player tries to ride Pokémon.
- Fixed Pokémon cries and animation sounds played from GUIs, such as the starter selection screen and the Pokédex, being tied to the Master volume slider instead of Friendly Creatures.
- Fixed Archeops T-posing during its `air_cry` animation.
### Developer
- Bumped NeoForge to `21.1.195` to get access to MixinExtras `0.5` and the expressions annotation.
- New Platform Event `PLAYER_RESPAWN` which is fired when a player respawns (either through death or returning from the end).
- Platform Events `SERVER_PLAYER_TICK_PRE` and `SERVER_PLAYER_TICK_POST` no longer fire when the player is dead.
- It is now tied much closer to the actual entity rather than the player or their connection.
### Molang & Datapacks
- Fixed `q.pokemon.nature` returning class instance instead of proper string value (`cobblemon:adamant`).
- Fixed `q.entity.is_looking_at` result being always `1` for whatever entity passed as the target parameter.
- Added hate chance optional parameter to `hate_entity` task. `1.0` will make an entity always attack the target.
### Localization
- Updated translations for:
- Russian
- Japanese
- Brazilian Portuguese
+6 -15
View File
@@ -1,8 +1,9 @@
import utilities.isSnapshot
import utilities.version
import utilities.gitBranch
import utilities.gitCommit
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
/*
*
@@ -71,14 +72,6 @@ tasks.withType<Test> {
}
}
// Git info lives in its own output dir (not processResources) so a new commit only rewrites this one file.
val writeGitInfo by tasks.registering(WriteProperties::class) {
destinationFile = layout.buildDirectory.file("generated/gitInfo/cobblemon-git.properties")
property("commit", gitCommit())
property("branch", gitBranch())
}
sourceSets.main { output.dir(mapOf("builtBy" to writeGitInfo), layout.buildDirectory.dir("generated/gitInfo")) }
tasks.processResources {
exclude("data/**/README.md")
}
@@ -105,13 +98,11 @@ sourceSets {
property("modid", "cobblemon")
property("version", project.version())
property("isSnapshot", if(rootProject.isSnapshot()) "true" else "false")
property("gitCommit", versioning.info.commit)
property("branch", versioning.info.branch)
System.getProperty("buildNumber")?.let { property("buildNumber", it) }
property("timestamp", OffsetDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm:ss")) + " UTC")
}
}
}
}
// The platform jars are built from :common:jar, so the remapped common jars are only needed by `publish`
tasks.assemble {
setDependsOn(dependsOn.filterNot { (it as? TaskProvider<*>)?.name in setOf("remapJar", "remapSourcesJar") })
}
@@ -1,24 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.duck;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
import java.util.Optional;
/**
* Duck that facilitates getting a level key for a given level.
*
* @author dualspiral
* @since August 30th, 2026
*/
public interface MinecraftServerDuck {
Optional<ResourceKey<Level>> cobblemon$getLevelKey(Level level);
}
@@ -1,21 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.duck;
import net.minecraft.resources.ResourceLocation;
/**
* Duck that facilitates getting a level key that we store on the level.
*
* @author dualspiral
* @since August 30th, 2026
*/
public interface ServerLevelDuck {
ResourceLocation cobblemon$getLevelResourceLocation();
}
@@ -8,42 +8,54 @@
package com.cobblemon.mod.common.mixin;
import com.llamalad7.mixinextras.sugar.Local;
import com.cobblemon.mod.common.block.SaccharineLeafBlock;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.animal.Bee;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Final;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(targets = "net.minecraft.world.entity.animal.Bee$BeeGrowCropGoal")
public abstract class BeeEntityMixin {
@Shadow(aliases = {"this$0", "field_20373"}) @Final private Bee bee;
@Unique
private BlockState cobblemon$result = null;
@Inject(method = "tick()V", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/block/BonemealableBlock;performBonemeal(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/util/RandomSource;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)V",
shift = At.Shift.BY,
by = 2
),
locals = LocalCapture.CAPTURE_FAILHARD
)
private void injectCustomGrowth(CallbackInfo ci, int i, BlockPos blockPos, BlockState blockState, Block block) {
if (block instanceof SaccharineLeafBlock) {
int age = blockState.getValue(SaccharineLeafBlock.Companion.getAGE());
boolean waterlogged = blockState.getValue(BlockStateProperties.WATERLOGGED);
if (age < 2 && !waterlogged) {
this.cobblemon$result = blockState.setValue(SaccharineLeafBlock.Companion.getAGE(), age + 1);
}
}
}
// Grows bonemealable blocks vanilla's hardcoded checks miss, returning the state so it handles the rest.
@ModifyVariable(method = "tick", at = @At(value = "LOAD", ordinal = 0), index = 5)
private BlockState cobblemon$growBonemealableBlocks(BlockState grownState, @Local(ordinal = 0) BlockState blockState, @Local BlockPos pos) {
if (grownState != null || blockState.is(Blocks.CAVE_VINES) || blockState.is(Blocks.CAVE_VINES_PLANT)) {
return grownState;
private BlockState applyCustomBlockState(BlockState before) {
if(this.cobblemon$result != null && before == null) {
BlockState result = this.cobblemon$result;
this.cobblemon$result = null;
return result;
}
if (!(blockState.getBlock() instanceof BonemealableBlock bonemealable)) {
return null;
}
ServerLevel level = (ServerLevel) this.bee.level();
if (!bonemealable.isValidBonemealTarget(level, pos, blockState)) {
return null;
}
bonemealable.performBonemeal(level, this.bee.getRandom(), pos, blockState);
return level.getBlockState(pos);
return before;
}
}
@@ -15,7 +15,6 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.mojang.serialization.MapCodec;
import java.util.Optional;
import java.util.function.Supplier;
import net.minecraft.SharedConstants;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.IntTag;
import net.minecraft.resources.ResourceKey;
@@ -46,24 +45,4 @@ public class ChunkStorageMixin {
int curVersion = vanillaFixed.contains(VERSION_KEY) ? vanillaFixed.getInt(VERSION_KEY) : 0;
return DataFixTypes.CHUNK.update(CobblemonSchemas.getDATA_FIXER(), vanillaFixed, curVersion, CobblemonSchemas.DATA_VERSION);
}
// Our update on the same MC version still needs its own fixes so run them here in that case.
@Inject(
method = "upgradeChunkTag(Lnet/minecraft/resources/ResourceKey;Ljava/util/function/Supplier;Lnet/minecraft/nbt/CompoundTag;Ljava/util/Optional;)Lnet/minecraft/nbt/CompoundTag;",
at = @At("HEAD"),
cancellable = true
)
private void cobblemon$doChunkFixOnCurrentVersion(
ResourceKey<Level> levelKey,
Supplier<DimensionDataStorage> storage,
CompoundTag chunkData,
Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey,
CallbackInfoReturnable<CompoundTag> cir
) {
if (ChunkStorage.getVersion(chunkData) != SharedConstants.getCurrentVersion().getDataVersion().getVersion()) {
return;
}
int curVersion = chunkData.contains(VERSION_KEY) ? chunkData.getInt(VERSION_KEY) : 0;
cir.setReturnValue(DataFixTypes.CHUNK.update(CobblemonSchemas.getDATA_FIXER(), chunkData, curVersion, CobblemonSchemas.DATA_VERSION));
}
}
@@ -0,0 +1,54 @@
/*
* 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.client.multiplayer.ClientLevel;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
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.ArrayList;
import java.util.Arrays;
import java.util.Objects;
@Mixin(ClientPacketListener.class)
public class ClientPacketListenerMixin {
@Shadow private ClientLevel level;
@Inject(method = "handleSetEntityPassengersPacket", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/world/entity/Entity;hasIndirectPassenger(Lnet/minecraft/world/entity/Entity;)Z", shift = At.Shift.BEFORE), cancellable = true)
private void cobblemon$handlePokemonPassengers(ClientboundSetPassengersPacket packet, CallbackInfo ci) {
Entity entity = this.level.getEntity(packet.getVehicle());
if (entity instanceof PokemonEntity pokemon) {
var passengers = new ArrayList<Entity>();
for (int passenger : packet.getPassengers()) {
passengers.add(this.level.getEntity(passenger));
}
pokemon.getOccupiedSeats().values().stream()
.filter(x -> !passengers.contains(x))
.filter(Objects::nonNull)
.forEach(Entity::stopRiding);
// Mount passengers not yet in the map
var currentPassengers = new ArrayList<>(pokemon.getOccupiedSeats().values());
passengers.stream()
.filter(x -> !currentPassengers.contains(x))
.filter(Objects::nonNull)
.forEach(x -> x.startRiding(pokemon, true));
ci.cancel();
}
}
}
@@ -0,0 +1,133 @@
/*
* 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.mixin.accessor.SinglePoolElementAccessor;
import com.cobblemon.mod.common.world.structureprocessors.HeightRangeStructureProcessor;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.pools.*;
import net.minecraft.world.level.levelgen.structure.pools.alias.PoolAliasLookup;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Optional;
@Mixin(JigsawPlacement.class)
public class JigsawPlacementMixin {
@Unique
private static final Logger cobblemon$LOGGER = LoggerFactory.getLogger("Cobblemon/HeightRangeVeto");
@Inject(
method = "addPieces(Lnet/minecraft/world/level/levelgen/structure/Structure$GenerationContext;Lnet/minecraft/core/Holder;Ljava/util/Optional;ILnet/minecraft/core/BlockPos;ZLjava/util/Optional;ILnet/minecraft/world/level/levelgen/structure/pools/alias/PoolAliasLookup;Lnet/minecraft/world/level/levelgen/structure/pools/DimensionPadding;Lnet/minecraft/world/level/levelgen/structure/templatesystem/LiquidSettings;)Ljava/util/Optional;",
at = @At("RETURN"),
cancellable = true
)
private static void onAddPieces(
Structure.GenerationContext context,
Holder<StructureTemplatePool> startPool,
Optional<ResourceLocation> startJigsawName,
int maxDepth,
BlockPos pos,
boolean useExpansionHack,
Optional<Heightmap.Types> projectStartToHeightmap,
int maxDistanceFromCenter,
PoolAliasLookup aliasLookup,
DimensionPadding dimensionPadding,
LiquidSettings liquidSettings,
CallbackInfoReturnable<Optional<Structure.GenerationStub>> cir
) {
Optional<Structure.GenerationStub> result = cir.getReturnValue();
if (result.isEmpty()) {
return;
}
BlockPos spawnPos = result.get().position();
String poolId = startPool.unwrapKey().map(key -> key.location().toString()).orElse("unknown");
boolean canPlace = cobblemon$canStructurePlace(context, startPool, spawnPos);
cobblemon$LOGGER.info(
"[HeightRangeVeto] addPieces evaluated requestedPos={} spawnPos={} startPool={} -> canPlace={}",
pos, spawnPos, poolId, canPlace
);
if (canPlace) {
return;
}
cobblemon$LOGGER.info(
"[HeightRangeVeto] Vetoing structure start at {} (startPool={})",
spawnPos, poolId
);
cir.setReturnValue(Optional.empty());
}
@Unique
private static boolean cobblemon$canStructurePlace(
Structure.GenerationContext generationContext,
Holder<StructureTemplatePool> startPool,
BlockPos startPos
) {
for (StructurePoolElement element : startPool.value().templates) {
Optional<HeightRangeStructureProcessor> heightRangeProcessor = cobblemon$getHeightRangeProcessor(element);
if (heightRangeProcessor.isEmpty()) {
continue;
}
HeightRangeStructureProcessor processor = heightRangeProcessor.get();
boolean valid = processor.isValidPlacement(
generationContext.chunkGenerator(),
generationContext.heightAccessor(),
generationContext.randomState(),
startPos
);
cobblemon$LOGGER.info(
"[HeightRangeVeto] template={} pos={} valid={}",
element, startPos, valid
);
if (!valid) {
return false;
}
}
return true;
}
@Unique
private static Optional<HeightRangeStructureProcessor> cobblemon$getHeightRangeProcessor(StructurePoolElement element) {
if (!(element instanceof SinglePoolElement)) {
return Optional.empty();
}
StructureProcessorList processorList = ((SinglePoolElementAccessor) element).getProcessors().value();
for (StructureProcessor processor : processorList.list()) {
if (processor instanceof HeightRangeStructureProcessor heightRangeStructureProcessor) {
return Optional.of(heightRangeStructureProcessor);
}
}
return Optional.empty();
}
}
@@ -75,10 +75,9 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
public @NotNull HitResult pick(double hitDistance, float partialTicks, boolean hitFluids) {
Entity vehicle = this.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity && pokemonEntity.getDelegate() instanceof PokemonClientDelegate delegate) {
// Passenger order isn't seat order, so look the seat up directly
Seat seat = pokemonEntity.getSeatForPassenger(this);
MatrixWrapper locator = seat != null ? delegate.getLocatorStates().get(seat.getLocator()) : null;
if (locator == null) return super.pick(hitDistance, partialTicks, hitFluids);
int seatIndex = pokemonEntity.getPassengers().indexOf(this);
Seat seat = pokemonEntity.getSeats().get(seatIndex);
MatrixWrapper locator = delegate.getLocatorStates().get(seat.getLocator());
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
@@ -100,8 +99,7 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
@Unique
private Vec3 cobblemon$getEyePosition(float partialTicks, PokemonEntity pokemonEntity, Vec3 locatorOffset) {
// The ride owns the orientation, the passenger's own controller is never active
OrientationController controller = ((OrientationControllable) pokemonEntity).getOrientationController();
OrientationController controller = this.getOrientationController();
float currEyeHeight = this.getEyeHeight();
Vec3 offset = locatorOffset.add(pokemonEntity.position());
@@ -109,7 +107,7 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
Quaternionf orientation = controller.getRenderOrientation(partialTicks);
Vec3 rotatedEyeHeight = new Vec3(orientation.transform(new Vector3f(0f, currEyeHeight - (this.getBbHeight() / 2), 0f)));
offset = offset.add(rotatedEyeHeight);
offset.add(rotatedEyeHeight);
}
return offset;
@@ -1,40 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.duck.MinecraftServerDuck;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.Map;
import java.util.Optional;
@Mixin(MinecraftServer.class)
public class MinecraftServerMixin implements MinecraftServerDuck {
@Shadow
@Final
private Map<ResourceKey<Level>, ServerLevel> levels;
@Override
public Optional<ResourceKey<Level>> cobblemon$getLevelKey(final Level level) {
return this.levels.entrySet()
.stream()
// Reference equality is intended here, the level we're handed is the same one as in the
// map so we don't need to check value semantics.
.filter(entry -> entry.getValue() == level)
.map(Map.Entry::getKey)
.findFirst();
}
}
@@ -35,16 +35,13 @@ import com.cobblemon.mod.common.util.DataKeys;
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
@@ -53,7 +50,6 @@ import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
@@ -375,33 +371,4 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
this.cobblemon$rideEyePos = rideEyePos;
}
/**
* Sweeping attacks apply post-attack enchantment effects (e.g. Fire Aspect) to every entity caught
* in the sweep, even if the damage itself was blocked. Skip them for Pokémon immune to the attack.
*/
@WrapOperation(
method = "attack",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/enchantment/EnchantmentHelper;doPostAttackEffects(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)V")
)
private void cobblemon$skipPostAttackEffectsOnImmunePokemon(ServerLevel level, Entity target, DamageSource source, Operation<Void> original) {
if (target instanceof PokemonEntity pokemonEntity && pokemonEntity.isInvulnerableTo(source)) {
return;
}
original.call(level, target, source);
}
/**
* Sweeping attacks also knock back every entity caught in the sweep regardless of whether it took damage.
*/
@WrapOperation(
method = "attack",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;knockback(DDD)V")
)
private void cobblemon$skipKnockbackOnImmunePokemon(LivingEntity target, double strength, double x, double z, Operation<Void> original, @Local DamageSource source) {
if (target instanceof PokemonEntity pokemonEntity && pokemonEntity.isInvulnerableTo(source)) {
return;
}
original.call(target, strength, x, z);
}
}
@@ -40,7 +40,6 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
import java.util.LinkedHashMap;
import java.util.Set;
import java.util.function.Consumer;
@@ -115,13 +114,15 @@ public abstract class ServerEntityMixin {
if (currentPassengers.equals(cobblemon$lastSeatPassengers)) return;
cobblemon$lastSeatPassengers = List.copyOf(currentPassengers);
var assignments = new LinkedHashMap<String, Integer>();
pokemon.getOccupiedSeats().forEach((seat, passenger) -> {
if (seat.getLocator() == null) return;
if (passenger == null) return;
assignments.put(seat.getLocator(), passenger.getId());
cobblemon$broadcast(new ClientboundSeatAssignmentPacket(
passenger.getId(),
pokemon.getId(),
seat.getLocator()
));
});
cobblemon$broadcast(new ClientboundSeatAssignmentPacket(pokemon.getId(), assignments));
}
private void cobblemon$broadcast(NetworkPacket<?> packet) {
@@ -8,69 +8,22 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.duck.MinecraftServerDuck;
import com.cobblemon.mod.common.duck.ServerLevelDuck;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.storage.WritableLevelData;
import net.minecraft.world.phys.AABB;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
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;
import java.util.function.Supplier;
@Mixin(ServerLevel.class)
public abstract class ServerLevelMixin extends Level implements ServerLevelDuck {
// Ignored by Mixin, fulfils Java contract
protected ServerLevelMixin(
WritableLevelData levelData,
ResourceKey<Level> dimension,
RegistryAccess registryAccess,
Holder<DimensionType> dimensionTypeRegistration,
Supplier<ProfilerFiller> profiler,
boolean isClientSide,
boolean isDebug,
long biomeZoomSeed,
int maxChainedNeighborUpdates
) {
super(levelData, dimension, registryAccess, dimensionTypeRegistration, profiler, isClientSide, isDebug, biomeZoomSeed, maxChainedNeighborUpdates);
}
@Shadow
public abstract MinecraftServer shadow$getServer();
/**
* Cached to speed up level world key checks every tick.
*/
private ResourceLocation cobblemon$levelResourceLocation;
@Override
public ResourceLocation cobblemon$getLevelResourceLocation() {
if (this.cobblemon$levelResourceLocation == null) {
this.cobblemon$levelResourceLocation = ((MinecraftServerDuck) this.shadow$getServer())
.cobblemon$getLevelKey(this)
.map(ResourceKey::location)
.orElse(null);
}
return this.cobblemon$levelResourceLocation;
}
public abstract class ServerLevelMixin {
@Inject(method = "findLightningRod", at = @At(value = "RETURN"), cancellable = true)
private void cobblemon$findLightningRod(BlockPos blockPos, CallbackInfoReturnable<Optional<BlockPos>> ci) {
@@ -15,18 +15,15 @@ import com.cobblemon.mod.common.api.riding.RidingStyle;
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner;
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawnerFactory;
import com.cobblemon.mod.common.api.stats.CobblemonStats;
import com.cobblemon.mod.common.duck.ServerLevelDuck;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@@ -36,13 +33,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ServerPlayer.class)
public abstract class ServerPlayerMixin implements PlayerSpawnerAccessor {
@Shadow
public abstract ServerLevel serverLevel();
@Shadow
@Final
public MinecraftServer server;
@Unique
public PlayerSpawner cobblemon$spawner;
@@ -62,9 +55,12 @@ public abstract class ServerPlayerMixin implements PlayerSpawnerAccessor {
@Inject(method = "tick", at = @At("TAIL"))
private void cobblemon$tickSpawner(CallbackInfo ci) {
if (cobblemon$canSpawn()) {
getPlayerSpawner().tick();
if (!Cobblemon.config.getEnableSpawning()) {
return;
} else if (!serverLevel().getGameRules().getBoolean(CobblemonGameRules.DO_POKEMON_SPAWNING)) {
return;
}
getPlayerSpawner().tick();
}
@Inject(method = "rideTick", at = @At("HEAD"))
@@ -125,21 +121,4 @@ public abstract class ServerPlayerMixin implements PlayerSpawnerAccessor {
return entity;
}
/**
* Gets whether we should spawn Cobblemon entities here.
*
* @return true if we can spawn cobblemon in the current level,
* false otherwise
*/
@SuppressWarnings("resource")
@Unique
private boolean cobblemon$canSpawn() {
return Cobblemon.config.getEnableSpawning() &&
this.serverLevel().getGameRules().getBoolean(CobblemonGameRules.DO_POKEMON_SPAWNING) &&
// intentionally last as this is a more expensive check, even with all the caching
!Cobblemon.config.getWorldSpawningBlocklist().contains(
((ServerLevelDuck) this.serverLevel()).cobblemon$getLevelResourceLocation()
);
}
}
@@ -1,103 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.mixin.accessor.SinglePoolElementAccessor;
import com.cobblemon.mod.common.world.structureprocessors.HeightRangeStructureProcessor;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Optional;
import java.util.function.Predicate;
@Mixin(Structure.class)
public abstract class StructureMixin {
/**
* Discards a structure start when any of its pieces carries a {@link HeightRangeStructureProcessor}
* whose range the piece does not fit in.
* Crab Note: We have to make this has to happen here rather than in the processor itself. Terrain adaptation and {@code /locate}
* both read the start, so by the time processors run the terrain has already been generated and the
* structure is already on the map which previously were left behdind when we were trying to make some structures not generate via processor. Now
* Returning {@link StructureStart#INVALID_START} keeps the start from ever existing, which makes sure that weird generation isn't left behind.
*/
@Inject(
method = "generate",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/levelgen/structure/StructureStart;isValid()Z"),
cancellable = true
)
private void cobblemon$rejectStartsOutsideHeightRange(
RegistryAccess registryAccess, ChunkGenerator chunkGenerator, BiomeSource biomeSource,
RandomState randomState, StructureTemplateManager structureTemplateManager, long seed,
ChunkPos chunkPos, int references, LevelHeightAccessor heightAccessor,
Predicate<Holder<Biome>> validBiome,
CallbackInfoReturnable<StructureStart> cir,
@Local StructureStart structureStart
) {
for (StructurePiece piece : structureStart.getPieces()) {
if (!(piece instanceof PoolElementStructurePiece poolPiece)) {
continue;
}
Optional<HeightRangeStructureProcessor> heightRangeProcessor = cobblemon$getHeightRangeProcessor(poolPiece.getElement());
if (heightRangeProcessor.isEmpty()) {
continue;
}
boolean valid = heightRangeProcessor.get().isValidPlacement(
chunkGenerator,
heightAccessor,
randomState,
piece.getBoundingBox()
);
if (!valid) {
cir.setReturnValue(StructureStart.INVALID_START);
return;
}
}
}
@Unique
private static Optional<HeightRangeStructureProcessor> cobblemon$getHeightRangeProcessor(StructurePoolElement element) {
if (!(element instanceof SinglePoolElement)) {
return Optional.empty();
}
StructureProcessorList processorList = ((SinglePoolElementAccessor) element).getProcessors().value();
for (StructureProcessor processor : processorList.list()) {
if (processor instanceof HeightRangeStructureProcessor heightRangeStructureProcessor) {
return Optional.of(heightRangeStructureProcessor);
}
}
return Optional.empty();
}
}
@@ -83,7 +83,7 @@ public abstract class CameraMixin implements CameraDuck {
}
// reset the rotation values if it is now not being ridden.
MountedCameraRenderer.INSTANCE.reset();
MountedCameraRenderer.INSTANCE.setSmoothRotation(null);
original.call(instance, x, y, z);
}
}
@@ -16,7 +16,6 @@ import com.cobblemon.mod.common.item.crafting.CookingPotShapelessRecipe;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.ClientRecipeBook;
import net.minecraft.client.RecipeBookCategories;
import net.minecraft.client.gui.screens.recipebook.RecipeCollection;
@@ -64,12 +63,23 @@ public abstract class ClientRecipeBookMixin {
}
}
@Inject(method = "setupCollections",
at = @At(
value = "INVOKE",
target = "Lcom/google/common/collect/ImmutableMap;copyOf(Ljava/util/Map;)Lcom/google/common/collect/ImmutableMap;"
))
private void addCustomAggregateCategories(Iterable<RecipeHolder<?>> recipes, RegistryAccess registryAccess, CallbackInfo ci, @Local(ordinal = 1) Map<RecipeBookCategories, List<RecipeCollection>> map2) {
@Inject(method = "setupCollections", at = @At("RETURN"))
private void addCustomAggregateCategories(Iterable<RecipeHolder<?>> recipes, RegistryAccess registryAccess, CallbackInfo ci) {
Map<RecipeBookCategories, List<List<RecipeHolder<?>>>> map = categorizeAndGroupRecipes(recipes);
Map<RecipeBookCategories, List<RecipeCollection>> map2 = Maps.newHashMap();
ImmutableList.Builder<RecipeCollection> builder = ImmutableList.builder();
map.forEach((recipeBookCategories, list) -> {
List<RecipeCollection> collections = list.stream()
.map(listx -> new RecipeCollection(registryAccess, listx))
.peek(builder::add)
.collect(ImmutableList.toImmutableList());
map2.put(recipeBookCategories, collections);
});
RecipeBookCategories.AGGREGATE_CATEGORIES.forEach((recipeBookCategories, list) -> map2.put(recipeBookCategories, (List) list.stream().flatMap((recipeBookCategoriesx) -> ((List) map2.getOrDefault(recipeBookCategoriesx, ImmutableList.of())).stream()).collect(ImmutableList.toImmutableList())));
CobblemonRecipeCategories.Companion.getCustomAggregateCategories().forEach((recipeBookCategories, list) -> map2.put(recipeBookCategories, (List) list.stream().flatMap((recipeBookCategoriesx) -> ((List) map2.getOrDefault(recipeBookCategoriesx, ImmutableList.of())).stream()).collect(ImmutableList.toImmutableList())));
this.collectionsByTab = ImmutableMap.copyOf(map2);
this.allCollections = builder.build();
}
}
@@ -10,13 +10,11 @@ package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker;
import com.cobblemon.mod.common.duck.RidePassenger;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import org.spongepowered.asm.mixin.Final;
@@ -98,14 +96,4 @@ public class HumanoidModelMixin {
MountedPlayerRenderer.INSTANCE.animate((HumanoidModel<AbstractClientPlayer>)(Object)this, pokemonEntity, player, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
}
}
@Inject(method = "setupAnim*", at = @At("TAIL"))
public void cobblemon$poseRecallArm(LivingEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch, CallbackInfo ci) {
if (!(entity instanceof AbstractClientPlayer) || entity.getVehicle() instanceof PokemonEntity) return;
float raise = RecallArmAnimationTracker.INSTANCE.raiseFactor(entity.getUUID());
if (raise <= 0f) return;
this.rightArm.xRot = Mth.lerp(raise, this.rightArm.xRot, (float) (-Math.PI / 2.0));
this.rightArm.yRot = Mth.lerp(raise, this.rightArm.yRot, 0f);
this.rightArm.zRot = Mth.lerp(raise, this.rightArm.zRot, 0f);
}
}
@@ -1,32 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.layers.ItemInHandLayer;
import net.minecraft.world.entity.HumanoidArm;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ItemInHandLayer.class)
public class ItemInHandLayerMixin {
@Inject(method = "renderArmWithItem", at = @At("HEAD"), cancellable = true)
private void cobblemon$hideRecallHandItem(LivingEntity entity, ItemStack itemStack, ItemDisplayContext itemDisplayContext, HumanoidArm humanoidArm, PoseStack poseStack, MultiBufferSource multiBufferSource, int light, CallbackInfo ci) {
if (humanoidArm == HumanoidArm.RIGHT && RecallArmAnimationTracker.INSTANCE.isRecalling(entity.getUUID())) {
ci.cancel();
}
}
}
@@ -9,14 +9,10 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.client.render.camera.MountedCameraRenderer;
import com.cobblemon.mod.common.client.render.player.FirstPersonRecallBallRenderer;
import com.cobblemon.mod.common.item.PokedexItem;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.renderer.ItemInHandRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.InteractionHand;
@@ -35,22 +31,11 @@ public class ItemInHandRendererMixin {
if (abstractClientPlayer.isUsingItem() && abstractClientPlayer.getUseItem().getItem() instanceof PokedexItem) {
ci.cancel();
}
else if (interactionHand == InteractionHand.MAIN_HAND && FirstPersonRecallBallRenderer.INSTANCE.render(poseStack, multiBufferSource, j, f)) {
ci.cancel();
}
else if (abstractClientPlayer instanceof OrientationControllable controllable && controllable.getOrientationController().isActive()) {
}
}
@Inject(method = "renderHandsWithItems", at = @At("HEAD"))
private void cobblemon$rollHandsWithRide(float partialTicks, PoseStack poseStack, MultiBufferSource.BufferSource buffer, LocalPlayer player, int light, CallbackInfo ci) {
float roll = MountedCameraRenderer.INSTANCE.getHandRoll();
if (roll != 0f) {
poseStack.mulPose(Axis.ZN.rotationDegrees(roll));
}
}
@Redirect(method = "renderHandsWithItems", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Quaternionf;)V"))
private void cobblemon$renderHandswithItems(PoseStack instance, Quaternionf quaternion) {
if (!(Minecraft.getInstance().player != null && Minecraft.getInstance().player.getVehicle() instanceof OrientationControllable controllable && controllable.getOrientationController().isActive())) {
@@ -32,6 +32,7 @@ import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.player.Input;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.ClipContext;
@@ -49,13 +50,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
import java.util.Objects;
@Mixin(LocalPlayer.class)
public abstract class LocalPlayerMixin extends AbstractClientPlayer implements OrientationControllable, DoubleJump {
@Unique Matrix3f cobblemon$lastOrientation;
@Unique int cobblemon$lastOrientationVehicleId = -1;
@Unique float cobblemon$lastRideXRot;
@Unique float cobblemon$lastRideYRot;
@@ -82,28 +81,30 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
private void cobblemon$updateRotationMatrixPassenger(CallbackInfo ci) {
if (!(this.getVehicle() instanceof OrientationControllable controllableVehicle)) return;
var vehicleController = controllableVehicle.getOrientationController();
if (!vehicleController.isActive()) {
// Forget the last sent value so the next ride always gets its first orientation sent
cobblemon$lastOrientation = null;
return;
}
var orientation = vehicleController.getOrientation();
var vehicleId = this.getVehicle().getId();
// Compare by value, the stored one is a copy so a reference check never matches
if (vehicleId == cobblemon$lastOrientationVehicleId && Objects.equals(orientation, cobblemon$lastOrientation)) return;
cobblemon$lastOrientation = orientation != null ? new Matrix3f(orientation) : null;
cobblemon$lastOrientationVehicleId = vehicleId;
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateOrientationPacket(vehicleId, orientation));
if (!vehicleController.isActive() || vehicleController.getOrientation() == cobblemon$lastOrientation) return;
cobblemon$lastOrientation = vehicleController.getOrientation() != null ? new Matrix3f(vehicleController.getOrientation()) : null;
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateOrientationPacket(this.getVehicle().getId(), vehicleController.getOrientation()));
}
@Inject(method = "rideTick", at = @At("HEAD"))
private void cobblemon$updateOrientationControllerRideTick(CallbackInfo ci) {
if (Minecraft.getInstance().player != (Object)this) return;
var driver = (LocalPlayer)(Object)this;
var vehicle = driver.getVehicle();
if (!(this.getVehicle() instanceof OrientationControllable controllableVehicle)) return;
var shouldUseCustomOrientation = cobblemon$shouldUseCustomOrientation((LocalPlayer)(Object)this);
var vehicleController = controllableVehicle.getOrientationController();
// Look angles get seeded by the camera on its first rolling frame
// If the player has just switched to riding a custom orientation ride then set their
// x and y rots local to the vehicle rots(if the controller wasn't active and will now be active)
// This ensures that on transition your camera stays in the same spot
if (!vehicleController.isActive() && shouldUseCustomOrientation) {
// Set local to the vehicle x and yrot so
var playerRotater = (RidePassenger)driver;
playerRotater.cobblemon$setRideXRot(Mth.wrapDegrees(driver.getXRot() - vehicle.getXRot()));
playerRotater.cobblemon$setRideYRot(Mth.wrapDegrees(driver.getYRot() - vehicle.getYRot()));
}
vehicleController.setActive(shouldUseCustomOrientation);
}
@@ -11,10 +11,6 @@ package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.riding.behaviour.RideSteerInput;
import com.cobblemon.mod.common.api.riding.camera.RideLookInput;
import com.cobblemon.mod.common.api.riding.camera.RideLookSettings;
import com.cobblemon.mod.common.api.riding.camera.RideLookTake;
import com.cobblemon.mod.common.client.CobblemonClient;
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding;
import com.cobblemon.mod.common.client.keybind.keybinds.RidingFreelookBinding;
@@ -24,12 +20,13 @@ import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.Blaze3D;
import kotlin.Unit;
import kotlin.Pair;
import net.minecraft.client.Minecraft;
import net.minecraft.client.MouseHandler;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.util.Mth;
import net.minecraft.util.SmoothDouble;
import net.minecraft.world.phys.Vec3;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -45,11 +42,6 @@ import static net.minecraft.util.Mth.lerp;
@Mixin(MouseHandler.class)
public abstract class MouseHandlerMixin {
// Entity.turn scales mouse movement to degrees by this
@Unique private static final float COBBLEMON$VANILLA_DEGREES_PER_MOUSE_UNIT = 0.15F;
// Share of the look offset closed per second as it drifts back to centre. Raise to recentre faster
@Unique private static final float COBBLEMON$LOOK_RECENTER_RATE = 5.0F;
@Shadow private double accumulatedScrollY;
@Shadow @Final private Minecraft minecraft;
@@ -168,48 +160,74 @@ public abstract class MouseHandlerMixin {
}
var vehicleController = controllableVehicle.getOrientationController();
var pokemonVehicle = (PokemonEntity) vehicle;
var freeLooking = RidingFreelookBinding.INSTANCE.isDown();
Pair<Boolean, Boolean> mouseShouldModifyDriver = ((PokemonEntity) vehicle).ifRidingAvailableSupply(new Pair(false, false), (behaviour, settings, state) -> {
return behaviour.mouseModifiesDriverRotation(
settings,
state,
(PokemonEntity) vehicle
);
});
//-------------------------------------------------------------------------
// If looking around then modify the players orientation instead and zero the mouse deltas so the mouse doesn't
// affect the vehicle orientation when looking around.
// If the client is a passenger it will modify these rotations and return
//-------------------------------------------------------------------------
if (freeLooking || player.getControlledVehicle() == null) {
// Modify driver rotations
cobblemon$setDriverRotations(player, cursorDeltaX, cursorDeltaY);
// If the player/passenger is not the driver then return and ignore all vehicle rotation logic
if (player.getControlledVehicle() == null) return false;
// Set to zero so that vehicle rotations don't also get affected by movement.
cursorDeltaX = 0.0;
cursorDeltaY = 0.0;
} else if (mouseShouldModifyDriver.getFirst()) { // If not freelooking then check modification per mouse input dimension
cobblemon$setDriverRotations(player, cursorDeltaX, 0.0);
cursorDeltaX = 0.0;
} else if (mouseShouldModifyDriver.getSecond()) {
cobblemon$setDriverRotations(player, 0.0, cursorDeltaY);
cursorDeltaY = 0.0;
}
//-------------------------------------------------------------------------
// Smoothly return driver rotations to zero when needed
//-------------------------------------------------------------------------
var playerRotater = (RidePassenger)player;
float yawDelta = (float)cursorDeltaX * COBBLEMON$VANILLA_DEGREES_PER_MOUSE_UNIT;
float pitchDelta = (float)cursorDeltaY * COBBLEMON$VANILLA_DEGREES_PER_MOUSE_UNIT;
// Passengers only ever look around
if (player.getControlledVehicle() == null) {
cobblemon$addLook(player, yawDelta, pitchDelta);
return false;
if( !freeLooking && !mouseShouldModifyDriver.getFirst()){
playerRotater.cobblemon$setRideYRot(lerp(min(5.0f * (float)movementTime, 1.0f), Mth.wrapDegrees(playerRotater.cobblemon$getRideYRot()), 0.0f));
}
if( !freeLooking && !mouseShouldModifyDriver.getSecond()) {
playerRotater.cobblemon$setRideXRot(lerp(min(5.0f * (float)movementTime, 1.0f), Mth.wrapDegrees(playerRotater.cobblemon$getRideXRot()), 0.0f));
}
// The ride takes what it wants of the mouse to steer with and the rest moves the look. Freelook keeps it all for the look
var lookInput = new RideLookInput(yawDelta, pitchDelta, playerRotater.cobblemon$getRideYRot(), playerRotater.cobblemon$getRideXRot());
RideLookTake take = RidingFreelookBinding.INSTANCE.isDown() ? RideLookTake.NONE : pokemonVehicle.ifRidingAvailableSupply(RideLookTake.all(lookInput), (behaviour, settings, state) -> {
return behaviour.consumeLook(settings, state, pokemonVehicle, lookInput);
});
float lookYaw = yawDelta - take.getYaw();
float lookPitch = pitchDelta - take.getPitch();
if (lookYaw != 0.0f || lookPitch != 0.0f) {
cobblemon$addLook(player, lookYaw, lookPitch);
// Send mouse input to be interpreted into rotation deltas by the ride controller
Vec3 angVecMouse = cobblemon$getRideMouseRotation(cursorDeltaX, cursorDeltaY, movementTime);
// Perform Rotation using mouse influenced rotation deltas.
vehicleController.rotate(
(float) angVecMouse.x,
(float) angVecMouse.y,
(float) angVecMouse.z
);
// Gather and apply the current rotation deltas
var angRot = cobblemon$getAngularVelocity(movementTime);
// Apply smoothing if requested by the controller.
if(cobblemon$shouldUseAngVelSmoothing()) {
var yaw = yawSmoother.getNewDeltaValue(angRot.x * 0.5f, d);
var pitch = pitchSmoother.getNewDeltaValue(angRot.y * 0.5f, d);
var roll = rollSmoother.getNewDeltaValue(angRot.z * 0.5f, d);
vehicleController.rotate((float) yaw, (float) pitch, (float) roll);
}
else {
vehicleController.rotate((float) (angRot.x * 10 * d), (float) (angRot.y * 10 * d), (float) (angRot.z * 10 * d));
}
// Drift the look back to centre on the axes the ride steers with
float recenter = min(COBBLEMON$LOOK_RECENTER_RATE * (float)movementTime, 1.0f);
if (take.getRecenterYaw()) {
playerRotater.cobblemon$setRideYRot(lerp(recenter, Mth.wrapDegrees(playerRotater.cobblemon$getRideYRot()), 0.0f));
}
if (take.getRecenterPitch()) {
playerRotater.cobblemon$setRideXRot(lerp(recenter, Mth.wrapDegrees(playerRotater.cobblemon$getRideXRot()), 0.0f));
}
// Hand the ride its share back in mouse units since that's what every ride's steering is tuned for
double rideDeltaX = yawDelta == 0.0f ? 0.0 : cursorDeltaX * (take.getYaw() / yawDelta);
double rideDeltaY = pitchDelta == 0.0f ? 0.0 : cursorDeltaY * (take.getPitch() / pitchDelta);
var steerInput = cobblemon$getSteerInput(vehicleController, rideDeltaX, rideDeltaY, movementTime);
pokemonVehicle.ifRidingAvailable((behaviour, settings, state) -> {
behaviour.steer(settings, state, pokemonVehicle, player, steerInput);
return Unit.INSTANCE;
});
// The camera sets the player's rotation from the look every frame so vanilla turn would only get overwritten
return false;
return returnValue;
}
@Inject(method = "handleAccumulatedMovement", at = @At("HEAD"))
@@ -230,9 +248,28 @@ public abstract class MouseHandlerMixin {
this.turnPlayer(cobblemon$timeDelta);
}
/** The ride's share of the mouse put through the riding sensitivity, invert and swap options. */
@Unique
private RideSteerInput cobblemon$getSteerInput(OrientationController controller, double mouseX, double mouseY, double deltaTime) {
private Vec3 cobblemon$getAngularVelocity(double deltaTime) {
var player = minecraft.player;
if (player == null) return Vec3.ZERO;
if (!(player instanceof OrientationControllable)) return Vec3.ZERO;
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return Vec3.ZERO;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return Vec3.ZERO;
return pokemonEntity.ifRidingAvailableSupply(Vec3.ZERO, (behaviour, settings, state) -> {
return behaviour.angRollVel(settings, state, pokemonEntity, player, deltaTime);
});
}
@Unique
private Vec3 cobblemon$getRideMouseRotation(double mouseX, double mouseY, double deltaTime) {
var player = minecraft.player;
if (player == null) return Vec3.ZERO;
var vehicle = player.getVehicle();
if (vehicle == null) return Vec3.ZERO;
if (!(vehicle instanceof PokemonEntity pokemonEntity)) return Vec3.ZERO;
var invertYaw = Cobblemon.config.getInvertYaw();
var invertPitch = Cobblemon.config.getInvertPitch();
@@ -245,18 +282,21 @@ public abstract class MouseHandlerMixin {
var pitch = (swapXAndY ? xValue : yValue) * (invertPitch? -1.0f : 1.0f);
return new RideSteerInput(
controller,
yaw,
pitch,
xMouseSmoother,
yMouseSmoother,
cobblemon$getRidingSensitivity(),
deltaTime,
yawSmoother,
pitchSmoother,
rollSmoother
);
var sensitivity = cobblemon$getRidingSensitivity();
return pokemonEntity.ifRidingAvailableSupply(Vec3.ZERO, (behaviour, settings, state) -> {
return behaviour.rotationOnMouseXY(
settings,
state,
pokemonEntity,
player,
pitch,
yaw,
yMouseSmoother,
xMouseSmoother,
sensitivity,
deltaTime
);
});
}
@Unique
@@ -265,33 +305,31 @@ public abstract class MouseHandlerMixin {
return Math.pow(sensitivity, 3);
}
/** Moves the ride look by degrees within the ride's look limits. Same job as vanilla turn but on the look instead of the player. */
@Unique
private void cobblemon$addLook(LocalPlayer player, float yawDelta, float pitchDelta) {
private void cobblemon$setDriverRotations(LocalPlayer player, double cursorDeltaX, double cursorDeltaY) {
// the vanilla turn() method redone so that it uses rideRotations.
float f = (float)cursorDeltaY * 0.15F; // Note: turn() uses xRot for pitch (up/down)
float g = (float)cursorDeltaX * 0.15F; // Note: turn() uses yRot for yaw (left/right)
var playerRotater = (RidePassenger)player;
var lookSettings = cobblemon$getLookSettings(player);
playerRotater.cobblemon$setRideXRot(cobblemon$softClamp(playerRotater.cobblemon$getRideXRot(), pitchDelta, lookSettings.getMaxPitch()));
playerRotater.cobblemon$setRideYRot(cobblemon$softClamp(playerRotater.cobblemon$getRideYRot(), yawDelta, lookSettings.getMaxYaw()));
playerRotater.cobblemon$setRideXRot(playerRotater.cobblemon$getRideXRot() + f);
playerRotater.cobblemon$setRideYRot(playerRotater.cobblemon$getRideYRot() + g);
playerRotater.cobblemon$setRideXRot(Mth.clamp(playerRotater.cobblemon$getRideXRot(), -89.0F, 89.0F));
playerRotater.cobblemon$setRideYRot(Mth.clamp(playerRotater.cobblemon$getRideYRot(), -105.0F, 105.0F));
if (player.getVehicle() != null) {
player.getVehicle().onPassengerTurned(player);
}
}
/**
* Moves an angle by delta while keeping it inside the limit. An angle already past the limit
* (the base turning under a low carry look can put it there) isn't snapped back, it just can't go further out.
*/
@Unique
private static float cobblemon$softClamp(float angle, float delta, float limit) {
float bound = Math.max(limit, Math.abs(angle));
return Mth.clamp(angle + delta, -bound, bound);
}
private boolean cobblemon$shouldUseAngVelSmoothing() {
var player = minecraft.player;
if (player == null) return true;
@Unique
private RideLookSettings cobblemon$getLookSettings(LocalPlayer player) {
if (!(player.getVehicle() instanceof PokemonEntity pokemonEntity)) return RideLookSettings.DEFAULT;
return pokemonEntity.ifRidingAvailableSupply(RideLookSettings.DEFAULT, (behaviour, settings, state) -> {
return behaviour.lookSettings(settings, state, pokemonEntity);
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return true;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return true;
return pokemonEntity.ifRidingAvailableSupply(true, (behaviour, settings, state) -> {
return behaviour.useAngVelSmoothing(settings, state, pokemonEntity);
});
}
@@ -9,25 +9,55 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.CobblemonRecipeCategories;
import com.llamalad7.mixinextras.expression.Definition;
import com.llamalad7.mixinextras.expression.Expression;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.ClientRecipeBook;
import net.minecraft.client.Minecraft;
import net.minecraft.client.RecipeBookCategories;
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
import net.minecraft.client.gui.screens.recipebook.RecipeBookPage;
import net.minecraft.client.gui.screens.recipebook.RecipeBookTabButton;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(RecipeBookComponent.class)
public class RecipeBookComponentMixin {
@Definition(id = "recipeBookCategories", local = @Local(type = RecipeBookCategories.class))
@Definition(id = "FURNACE_SEARCH", field = "Lnet/minecraft/client/RecipeBookCategories;FURNACE_SEARCH:Lnet/minecraft/client/RecipeBookCategories;")
@Expression("recipeBookCategories != FURNACE_SEARCH")
@ModifyExpressionValue(
method = "updateTabs",
at = @At(value = "MIXINEXTRAS:EXPRESSION")
)
private boolean notCookingPotSearch(boolean original, @Local RecipeBookCategories recipeBookCategories) {
return original && recipeBookCategories != CobblemonRecipeCategories.COOKING_POT_SEARCH.toVanillaCategory();
@Shadow private ClientRecipeBook book;
@Shadow protected Minecraft minecraft;
@Shadow private int width;
@Shadow private int xOffset;
@Shadow private int height;
@Shadow @Final private RecipeBookPage recipeBookPage;
@Inject(method = "updateTabs", at = @At("HEAD"), cancellable = true)
private void customUpdateTabs(CallbackInfo ci) {
RecipeBookComponent instance = (RecipeBookComponent) (Object) this;
int i = (width - 147) / 2 - xOffset - 30;
int j = (height - 166) / 2 + 3;
int k = 27;
int l = 0;
for (RecipeBookTabButton recipeBookTabButton : instance.tabButtons) {
RecipeBookCategories recipeBookCategories = recipeBookTabButton.getCategory();
if (recipeBookCategories != RecipeBookCategories.CRAFTING_SEARCH && recipeBookCategories != RecipeBookCategories.FURNACE_SEARCH && recipeBookCategories != CobblemonRecipeCategories.COOKING_POT_SEARCH.toVanillaCategory()) {
if (recipeBookTabButton.updateVisibility(book)) {
recipeBookTabButton.setPosition(i, j + 27 * l++);
recipeBookTabButton.startAnimation(minecraft);
}
} else {
recipeBookTabButton.visible = true;
recipeBookTabButton.setPosition(i, j + k * l++);
}
}
ci.cancel();
}
}
@@ -38,9 +38,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(Entity.class)
public abstract class EntityMixin {
// How far the clientsent ride eye can sit from the players
@Unique private static final double COBBLEMON$MAX_RIDE_EYE_OFFSET = 3.0;
@Shadow @Nullable public abstract Entity getVehicle();
@Inject(method = "updateInWaterStateAndDoWaterCurrentPushing", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/Entity;wasTouchingWater:Z", ordinal = 1), cancellable = true)
@@ -94,9 +91,6 @@ public abstract class EntityMixin {
if (vehicleController == null) return;
Vec3 customEyePos = ridePassenger.cobblemon$getRideEyePos();
Vec3 vanillaEyePos = new Vec3(player.getX(), player.getEyeY(), player.getZ());
double maxSqr = COBBLEMON$MAX_RIDE_EYE_OFFSET * COBBLEMON$MAX_RIDE_EYE_OFFSET;
if (customEyePos == null || !(customEyePos.distanceToSqr(vanillaEyePos) <= maxSqr)) return;
cir.setReturnValue(customEyePos);
}
@@ -20,7 +20,7 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
// There appears to be a data driven way to do this now, however I am not sure how it works since there is no wiki or anything so yolo
//This is a yucky hack but better than what we were doing before
@Mixin(BlockVariantSets.class)
public abstract class AdornRegisterInvoker {
@Final
@@ -8,12 +8,8 @@ object CobblemonBuildDetails {
const val VERSION = "{{ version }}"
const val SNAPSHOT = {{ isSnapshot }}
// Read at runtime rather than templated in, so a new commit doesn't invalidate compiled code.
private val git = java.util.Properties().apply {
CobblemonBuildDetails::class.java.getResourceAsStream("/cobblemon-git.properties")?.use(::load)
}
@JvmField val GIT_COMMIT: String = git.getProperty("commit", "Not Specified")
@JvmField val BRANCH: String = git.getProperty("branch", "Not Specified")
const val GIT_COMMIT = "{{ gitCommit | default("Not Specified") }}"
const val BRANCH = "{{ branch | default("Not Specified") }}"
fun smallCommitHash(): String {
return this.GIT_COMMIT.substring(0, 7)
@@ -260,9 +260,6 @@ object Cobblemon {
RequestManager.onLogoff(it.player)
serverPlayerStructs.remove(it.player.uuid)
}
PlatformEvents.PLAYER_RESPAWN.subscribe {
serverPlayerStructs.remove(it.player.uuid)
}
PlatformEvents.PLAYER_DEATH.subscribe {
PCLinkManager.removeLink(it.player.uuid)
battleRegistry.getBattleByParticipatingPlayer(it.player)?.stop()
@@ -109,21 +109,4 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
"poke_snack",
BlockEntityType.Builder.of(::PokeSnackBlockEntity, CobblemonBlocks.POKE_SNACK, CobblemonBlocks.POKE_CAKE).build(null)
)
@JvmField
val TERRAIN_PLANT: BlockEntityType<TerrainPlantBlockEntity> = this.create(
"terrain_plant",
BlockEntityType.Builder.of(::TerrainPlantBlockEntity,
CobblemonBlocks.VOLTACONIA,
CobblemonBlocks.LUCIBELL,
CobblemonBlocks.VERDROOT
).build(null)
)
@JvmField
val MISTLEAF_FLOWER: BlockEntityType<MistleafFlowerBlockEntity> = this.create(
"mistleaf",
BlockEntityType.Builder.of(::MistleafFlowerBlockEntity,
CobblemonBlocks.MISTLEAF
).build(null)
)
}
@@ -16,7 +16,6 @@ import com.cobblemon.mod.common.block.MintBlock.MintType
import com.cobblemon.mod.common.block.campfirepot.CampfireBlock
import com.cobblemon.mod.common.block.campfirepot.CampfirePotBlock
import com.cobblemon.mod.common.block.chest.GildedChestBlock
import com.cobblemon.mod.common.block.decor.*
import com.cobblemon.mod.common.block.general.BaleBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
@@ -27,7 +26,6 @@ import com.cobblemon.mod.common.block.sign.CobblemonSignBlock
import com.cobblemon.mod.common.block.sign.CobblemonWallHangingSignBlock
import com.cobblemon.mod.common.block.sign.CobblemonWallSignBlock
import com.cobblemon.mod.common.block.tmmachine.TMMachineBlock
import com.cobblemon.mod.common.block.general.MintLitterBlock
import com.cobblemon.mod.common.mixin.invoker.BlocksInvoker
import com.cobblemon.mod.common.mixin.invoker.DoorBlockInvoker
import com.cobblemon.mod.common.mixin.invoker.FireBlockInvoker
@@ -623,131 +621,37 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val WEAKNESS_POLICY = create("weakness_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_PAPER_LARGE_SOUNDS).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
@JvmField
val RING_TARGET = create("ring_target", RingTargetBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).mapColor(MapColor.NONE).noOcclusion().strength(0.125f)))
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val MAGNET = create("magnet", MagnetBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion().strength(0.075f)))
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val GALARICA_WREATH = create("galarica_wreath", GalaricaWreathBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noOcclusion().noCollission()))
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val FLAME_ORB = create("flame_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{15}))
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val TOXIC_ORB = create("toxic_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val LIFE_ORB = create("life_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{7}))
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val LIGHT_BALL = create("light_ball", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{13}))
@JvmField
val SMOKE_BALL = create("smoke_ball", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val KINGS_ROCK = create("kings_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.CENTERED_6x))
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val GALARICA_CUFF = create("galarica_cuff", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noCollission().noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_10x4))
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val UNREMARKABLE_TEACUP = create("unremarkable_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val MASTERPIECE_TEACUP = create("masterpiece_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
@JvmField
val CHIPPED_POT = create("chipped_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
@JvmField
val CRACKED_POT = create("cracked_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
@JvmField
val ELECTIRIZER = create("electirizer", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.IZER))
@JvmField
val MAGMARIZER = create("magmarizer", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.IZER))
@JvmField
val SACHET = create("sachet", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val METAL_POWDER = create("metal_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val QUICK_POWDER = create("quick_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val BRIGHT_POWDER = create("bright_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion().lightLevel{3}, DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val SOFT_SAND = create("soft_sand", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val NEVER_MELT_ICE = create("never_melt_ice", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().friction(0.989F), DecorativeItemBlock.DecorativeItemBlockType.NEVERMELT))
@JvmField
val SCOPE_LENS = create("scope_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.SCOPE))
@JvmField
val WIDE_LENS = create("wide_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.WIDE))
@JvmField
val ZOOM_LENS = create("zoom_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.ZOOM))
@JvmField
val ICY_ROCK = create("icy_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
@JvmField
val HEAT_ROCK = create("heat_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
@JvmField
val SMOOTH_ROCK = create("smooth_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
@JvmField
val DAMP_ROCK = create("damp_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x6))
@JvmField
val DESTINY_KNOT = create("destiny_knot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.KNOT))
@JvmField
val LUMINOUS_MOSS = create("luminous_moss", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.MOSS).mapColor(MapColor.NONE).noOcclusion().lightLevel{5}, DecorativeItemBlock.DecorativeItemBlockType.CENTERED_8x))
@JvmField
val METRONOME = create("metronome", ActivatableDecorationBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion(), ActivatableDecorationBlock.ActivatableDecorationBlockType.METRONOME))
@JvmField
val EJECT_BUTTON = create("eject_button", EjectButtonBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().strength(0.125f), 40))
@JvmField
val LOADED_DICE = create("loaded_dice", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_DICE_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.DICE))
@JvmField
val IRON_BALL = create("iron_ball", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.IRON_BALL))
@JvmField
val METAL_COAT = create("metal_coat", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.METAL_COAT))
@JvmField
val UPGRADE = create("upgrade", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.UPGRADE))
@JvmField
val DUBIOUS_DISC = create("dubious_disc", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.UPGRADE))
@JvmField
val THROAT_SPRAY = create("throat_spray", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.THROAT_SPRAY))
@JvmField
val TERRAIN_EXTENDER = create("terrain_extender", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_METAL_CAN_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.EXTENDER))
@JvmField
val MOOMOO_MILK = create("moomoo_milk", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_JAR_GLASS_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.GLASS_BOTTLE))
@JvmField
val MEDICINAL_BREW = create("medicinal_brew", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_JAR_GLASS_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.GLASS_BOTTLE))
@JvmField
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
@JvmField
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
@JvmField
val DIRE_HIT = create("dire_hit", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@@ -766,23 +670,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val X_SPEED = create("x_${Stats.SPEED.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val HP_UP = create("hp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val PROTEIN = create("protein", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val IRON = create("iron", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val CARBOS = create("carbos", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val CALCIUM = create("calcium", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val ZINC = create("zinc", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val PP_UP = create("pp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val PP_MAX = create("pp_max", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val MONITOR = create("monitor", MonitorBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).lightLevel { if (it.getValue(MonitorBlock.SCREEN) != MonitorBlock.MonitorScreen.OFF) 13 else 0 }))
@JvmField
@@ -798,7 +685,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val PC = create("pc", PCBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F).noOcclusion().lightLevel { if ((it.getValue(PCBlock.ON) as Boolean) && (it.getValue(PCBlock.PART) == PCBlock.PCPart.TOP)) 13 else 0 }))
@JvmField
val TM_MACHINE = create(
"tm_machine",
TMMachineBlock(
@@ -827,7 +713,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val PASTURE = create("pasture", PastureBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BROWN).sound(SoundType.WOOD).strength(2F).noOcclusion().pushReaction(PushReaction.BLOCK).lightLevel { if ((it.getValue(PastureBlock.ON) as Boolean) && (it.getValue(PastureBlock.PART) == PastureBlock.PasturePart.TOP)) 13 else 0 }))
@JvmField
val RED_MINT = create("red_mint", MintBlock(MintType.RED, BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_RED).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MINT_SOUNDS).pushReaction(PushReaction.DESTROY)))
@JvmField
val BLUE_MINT = create("blue_mint", MintBlock(MintType.BLUE, BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BLUE).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MINT_SOUNDS).pushReaction(PushReaction.DESTROY)))
@@ -845,68 +731,29 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val PEP_UP_FLOWER = this.create("pep_up_flower", FlowerBlock(MobEffects.LEVITATION, 10F, BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().instabreak().sound(SoundType.GRASS).offsetType(BlockBehaviour.OffsetType.XZ).pushReaction(PushReaction.DESTROY)))
@JvmField
val POTTED_PEP_UP_FLOWER = this.create("potted_pep_up_flower", BlocksInvoker.createFlowerPotBlock(PEP_UP_FLOWER))
@JvmField
val HEARTY_GRAINS = this.create("hearty_grains", HeartyGrainsBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.PLANT).noCollission().randomTicks().instabreak()))
@JvmField
val GALARICA_NUT_BUSH = this.create("galarica_nut_bush", NutBushBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).randomTicks().noCollission().sound(SoundType.SWEET_BERRY_BUSH).pushReaction(PushReaction.DESTROY)))
@JvmField
val RED_MINT_LEAF_LITTER = this.mintLitter("red_mint_leaf_litter", MapColor.COLOR_RED)
val POTTED_RED_APRICORN_SAPLING = this.create("potted_red_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(RED_APRICORN_SAPLING))
@JvmField
val POTTED_RED_MINT = this.flowerPot("potted_red_mint", RED_MINT_LEAF_LITTER)
val POTTED_YELLOW_APRICORN_SAPLING = this.create("potted_yellow_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(YELLOW_APRICORN_SAPLING))
@JvmField
val BLUE_MINT_LEAF_LITTER = this.mintLitter("blue_mint_leaf_litter", MapColor.COLOR_BLUE)
val POTTED_GREEN_APRICORN_SAPLING = this.create("potted_green_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(GREEN_APRICORN_SAPLING))
@JvmField
val POTTED_BLUE_MINT = this.flowerPot("potted_blue_mint", BLUE_MINT_LEAF_LITTER)
val POTTED_BLUE_APRICORN_SAPLING = this.create("potted_blue_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(BLUE_APRICORN_SAPLING))
@JvmField
val CYAN_MINT_LEAF_LITTER = this.mintLitter("cyan_mint_leaf_litter", MapColor.COLOR_CYAN)
val POTTED_PINK_APRICORN_SAPLING = this.create("potted_pink_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(PINK_APRICORN_SAPLING))
@JvmField
val POTTED_CYAN_MINT = this.flowerPot("potted_cyan_mint", CYAN_MINT_LEAF_LITTER)
val POTTED_BLACK_APRICORN_SAPLING = this.create("potted_black_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(BLACK_APRICORN_SAPLING))
@JvmField
val PINK_MINT_LEAF_LITTER = this.mintLitter("pink_mint_leaf_litter", MapColor.COLOR_PINK)
val POTTED_WHITE_APRICORN_SAPLING = this.create("potted_white_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(WHITE_APRICORN_SAPLING))
@JvmField
val POTTED_PINK_MINT = this.flowerPot("potted_pink_mint", PINK_MINT_LEAF_LITTER)
@JvmField
val GREEN_MINT_LEAF_LITTER = this.mintLitter("green_mint_leaf_litter", MapColor.COLOR_GREEN)
@JvmField
val POTTED_GREEN_MINT = this.flowerPot("potted_green_mint", GREEN_MINT_LEAF_LITTER)
@JvmField
val WHITE_MINT_LEAF_LITTER = this.mintLitter("white_mint_leaf_litter", MapColor.SNOW)
@JvmField
val POTTED_WHITE_MINT = this.flowerPot("potted_white_mint", WHITE_MINT_LEAF_LITTER)
@JvmField
val LOBAILA = this.create("lobaila", LobailaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().instabreak().sound(SoundType.GRASS).offsetType(BlockBehaviour.OffsetType.XZ).ignitedByLava().pushReaction(PushReaction.DESTROY)))
@JvmField
val POMOHANA = this.create("pomohana", PomohanaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().instabreak().sound(SoundType.GRASS).ignitedByLava().pushReaction(PushReaction.DESTROY)))
@JvmField
val PAUVILLEA = this.create("pauvillea",PauvilleaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY)))
@JvmField
val WISTENSU = this.create("wistensu", WistensuFlowerHeadBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY).lightLevel{11}))
@JvmField
val WISTENSU_VINE = this.create("wistensu_vine", WistensuFlowerVineBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY)))
@JvmField
val ELECTRIC_SEED = this.terrainSeed("electric_seed", TerrainSeedBlock.TerrainSeedType.ELECTRIC)
@JvmField
val PSYCHIC_SEED = this.terrainSeed("psychic_seed", TerrainSeedBlock.TerrainSeedType.PSYCHIC)
@JvmField
val MISTY_SEED = this.terrainSeed("misty_seed", TerrainSeedBlock.TerrainSeedType.MISTY)
@JvmField
val GRASSY_SEED = this.terrainSeed("grassy_seed", TerrainSeedBlock.TerrainSeedType.GRASSY)
@JvmField
val VOLTACONIA = this.terrainPlant("voltaconia") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 8 else 0 }
@JvmField
val VERDROOT = this.terrainPlant("verdroot") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 5 else 0 }
@JvmField
val LUCIBELL = this.terrainPlant("lucibell") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 8 else 0 }
@JvmField
val MISTLEAF = this.create(
"mistleaf",
MistleafFlowerBlock {
if ((it.getValue(MistleafFlowerBlock.GLOWING) >= 1)) it.getValue(MistleafFlowerBlock.GLOWING) + 2 else 0
}
)
val POTTED_SACCHARINE_SAPLING = this.create("potted_saccharine_sapling", BlocksInvoker.createFlowerPotBlock(SACCHARINE_SAPLING))
@JvmField
val HEARTY_GRAIN_BALE = this.create("hearty_grain_bale", BaleBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).instrument(NoteBlockInstrument.BANJO).strength(0.5F).sound(CobblemonSounds.HEARTY_GRAIN_BALE_SOUNDS)))
@@ -1084,165 +931,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val YACHE_BERRY = this.berryBlock("yache")
//val BINDING_SOIL = this.create("binding_soil", BindingSoilBlock(BlockBehaviour.Properties.of()))
@JvmField
val POTTED_AGUAV_BERRY = this.flowerPot("potted_aguav_berry", AGUAV_BERRY)
@JvmField
val POTTED_APICOT_BERRY = this.flowerPot("potted_apicot_berry", APICOT_BERRY)
@JvmField
val POTTED_ASPEAR_BERRY = this.flowerPot("potted_aspear_berry", ASPEAR_BERRY)
@JvmField
val POTTED_BABIRI_BERRY = this.flowerPot("potted_babiri_berry", BABIRI_BERRY)
@JvmField
val POTTED_BELUE_BERRY = this.flowerPot("potted_belue_berry", BELUE_BERRY)
@JvmField
val POTTED_BLUK_BERRY = this.flowerPot("potted_bluk_berry", BLUK_BERRY)
@JvmField
val POTTED_CHARTI_BERRY = this.flowerPot("potted_charti_berry", CHARTI_BERRY)
@JvmField
val POTTED_CHERI_BERRY = this.flowerPot("potted_cheri_berry", CHERI_BERRY)
@JvmField
val POTTED_CHESTO_BERRY = this.flowerPot("potted_chesto_berry", CHESTO_BERRY)
@JvmField
val POTTED_CHILAN_BERRY = this.flowerPot("potted_chilan_berry", CHILAN_BERRY)
@JvmField
val POTTED_CHOPLE_BERRY = this.flowerPot("potted_chople_berry", CHOPLE_BERRY)
@JvmField
val POTTED_COBA_BERRY = this.flowerPot("potted_coba_berry", COBA_BERRY)
@JvmField
val POTTED_COLBUR_BERRY = this.flowerPot("potted_colbur_berry", COLBUR_BERRY)
@JvmField
val POTTED_CORNN_BERRY = this.flowerPot("potted_cornn_berry", CORNN_BERRY)
@JvmField
val POTTED_CUSTAP_BERRY = this.flowerPot("potted_custap_berry", CUSTAP_BERRY)
@JvmField
val POTTED_DURIN_BERRY = this.flowerPot("potted_durin_berry", DURIN_BERRY)
@JvmField
val POTTED_EGGANT_BERRY = this.flowerPot("potted_eggant_berry", EGGANT_BERRY)
@JvmField
val POTTED_ENIGMA_BERRY = this.flowerPot("potted_enigma_berry", ENIGMA_BERRY)
@JvmField
val POTTED_FIGY_BERRY = this.flowerPot("potted_figy_berry", FIGY_BERRY)
@JvmField
val POTTED_GANLON_BERRY = this.flowerPot("potted_ganlon_berry", GANLON_BERRY)
@JvmField
val POTTED_GREPA_BERRY = this.flowerPot("potted_grepa_berry", GREPA_BERRY)
@JvmField
val POTTED_HABAN_BERRY = this.flowerPot("potted_haban_berry", HABAN_BERRY)
@JvmField
val POTTED_HONDEW_BERRY = this.flowerPot("potted_hondew_berry", HONDEW_BERRY)
@JvmField
val POTTED_HOPO_BERRY = this.flowerPot("potted_hopo_berry", HOPO_BERRY)
@JvmField
val POTTED_IAPAPA_BERRY = this.flowerPot("potted_iapapa_berry", IAPAPA_BERRY)
@JvmField
val POTTED_JABOCA_BERRY = this.flowerPot("potted_jaboca_berry", JABOCA_BERRY)
@JvmField
val POTTED_KASIB_BERRY = this.flowerPot("potted_kasib_berry", KASIB_BERRY)
@JvmField
val POTTED_KEBIA_BERRY = this.flowerPot("potted_kebia_berry", KEBIA_BERRY)
@JvmField
val POTTED_KEE_BERRY = this.flowerPot("potted_kee_berry", KEE_BERRY)
@JvmField
val POTTED_KELPSY_BERRY = this.flowerPot("potted_kelpsy_berry", KELPSY_BERRY)
@JvmField
val POTTED_LANSAT_BERRY = this.flowerPot("potted_lansat_berry", LANSAT_BERRY)
@JvmField
val POTTED_LEPPA_BERRY = this.flowerPot("potted_leppa_berry", LEPPA_BERRY)
@JvmField
val POTTED_LIECHI_BERRY = this.flowerPot("potted_liechi_berry", LIECHI_BERRY)
@JvmField
val POTTED_LUM_BERRY = this.flowerPot("potted_lum_berry", LUM_BERRY)
@JvmField
val POTTED_MAGO_BERRY = this.flowerPot("potted_mago_berry", MAGO_BERRY)
@JvmField
val POTTED_MAGOST_BERRY = this.flowerPot("potted_magost_berry", MAGOST_BERRY)
@JvmField
val POTTED_MARANGA_BERRY = this.flowerPot("potted_maranga_berry", MARANGA_BERRY)
@JvmField
val POTTED_MICLE_BERRY = this.flowerPot("potted_micle_berry", MICLE_BERRY)
@JvmField
val POTTED_NANAB_BERRY = this.flowerPot("potted_nanab_berry", NANAB_BERRY)
@JvmField
val POTTED_NOMEL_BERRY = this.flowerPot("potted_nomel_berry", NOMEL_BERRY)
@JvmField
val POTTED_OCCA_BERRY = this.flowerPot("potted_occa_berry", OCCA_BERRY)
@JvmField
val POTTED_ORAN_BERRY = this.flowerPot("potted_oran_berry", ORAN_BERRY)
@JvmField
val POTTED_PAMTRE_BERRY = this.flowerPot("potted_pamtre_berry", PAMTRE_BERRY)
@JvmField
val POTTED_PASSHO_BERRY = this.flowerPot("potted_passho_berry", PASSHO_BERRY)
@JvmField
val POTTED_PAYAPA_BERRY = this.flowerPot("potted_payapa_berry", PAYAPA_BERRY)
@JvmField
val POTTED_PECHA_BERRY = this.flowerPot("potted_pecha_berry", PECHA_BERRY)
@JvmField
val POTTED_PERSIM_BERRY = this.flowerPot("potted_persim_berry", PERSIM_BERRY)
@JvmField
val POTTED_PETAYA_BERRY = this.flowerPot("potted_petaya_berry", PETAYA_BERRY)
@JvmField
val POTTED_PINAP_BERRY = this.flowerPot("potted_pinap_berry", PINAP_BERRY)
@JvmField
val POTTED_POMEG_BERRY = this.flowerPot("potted_pomeg_berry", POMEG_BERRY)
@JvmField
val POTTED_QUALOT_BERRY = this.flowerPot("potted_qualot_berry", QUALOT_BERRY)
@JvmField
val POTTED_RABUTA_BERRY = this.flowerPot("potted_rabuta_berry", RABUTA_BERRY)
@JvmField
val POTTED_RAWST_BERRY = this.flowerPot("potted_rawst_berry", RAWST_BERRY)
@JvmField
val POTTED_RAZZ_BERRY = this.flowerPot("potted_razz_berry", RAZZ_BERRY)
@JvmField
val POTTED_RINDO_BERRY = this.flowerPot("potted_rindo_berry", RINDO_BERRY)
@JvmField
val POTTED_ROSELI_BERRY = this.flowerPot("potted_roseli_berry", ROSELI_BERRY)
@JvmField
val POTTED_ROWAP_BERRY = this.flowerPot("potted_rowap_berry", ROWAP_BERRY)
@JvmField
val POTTED_SALAC_BERRY = this.flowerPot("potted_salac_berry", SALAC_BERRY)
@JvmField
val POTTED_SHUCA_BERRY = this.flowerPot("potted_shuca_berry", SHUCA_BERRY)
@JvmField
val POTTED_SITRUS_BERRY = this.flowerPot("potted_sitrus_berry", SITRUS_BERRY)
@JvmField
val POTTED_SPELON_BERRY = this.flowerPot("potted_spelon_berry", SPELON_BERRY)
@JvmField
val POTTED_STARF_BERRY = this.flowerPot("potted_starf_berry", STARF_BERRY)
@JvmField
val POTTED_TAMATO_BERRY = this.flowerPot("potted_tamato_berry", TAMATO_BERRY)
@JvmField
val POTTED_TANGA_BERRY = this.flowerPot("potted_tanga_berry", TANGA_BERRY)
@JvmField
val POTTED_TOUGA_BERRY = this.flowerPot("potted_touga_berry", TOUGA_BERRY)
@JvmField
val POTTED_WACAN_BERRY = this.flowerPot("potted_wacan_berry", WACAN_BERRY)
@JvmField
val POTTED_WATMEL_BERRY = this.flowerPot("potted_watmel_berry", WATMEL_BERRY)
@JvmField
val POTTED_WEPEAR_BERRY = this.flowerPot("potted_wepear_berry", WEPEAR_BERRY)
@JvmField
val POTTED_WIKI_BERRY = this.flowerPot("potted_wiki_berry", WIKI_BERRY)
@JvmField
val POTTED_YACHE_BERRY = this.flowerPot("potted_yache_berry", YACHE_BERRY)
@JvmField
val POTTED_PEP_UP_FLOWER = this.flowerPot("potted_pep_up_flower", PEP_UP_FLOWER)
@JvmField
val POTTED_RED_APRICORN_SAPLING = this.flowerPot("potted_red_apricorn_sapling", RED_APRICORN_SAPLING)
@JvmField
val POTTED_YELLOW_APRICORN_SAPLING = this.flowerPot("potted_yellow_apricorn_sapling", YELLOW_APRICORN_SAPLING)
@JvmField
val POTTED_GREEN_APRICORN_SAPLING = this.flowerPot("potted_green_apricorn_sapling", GREEN_APRICORN_SAPLING)
@JvmField
val POTTED_BLUE_APRICORN_SAPLING = this.flowerPot("potted_blue_apricorn_sapling", BLUE_APRICORN_SAPLING)
@JvmField
val POTTED_PINK_APRICORN_SAPLING = this.flowerPot("potted_pink_apricorn_sapling", PINK_APRICORN_SAPLING)
@JvmField
val POTTED_BLACK_APRICORN_SAPLING = this.flowerPot("potted_black_apricorn_sapling", BLACK_APRICORN_SAPLING)
@JvmField
val POTTED_WHITE_APRICORN_SAPLING = this.flowerPot("potted_white_apricorn_sapling", WHITE_APRICORN_SAPLING)
@JvmField
val POTTED_SACCHARINE_SAPLING = this.flowerPot("potted_saccharine_sapling", SACCHARINE_SAPLING)
init {
/**
* Makes all blocks in array flammable by adding them to FireBlock's flammableRegistry.
@@ -1344,57 +1032,4 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
}
fun typeGemClusters() = typeGemClusters.toMap()
private fun flowerPot(name: String, flower: Block) = this.create(
name,
FlowerPotBlock(
flower,
BlockBehaviour.Properties.of()
.instabreak()
.noOcclusion()
.pushReaction(PushReaction.DESTROY)
)
)
private fun mintLitter(name: String, color: MapColor) = this.create(
name,
MintLitterBlock(
BlockBehaviour.Properties.of()
.sound(CobblemonSounds.MINT_SOUNDS)
.mapColor(color)
.noOcclusion()
.noCollission()
.pushReaction(PushReaction.DESTROY)
)
)
private fun terrainSeed(name: String, type: TerrainSeedBlock.TerrainSeedType) = this.create(
name,
TerrainSeedBlock(
type,
BlockBehaviour.Properties.of()
.pushReaction(PushReaction.DESTROY)
.ignitedByLava()
.mapColor(MapColor.PLANT)
.offsetType(BlockBehaviour.OffsetType.XZ)
.noCollission()
.randomTicks()
.instabreak()
)
)
private fun terrainPlant(name: String, lightLevel: (BlockState) -> Int) = this.create(
name,
TerrainPlantBlock(
BlockBehaviour.Properties.of()
.mapColor(MapColor.PLANT)
.noCollission()
.instabreak()
.sound(SoundType.GRASS)
.offsetType(BlockBehaviour.OffsetType.XZ)
.ignitedByLava()
.pushReaction(PushReaction.DESTROY)
.lightLevel(lightLevel)
)
)
}
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.api.net.NetworkPacket
import com.mojang.brigadier.arguments.ArgumentType
import kotlin.reflect.KClass
import net.minecraft.commands.synchronization.ArgumentTypeInfo
import net.minecraft.core.HolderLookup
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.MinecraftServer
@@ -169,16 +168,12 @@ interface CobblemonImplementation {
* TODO
*
* @param identifier
* @param reloaderFactory
* @param reloader
* @param type
* @param dependencies
*/
fun registerResourceReloader(
identifier: ResourceLocation,
type: PackType,
dependencies: Collection<ResourceLocation>,
reloaderFactory: (HolderLookup.Provider) -> PreparableReloadListener
)
fun registerResourceReloader(identifier: ResourceLocation, reloader: PreparableReloadListener, type: PackType, dependencies: Collection<ResourceLocation>)
/**
* TODO
*
@@ -472,18 +472,18 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
// Evolution items
@JvmField val LINK_CABLE = create("link_cable", LinkCableItem())
@JvmField val DRAGON_SCALE = noSettingsItem("dragon_scale")
@JvmField val METAL_COAT = itemNameBlockItem("metal_coat", CobblemonBlocks.METAL_COAT)
@JvmField val UPGRADE = itemNameBlockItem("upgrade", CobblemonBlocks.UPGRADE)
@JvmField val DUBIOUS_DISC = itemNameBlockItem("dubious_disc", CobblemonBlocks.DUBIOUS_DISC)
@JvmField val METAL_COAT = noSettingsItem("metal_coat")
@JvmField val UPGRADE = noSettingsItem("upgrade")
@JvmField val DUBIOUS_DISC = noSettingsItem("dubious_disc")
@JvmField val DEEP_SEA_SCALE = noSettingsItem("deep_sea_scale")
@JvmField val DEEP_SEA_TOOTH = noSettingsItem("deep_sea_tooth")
@JvmField val ELECTIRIZER = itemNameBlockItem("electirizer", CobblemonBlocks.ELECTIRIZER)
@JvmField val MAGMARIZER = itemNameBlockItem("magmarizer", CobblemonBlocks.MAGMARIZER)
@JvmField val ELECTIRIZER = noSettingsItem("electirizer")
@JvmField val MAGMARIZER = noSettingsItem("magmarizer")
@JvmField val OVAL_STONE = noSettingsItem("oval_stone")
@JvmField val PROTECTOR = noSettingsItem("protector")
@JvmField val REAPER_CLOTH = noSettingsItem("reaper_cloth")
@JvmField val PRISM_SCALE = noSettingsItem("prism_scale")
@JvmField val SACHET = itemNameBlockItem("sachet", CobblemonBlocks.SACHET)
@JvmField val SACHET = noSettingsItem("sachet")
@JvmField val WHIPPED_DREAM = create("whipped_dream", foodItem(8, 0.3f))
@JvmField val STRAWBERRY_SWEET = create("strawberry_sweet", foodItem(6, 0.125f))
@JvmField val LOVE_SWEET = create("love_sweet", foodItem(6, 0.125f))
@@ -492,20 +492,22 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val FLOWER_SWEET = create("flower_sweet", foodItem(6, 0.125f))
@JvmField val STAR_SWEET = create("star_sweet", foodItem(6, 0.125f))
@JvmField val RIBBON_SWEET = create("ribbon_sweet", foodItem(6, 0.125f))
@JvmField val CHIPPED_POT = itemNameBlockItem("chipped_pot", CobblemonBlocks.CHIPPED_POT)
@JvmField val CRACKED_POT = itemNameBlockItem("cracked_pot", CobblemonBlocks.CRACKED_POT)
@JvmField val MASTERPIECE_TEACUP = itemNameBlockItem("masterpiece_teacup", CobblemonBlocks.MASTERPIECE_TEACUP)
@JvmField val UNREMARKABLE_TEACUP = itemNameBlockItem("unremarkable_teacup", CobblemonBlocks.UNREMARKABLE_TEACUP)
@JvmField val CHIPPED_POT = noSettingsItem("chipped_pot")
@JvmField val CRACKED_POT = noSettingsItem("cracked_pot")
@JvmField val MASTERPIECE_TEACUP = noSettingsItem("masterpiece_teacup")
@JvmField val UNREMARKABLE_TEACUP = noSettingsItem("unremarkable_teacup")
@JvmField val SWEET_APPLE = compostableItem("sweet_apple", foodItem(4, 0.3f), 0.65f)
@JvmField val TART_APPLE = compostableItem("tart_apple", foodItem(4, 0.3f), 0.65f)
@JvmField val SYRUPY_APPLE = compostableItem("syrupy_apple", foodItem(4, 0.3f), 0.65f)
@JvmField val GALARICA_CUFF = itemNameBlockItem("galarica_cuff", CobblemonBlocks.GALARICA_CUFF)
@JvmField val GALARICA_CUFF = noSettingsItem("galarica_cuff")
@JvmField val GALARICA_WREATH = noSettingsItem("galarica_wreath")
@JvmField val BLACK_AUGURITE = noSettingsItem("black_augurite")
@JvmField val PEAT_BLOCK = noSettingsItem("peat_block")
@JvmField val RAZOR_CLAW = noSettingsItem("razor_claw")
@JvmField val RAZOR_FANG = noSettingsItem("razor_fang")
@JvmField val AUSPICIOUS_ARMOR = heldItem("auspicious_armor")
@JvmField val MALICIOUS_ARMOR = heldItem("malicious_armor")
@JvmField val SHELL_HELMET = heldItem("shell_helmet")
@JvmField val METAL_ALLOY = noSettingsItem("metal_alloy")
@JvmField val SCROLL_OF_DARKNESS = noSettingsItem("scroll_of_darkness")
@JvmField val SCROLL_OF_WATERS = noSettingsItem("scroll_of_waters")
@@ -649,17 +651,17 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val EXPERIENCE_CANDY_XL = candyItem("exp_candy_xl") { _, _ -> CandyItem.DEFAULT_XL_CANDY_YIELD }
@JvmField
val CALCIUM = create("calcium", VitaminItem(Stats.SPECIAL_ATTACK, CobblemonBlocks.CALCIUM))
val CALCIUM = create("calcium", VitaminItem(Stats.SPECIAL_ATTACK))
@JvmField
val CARBOS = create("carbos", VitaminItem(Stats.SPEED, CobblemonBlocks.CARBOS))
val CARBOS = create("carbos", VitaminItem(Stats.SPEED))
@JvmField
val HP_UP = create("hp_up", VitaminItem(Stats.HP, CobblemonBlocks.HP_UP))
val HP_UP = create("hp_up", VitaminItem(Stats.HP))
@JvmField
val IRON = create("iron", VitaminItem(Stats.DEFENCE, CobblemonBlocks.IRON))
val IRON = create("iron", VitaminItem(Stats.DEFENCE))
@JvmField
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK, CobblemonBlocks.PROTEIN))
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK))
@JvmField
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE, CobblemonBlocks.ZINC))
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE))
@JvmField
val HEALTH_MOCHI = create("health_mochi", MochiItem(Stats.HP))
@JvmField
@@ -711,7 +713,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val PEP_UP_FLOWER = compostableBlockItem("pep_up_flower", CobblemonBlocks.PEP_UP_FLOWER)
@JvmField
val MEDICINAL_BREW = itemNameBlockItem("medicinal_brew", CobblemonBlocks.MEDICINAL_BREW)
val MEDICINAL_BREW = noSettingsItem("medicinal_brew")
@JvmField
val REMEDY = compostableItem("remedy", RemedyItem(RemedyItem.NORMAL), 0.65f)
@JvmField
@@ -719,7 +721,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SUPERB_REMEDY = compostableItem("superb_remedy", RemedyItem(RemedyItem.SUPERB), 1f)
@JvmField
val MOOMOO_MILK = create("moomoo_milk", MoomooMilk(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16)), CobblemonBlocks.MOOMOO_MILK))
val MOOMOO_MILK = create("moomoo_milk", MoomooMilk(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16))))
@JvmField
val POTION = create("potion", PotionItem(PotionType.POTION, CobblemonBlocks.POTION))
@@ -741,34 +743,34 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val MAX_REVIVE = create("max_revive", ReviveItem(max = true))
@JvmField
val PP_UP = create("pp_up", PPUpItem(1, CobblemonBlocks.PP_UP))
val PP_UP = create("pp_up", PPUpItem(1))
@JvmField
val PP_MAX = create("pp_max", PPUpItem(3, CobblemonBlocks.PP_MAX))
val PP_MAX = create("pp_max", PPUpItem(3))
@JvmField
val RED_MINT_SEEDS = mintSeed("red", MintType.RED.getCropBlock())
@JvmField
val RED_MINT_LEAF = compostableItemNameBlockItem("red_mint_leaf", CobblemonBlocks.RED_MINT_LEAF_LITTER, 0.50f)
val RED_MINT_LEAF = mintLeaf("red", MintLeafItem(MintType.RED))
@JvmField
val BLUE_MINT_SEEDS = mintSeed("blue", MintType.BLUE.getCropBlock())
@JvmField
val BLUE_MINT_LEAF = compostableItemNameBlockItem("blue_mint_leaf", CobblemonBlocks.BLUE_MINT_LEAF_LITTER, 0.50f)
val BLUE_MINT_LEAF = mintLeaf("blue", MintLeafItem(MintType.BLUE))
@JvmField
val CYAN_MINT_SEEDS = mintSeed("cyan", MintType.CYAN.getCropBlock())
@JvmField
val CYAN_MINT_LEAF = compostableItemNameBlockItem("cyan_mint_leaf", CobblemonBlocks.CYAN_MINT_LEAF_LITTER, 0.50f)
val CYAN_MINT_LEAF = mintLeaf("cyan", MintLeafItem(MintType.CYAN))
@JvmField
val PINK_MINT_SEEDS = mintSeed("pink", MintType.PINK.getCropBlock())
@JvmField
val PINK_MINT_LEAF = compostableItemNameBlockItem("pink_mint_leaf", CobblemonBlocks.PINK_MINT_LEAF_LITTER, 0.50f)
val PINK_MINT_LEAF = mintLeaf("pink", MintLeafItem(MintType.PINK))
@JvmField
val GREEN_MINT_SEEDS = mintSeed("green", MintType.GREEN.getCropBlock())
@JvmField
val GREEN_MINT_LEAF = compostableItemNameBlockItem("green_mint_leaf", CobblemonBlocks.GREEN_MINT_LEAF_LITTER, 0.50f)
val GREEN_MINT_LEAF = mintLeaf("green", MintLeafItem(MintType.GREEN))
@JvmField
val WHITE_MINT_SEEDS = mintSeed("white", MintType.WHITE.getCropBlock())
@JvmField
val WHITE_MINT_LEAF = compostableItemNameBlockItem("white_mint_leaf", CobblemonBlocks.WHITE_MINT_LEAF_LITTER, 0.50f)
val WHITE_MINT_LEAF = mintLeaf("white", MintLeafItem(MintType.WHITE))
val mints = mutableMapOf<String, MintItem>()
@@ -920,18 +922,11 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val CHOICE_SPECS = wearableItem("choice_specs")
@JvmField val EXP_SHARE = wearableItem("exp_share")
@JvmField val FOCUS_BAND = wearableItem("focus_band")
@JvmField val KINGS_ROCK = wearableBlockItem("kings_rock", CobblemonBlocks.KINGS_ROCK)
@JvmField val NEVER_MELT_ICE = wearableBlockItem("never_melt_ice", CobblemonBlocks.NEVER_MELT_ICE)
@JvmField val KINGS_ROCK = wearableItem("kings_rock")
@JvmField val MUSCLE_BAND = wearableItem("muscle_band")
@JvmField val ROCKY_HELMET = wearableItem("rocky_helmet")
@JvmField val SAFETY_GOGGLES = wearableItem("safety_goggles")
@JvmField val WISE_GLASSES = wearableItem("wise_glasses")
@JvmField val SHARP_BEAK = wearableItem("sharp_beak")
@JvmField val COVERT_CLOAK = wearableItem("covert_cloak")
@JvmField val SHELL_HELMET = wearableItem("shell_helmet")
@JvmField val GALARICA_WREATH = wearableBlockItem("galarica_wreath", CobblemonBlocks.GALARICA_WREATH)
@JvmField val CLEANSE_TAG = wearableBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
@JvmField val SPELL_TAG = wearableBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
// Held Items
@JvmField
@@ -959,17 +954,21 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val CHOICE_SCARF = heldItem("choice_scarf")
@JvmField
val CLEANSE_TAG = itemNameBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
@JvmField
val CLEAR_AMULET = heldItem("clear_amulet")
@JvmField
val DESTINY_KNOT = itemNameBlockItem("destiny_knot", CobblemonBlocks.DESTINY_KNOT)
val COVERT_CLOAK = heldItem("covert_cloak")
@JvmField
val DESTINY_KNOT = heldItem("destiny_knot")
@JvmField
val DRAGON_FANG = heldItem("dragon_fang")
@JvmField
val EJECT_BUTTON = itemNameBlockItem("eject_button", CobblemonBlocks.EJECT_BUTTON)
val EJECT_BUTTON = heldItem("eject_button")
@JvmField
val EJECT_PACK = heldItem("eject_pack")
@JvmField
val ELECTRIC_SEED = itemNameBlockItem("electric_seed", CobblemonBlocks.ELECTRIC_SEED)
val ELECTRIC_SEED = heldItem("electric_seed")
@JvmField
val EVERSTONE = heldItem("everstone")
@JvmField
@@ -979,13 +978,13 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val FAIRY_FEATHER = heldItem("fairy_feather")
@JvmField
val FLAME_ORB = itemNameBlockItem("flame_orb", CobblemonBlocks.FLAME_ORB)
val FLAME_ORB = heldItem("flame_orb")
@JvmField
val FLOAT_STONE = heldItem("float_stone")
@JvmField
val FOCUS_SASH = heldItem("focus_sash")
@JvmField
val GRASSY_SEED = itemNameBlockItem("grassy_seed", CobblemonBlocks.GRASSY_SEED)
val GRASSY_SEED = heldItem("grassy_seed")
@JvmField
val GRIP_CLAW = heldItem("grip_claw")
@JvmField
@@ -993,32 +992,34 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val HEAVY_DUTY_BOOTS = heldItem("heavy_duty_boots")
@JvmField
val IRON_BALL = itemNameBlockItem("iron_ball", CobblemonBlocks.IRON_BALL)
val IRON_BALL = heldItem("iron_ball")
@JvmField
val LAGGING_TAIL = heldItem("lagging_tail")
@JvmField
val LEFTOVERS = compostableHeldItem("leftovers", null, 0.50f)
@JvmField
val LIFE_ORB = itemNameBlockItem("life_orb", CobblemonBlocks.LIFE_ORB)
val LIFE_ORB = heldItem("life_orb")
@JvmField
val LIGHT_BALL = itemNameBlockItem("light_ball", CobblemonBlocks.LIGHT_BALL)
val LIGHT_BALL = heldItem("light_ball")
@JvmField
val LIGHT_CLAY = heldItem("light_clay")
@JvmField
val LOADED_DICE = itemNameBlockItem("loaded_dice", CobblemonBlocks.LOADED_DICE)
val LOADED_DICE = heldItem("loaded_dice")
@JvmField
val LUCKY_EGG = heldItem("lucky_egg")
@JvmField
val LUMINOUS_MOSS = itemNameBlockItem("luminous_moss", CobblemonBlocks.LUMINOUS_MOSS)
val LUMINOUS_MOSS = heldItem("luminous_moss")
@JvmField
val MAGNET = itemNameBlockItem("magnet", CobblemonBlocks.MAGNET)
val MAGNET = heldItem("magnet")
@JvmField
val METRONOME = itemNameBlockItem("metronome", CobblemonBlocks.METRONOME)
val METRONOME = heldItem("metronome")
@JvmField
val MIRACLE_SEED = heldItem("miracle_seed")
@JvmField
val MYSTIC_WATER = heldItem("mystic_water")
@JvmField
val NEVER_MELT_ICE = heldItem("never_melt_ice")
@JvmField
val POISON_BARB = heldItem("poison_barb")
@JvmField
val POWER_ANKLET = heldItem("power_anklet")
@@ -1033,7 +1034,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val POWER_WEIGHT = heldItem("power_weight")
@JvmField
val PSYCHIC_SEED = itemNameBlockItem("psychic_seed", CobblemonBlocks.PSYCHIC_SEED)
val PSYCHIC_SEED = heldItem("psychic_seed")
@JvmField
val PROTECTIVE_PADS = heldItem("protective_pads")
@JvmField
@@ -1043,11 +1044,13 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val RED_CARD = heldItem("red_card")
@JvmField
val RING_TARGET = itemNameBlockItem("ring_target", CobblemonBlocks.RING_TARGET)
val RING_TARGET = heldItem("ring_target")
@JvmField
val ROOM_SERVICE = heldItem("room_service")
@JvmField
val SCOPE_LENS = itemNameBlockItem("scope_lens", CobblemonBlocks.SCOPE_LENS)
val SCOPE_LENS = heldItem("scope_lens")
@JvmField
val SHARP_BEAK = heldItem("sharp_beak")
@JvmField
val SHED_SHELL = heldItem("shed_shell")
@JvmField
@@ -1057,19 +1060,21 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SILVER_POWDER = heldItem("silver_powder")
@JvmField
val SOFT_SAND = itemNameBlockItem("soft_sand", CobblemonBlocks.SOFT_SAND)
val SOFT_SAND = heldItem("soft_sand")
@JvmField
val SMOKE_BALL = itemNameBlockItem("smoke_ball", CobblemonBlocks.SMOKE_BALL)
val SPELL_TAG = itemNameBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
@JvmField
val SMOKE_BALL = heldItem("smoke_ball")
@JvmField
val SOOTHE_BELL = heldItem("soothe_bell")
@JvmField
val STICKY_BARB = heldItem("sticky_barb")
@JvmField
val TERRAIN_EXTENDER = itemNameBlockItem("terrain_extender", CobblemonBlocks.TERRAIN_EXTENDER)
val TERRAIN_EXTENDER = heldItem("terrain_extender")
@JvmField
val THROAT_SPRAY = itemNameBlockItem("throat_spray", CobblemonBlocks.THROAT_SPRAY)
val THROAT_SPRAY = heldItem("throat_spray")
@JvmField
val TOXIC_ORB = itemNameBlockItem("toxic_orb", CobblemonBlocks.TOXIC_ORB)
val TOXIC_ORB = heldItem("toxic_orb")
@JvmField
val TWISTED_SPOON = heldItem("twisted_spoon")
@JvmField
@@ -1077,33 +1082,33 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val WEAKNESS_POLICY = itemNameBlockItem("weakness_policy", CobblemonBlocks.WEAKNESS_POLICY)
@JvmField
val WIDE_LENS = itemNameBlockItem("wide_lens", CobblemonBlocks.WIDE_LENS)
val WIDE_LENS = heldItem("wide_lens")
@JvmField
val ZOOM_LENS = itemNameBlockItem("zoom_lens", CobblemonBlocks.ZOOM_LENS)
val ZOOM_LENS = heldItem("zoom_lens")
@JvmField
val MENTAL_HERB = compostableHeldItem("mental_herb", null, 0.85F)
@JvmField
val MIRROR_HERB = compostableHeldItem("mirror_herb", null, 0.85F)
@JvmField
val MISTY_SEED = itemNameBlockItem("misty_seed", CobblemonBlocks.MISTY_SEED)
val MISTY_SEED = heldItem("misty_seed")
@JvmField
val POWER_HERB = compostableHeldItem("power_herb", null, 0.85F)
@JvmField
val WHITE_HERB = compostableHeldItem("white_herb", null, 0.85F)
@JvmField
val BRIGHT_POWDER = glowItem("bright_powder", CobblemonBlocks.BRIGHT_POWDER)
val BRIGHT_POWDER = heldItem("bright_powder")
@JvmField
val METAL_POWDER = itemNameBlockItem("metal_powder", CobblemonBlocks.METAL_POWDER)
val METAL_POWDER = heldItem("metal_powder")
@JvmField
val QUICK_POWDER = itemNameBlockItem("quick_powder", CobblemonBlocks.QUICK_POWDER)
val QUICK_POWDER = heldItem("quick_powder")
@JvmField
val DAMP_ROCK = itemNameBlockItem("damp_rock", CobblemonBlocks.DAMP_ROCK)
val DAMP_ROCK = heldItem("damp_rock")
@JvmField
val HEAT_ROCK = itemNameBlockItem("heat_rock", CobblemonBlocks.HEAT_ROCK)
val HEAT_ROCK = heldItem("heat_rock")
@JvmField
val SMOOTH_ROCK = itemNameBlockItem("smooth_rock", CobblemonBlocks.SMOOTH_ROCK)
val SMOOTH_ROCK = heldItem("smooth_rock")
@JvmField
val ICY_ROCK = itemNameBlockItem("icy_rock", CobblemonBlocks.ICY_ROCK)
val ICY_ROCK = heldItem("icy_rock")
// Mulch
@JvmField
@@ -1558,25 +1563,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
//@JvmField
//val BINDING_SOIL = blockItem("binding_soil", CobblemonBlocks.BINDING_SOIL)
// Plants, Flora, and Flowers
@JvmField
val LOBAILA = compostableBlockItem("lobaila", CobblemonBlocks.LOBAILA, 0.65f)
@JvmField
val POMOHANA = compostableBlockItem("pomohana", CobblemonBlocks.POMOHANA, 0.3f)
@JvmField
val PAUVILLEA = compostableBlockItem("pauvillea", CobblemonBlocks.PAUVILLEA, 0.65f)
@JvmField
val WISTENSU = compostableBlockItem("wistensu", CobblemonBlocks.WISTENSU, 0.5f)
@JvmField
val VOLTACONIA = compostableBlockItem("voltaconia", CobblemonBlocks.VOLTACONIA, 0.85f)
@JvmField
val VERDROOT = compostableBlockItem("verdroot", CobblemonBlocks.VERDROOT, 0.85f)
@JvmField
val MISTLEAF = compostableBlockItem("mistleaf", CobblemonBlocks.MISTLEAF, 0.85f)
@JvmField
val LUCIBELL = compostableBlockItem("lucibell", CobblemonBlocks.LUCIBELL, 0.85f)
private fun blockItem(name: String, block: Block, rarity: Rarity = Rarity.COMMON): BlockItem = this.create(name, BlockItem(block, Item.Properties().rarity(rarity)))
private fun itemNameBlockItem(name: String, block: Block, rarity: Rarity = Rarity.COMMON): ItemNameBlockItem = this.create(name, ItemNameBlockItem(block, Item.Properties().rarity(rarity)))
@@ -1613,16 +1599,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
private fun wearableItem(name: String, heldItemRemappedName: String? = null): CobblemonItem = create(
name,
WearableHatItem(name).also {
wearables.add(it)
if (heldItemRemappedName != null) {
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
}
}
)
private fun wearableBlockItem(name: String, block: Block, heldItemRemappedName: String? = null): WearableBlockItem = create(
name,
WearableBlockItem(name, block).also {
WearableItem(name).also {
wearables.add(it)
if (heldItemRemappedName != null) {
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
@@ -1779,6 +1756,4 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
private fun foodItem(nutrition: Int, saturationModifier: Float): Item {
return CobblemonItem(Item.Properties().food(FoodProperties.Builder().nutrition(nutrition).saturationModifier(saturationModifier).build()))
}
private fun glowItem(name: String, block: Block): GlowItem = this.create(name, GlowItem(block, Item.Properties()))
}
@@ -425,7 +425,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(MovesRegistrySyncPacket.ID, MovesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BerryRegistrySyncPacket.ID, BerryRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SpeciesRegistrySyncPacket.ID, SpeciesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(DexEvolutionDisplaySyncPacket.ID, DexEvolutionDisplaySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(PropertiesCompletionRegistrySyncPacket.ID, PropertiesCompletionRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(StandardSpeciesFeatureSyncPacket.ID, StandardSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(GlobalSpeciesFeatureSyncPacket.ID, GlobalSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
@@ -20,7 +20,6 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.air.HelicopterSetting
import com.cobblemon.mod.common.api.riding.behaviour.types.air.HoverSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.JetSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.RocketSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.BoardSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleSettings
@@ -57,7 +56,6 @@ object CobblemonRideSettings : DataRegistry {
var hover = HoverSettings()
var jet = JetSettings()
var rocket = RocketSettings()
var board = BoardSettings()
var horse = HorseSettings()
var minekart = MinekartSettings()
@@ -77,7 +75,6 @@ object CobblemonRideSettings : DataRegistry {
hover = hover,
jet = jet,
rocket = rocket,
board = board,
horse = horse,
minekart = minekart,
vehicle = vehicle,
@@ -96,7 +93,6 @@ object CobblemonRideSettings : DataRegistry {
hover = loadStyle(manager, "hover", HoverSettings::class.java)
jet = loadStyle(manager, "jet", JetSettings::class.java)
rocket = loadStyle(manager, "rocket", RocketSettings::class.java)
board = loadStyle(manager, "board", BoardSettings::class.java)
horse = loadStyle(manager, "horse", HorseSettings::class.java)
minekart = loadStyle(manager, "minekart", MinekartSettings::class.java)
vehicle = loadStyle(manager, "vehicle", VehicleSettings::class.java)
@@ -148,9 +148,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val MONITOR_GLITCHING = this.create("block.monitor.glitching")
@JvmField
val BRIGHT_POWDER_USE = this.create("block.bright_powder.use")
@JvmField
val FOSSIL_MACHINE_ACTIVATE = this.create("block.fossil_machine.activate")
@JvmField
@@ -660,95 +657,5 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
SoundEvents.WOOL_FALL
)
@JvmField
val ITEM_BLOCK_POWDER_PLACE = this.create("block.item_block.powder.place")
@JvmField
val ITEM_BLOCK_POWDER_BREAK = this.create("block.item_block.powder.break")
@JvmField
val ITEM_BLOCK_POWDER_STEP = this.create("block.item_block.powder.step")
@JvmField
val ITEM_BLOCK_POWDER_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_POWDER_BREAK,
ITEM_BLOCK_POWDER_STEP,
ITEM_BLOCK_POWDER_PLACE,
ITEM_BLOCK_POWDER_STEP,
ITEM_BLOCK_POWDER_STEP
)
@JvmField
val ITEM_BLOCK_BOTTLE_LIQUID_PLACE = this.create("block.item_block.bottle_liquid.place")
@JvmField
val ITEM_BLOCK_BOTTLE_LIQUID_BREAK = this.create("block.item_block.bottle_liquid.break")
@JvmField
val ITEM_BLOCK_BOTTLE_LIQUID_STEP = this.create("block.item_block.bottle_liquid.step")
@JvmField
val ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_BOTTLE_LIQUID_BREAK,
ITEM_BLOCK_BOTTLE_LIQUID_STEP,
ITEM_BLOCK_BOTTLE_LIQUID_PLACE,
ITEM_BLOCK_BOTTLE_LIQUID_PLACE,
ITEM_BLOCK_BOTTLE_LIQUID_PLACE
)
@JvmField
val ITEM_BLOCK_BOTTLE_PILLS_PLACE = this.create("block.item_block.bottle_pills.place")
@JvmField
val ITEM_BLOCK_BOTTLE_PILLS_BREAK = this.create("block.item_block.bottle_pills.break")
@JvmField
val ITEM_BLOCK_BOTTLE_PILLS_STEP = this.create("block.item_block.bottle_pills.step")
@JvmField
val ITEM_BLOCK_BOTTLE_PILLS_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_BOTTLE_PILLS_BREAK,
ITEM_BLOCK_BOTTLE_PILLS_STEP,
ITEM_BLOCK_BOTTLE_PILLS_PLACE,
ITEM_BLOCK_BOTTLE_PILLS_PLACE,
ITEM_BLOCK_BOTTLE_PILLS_PLACE
)
@JvmField
val ITEM_BLOCK_DICE_PLACE = this.create("block.item_block.dice.place")
@JvmField
val ITEM_BLOCK_DICE_BREAK = this.create("block.item_block.dice.break")
@JvmField
val ITEM_BLOCK_DICE_STEP = this.create("block.item_block.dice.step")
@JvmField
val ITEM_BLOCK_DICE_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_DICE_BREAK,
ITEM_BLOCK_DICE_STEP,
ITEM_BLOCK_DICE_PLACE,
ITEM_BLOCK_DICE_STEP,
ITEM_BLOCK_DICE_STEP
)
@JvmField
val ITEM_BLOCK_METAL_CAN_PLACE = this.create("block.item_block.metal_can.place")
@JvmField
val ITEM_BLOCK_METAL_CAN_BREAK = this.create("block.item_block.metal_can.break")
@JvmField
val ITEM_BLOCK_METAL_CAN_STEP = this.create("block.item_block.metal_can.step")
@JvmField
val ITEM_BLOCK_METAL_CAN_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_METAL_CAN_BREAK,
ITEM_BLOCK_METAL_CAN_STEP,
ITEM_BLOCK_METAL_CAN_PLACE,
ITEM_BLOCK_METAL_CAN_STEP,
ITEM_BLOCK_METAL_CAN_STEP
)
@JvmField
val ITEM_BLOCK_JAR_GLASS_LIQUID_PLACE = this.create("block.item_block.jar_glass_liquid.place")
@JvmField
val ITEM_BLOCK_JAR_GLASS_LIQUID_BREAK = this.create("block.item_block.jar_glass_liquid.break")
@JvmField
val ITEM_BLOCK_JAR_GLASS_LIQUID_STEP = this.create("block.item_block.jar_glass_liquid.step")
@JvmField
val ITEM_BLOCK_JAR_GLASS_LIQUID_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_JAR_GLASS_LIQUID_BREAK,
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP,
ITEM_BLOCK_JAR_GLASS_LIQUID_PLACE,
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP,
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP
)
private fun create(name: String): SoundEvent = this.create(name, SoundEvent.createVariableRangeEvent(cobblemonResource(name)))
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.bedrockk.molang.Expression
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.molang.MoLangFunctions.asMostSpecificMoLangValue
@@ -18,7 +17,6 @@ import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.withQueryValue
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
@@ -36,8 +34,6 @@ import net.minecraft.world.entity.ai.sensing.SensorType
class HateEntityTaskConfig : SingleTaskConfig {
val entityCondition: Expression = "true".asExpression()
val range: ExpressionOrEntityVariable = Either.left("24".asExpression())
val chance: Float = Cobblemon.config.predationChance
private val coercedChance = chance.coerceIn(0f, 1f)
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = emptyList<MoLangConfigVariable>()
override fun createTask(
@@ -47,44 +43,24 @@ class HateEntityTaskConfig : SingleTaskConfig {
val range = range.resolveFloat(behaviourConfigurationContext.runtime)
behaviourConfigurationContext.addMemories(
MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES,
MemoryModuleType.ANGRY_AT,
MemoryModuleType.ATTACK_TARGET
MemoryModuleType.ANGRY_AT
)
behaviourConfigurationContext.addSensors(SensorType.NEAREST_LIVING_ENTITIES)
return BehaviorBuilder.create { instance ->
instance.group(
instance.present(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES),
instance.registered(MemoryModuleType.ANGRY_AT),
instance.registered(MemoryModuleType.ATTACK_TARGET),
).apply(instance) { entities, angryAt, attackTarget ->
instance.registered(MemoryModuleType.ANGRY_AT)
).apply(instance) { entities, angryAt ->
Trigger { world, entity, _ ->
// bail if gamerule is disabled
if (!world.gameRules.getBoolean(CobblemonGameRules.DO_POKEMON_PREDATION)) return@Trigger false
val target = instance.get(entities).findClosest { hated ->
mainThreadRuntime.withQueryValue("entity", hated.asMostSpecificMoLangValue())
val target = instance.get(entities).findClosest {
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
return@findClosest mainThreadRuntime.resolveBoolean(entityCondition)
}.orElse(null)
var shouldTrigger = false
if (target != null && target.distanceTo(entity) <= range) {
shouldTrigger = when {
// set always attacking if activity chance is equal to 1.0
coercedChance == 1f -> {
angryAt.set(target.uuid)
attackTarget.set(target)
true
}
// roll the dice in other way
coercedChance != 1f && world.random.nextFloat() < coercedChance -> {
angryAt.set(target.uuid)
attackTarget.set(target)
true
}
// fallback to peaceful behavior in unattended cases
else -> false
}
angryAt.set(target.uuid)
}
return@Trigger shouldTrigger
return@Trigger true
}
}
}
@@ -1,25 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.battles.model
/**
* How a [PokemonBattle] came to an end.
*/
enum class BattleEndResult {
/** One side defeated the other. */
VICTORY,
/** A wild Pokémon was captured, ending the battle. */
CAPTURE,
/** An actor forfeited, giving the victory to the other side. */
FORFEIT,
/** A player fled from a wild Pokémon. */
FLED,
/** The battle was stopped without a winner, such as by a command, a player disconnecting or an error. */
STOPPED
}
@@ -21,7 +21,6 @@ import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
import com.cobblemon.mod.common.api.battles.model.actor.FleeableBattleActor
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.BattleEndedEvent
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
import com.cobblemon.mod.common.api.events.battles.BattleFleeAttemptEvent
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
@@ -125,12 +124,6 @@ open class PokemonBattle(
var winners = listOf<BattleActor>()
var losers = listOf<BattleActor>()
var ended = false
/** How the battle ended, or null if it hasn't ended yet. */
var endResult: BattleEndResult? = null
/** The actor that forfeited the battle, if any. */
var forfeitedBy: BattleActor? = null
/** Whether a player fled from the battle. */
var fled = false
// TEMP battle showcase stuff
var announcingRules = false
var turn: Int = 0
@@ -140,7 +133,7 @@ open class PokemonBattle(
set(value) { field = value.coerceAtMost(Int.MAX_VALUE) } // go outside
/** The current duration of the battle in seconds. */
val time: Int get() = ticks / 20
val time: Int get() = ticks % 20
var dispatchResult = GO
val dispatches = ConcurrentLinkedDeque<BattleDispatch>()
@@ -239,10 +232,6 @@ open class PokemonBattle(
}
fun writeShowdownAction(vararg messages: String) {
if (messages.any { it.contains("\n") || it.contains("\r") }) {
LOGGER.warn("Refusing to forward Showdown action with an embedded line break for battle {}: {}", battleId, messages.joinToString(" | "))
return
}
log(messages.joinToString("\n"))
ShowdownService.service.send(battleId, messages.toList().toTypedArray())
}
@@ -262,14 +251,6 @@ open class PokemonBattle(
fun end() {
ended = true
val result = when {
winners.isNotEmpty() && showdownMessages.any { "capture" in it } -> BattleEndResult.CAPTURE
winners.isNotEmpty() && forfeitedBy != null -> BattleEndResult.FORFEIT
winners.isNotEmpty() -> BattleEndResult.VICTORY
fled -> BattleEndResult.FLED
else -> BattleEndResult.STOPPED
}
endResult = result
val awardToFainted = Cobblemon.config.awardExperienceToFaintedPokemon
val awardOnLoss = Cobblemon.config.awardExperienceOnBattleLoss
this.actors.forEach { actor ->
@@ -351,7 +332,6 @@ open class PokemonBattle(
}
}
}
CobblemonEvents.BATTLE_ENDED.post(BattleEndedEvent(this, result, forfeitedBy.takeIf { result == BattleEndResult.FORFEIT }))
sendUpdate(BattleEndPacket())
BattleRegistry.closeBattle(this)
}
@@ -522,7 +502,6 @@ open class PokemonBattle(
.forEach{it.pokemon.heal()}
CobblemonEvents.BATTLE_FLED.post(BattleFledEvent(this, actors.asSequence().filterIsInstance<PlayerBattleActor>().iterator().next()))
actors.filterIsInstance<EntityBackedBattleActor<*>>().mapNotNull { it.entity }.forEach { it.sendSystemMessage(battleLang("flee").yellow()) }
fled = true
stop()
}
}
@@ -560,7 +539,6 @@ open class PokemonBattle(
private fun checkForfeit(): Boolean {
val forfeit = actors.find { it.responses.any { it is ForfeitActionResponse } }
return forfeit?.let {
forfeitedBy = it
this.dispatchWaiting { this.broadcastChatMessage(battleLang("forfeit", it.getName()).red()) }
writeShowdownAction(">forcelose ${it.showdownId}")
true
@@ -1,93 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.data
import com.cobblemon.mod.common.util.endsWith
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import net.minecraft.core.HolderLookup
import java.io.BufferedReader
import java.io.File
import java.util.concurrent.ExecutionException
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.ResourceManager
/**
* A [DataRegistry] that consumes JSON files.
* Every deserialized instance is attached to an [Identifier].
* For example a file under data/mymod/[resourcePath]/entry.json would be backed by the identifier modid:entry.
*
* @param T The type of the data consumed by this registry.
*
* @author Licious
* @since August 5th, 2022
*/
interface ServerJsonDataRegistry<T> : DataRegistry {
/**
* The [TypeToken] of type [T].
*/
val typeToken: TypeToken<T>
/**
* The folder location for the data this registry will consume.
*/
val resourcePath: String
override val type: PackType
get() = PackType.SERVER_DATA
override fun reload(manager: ResourceManager) {
//NO-OP, handled by reload(manager, registry)
}
fun reload(manager: ResourceManager, registry: HolderLookup.Provider) {
val gson = setupParser(registry)
val data = hashMapOf<ResourceLocation, T>()
manager.listResources(resourcePath) { path -> path.endsWith(JSON_EXTENSION) }.forEach { (identifier, resource) ->
if (identifier.namespace == "pixelmon") {
return@forEach
}
resource.open().use { stream ->
stream.bufferedReader().use { reader ->
val resolvedIdentifier = ResourceLocation.fromNamespaceAndPath(identifier.namespace, File(identifier.path).nameWithoutExtension)
data[resolvedIdentifier] = parse(reader, resolvedIdentifier, gson)
}
}
}
reload(data, registry)
}
fun parse(reader: BufferedReader, identifier: ResourceLocation, gson: Gson): T {
return try {
gson.fromJson(reader, typeToken.type)
} catch (exception: Exception) {
throw ExecutionException("Error loading JSON for data: $identifier", exception)
}
}
/**
* Allows the creation of [Gson] instances which have adapters that are registry-aware, e.g. [com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter]
*/
fun setupParser(registry: HolderLookup.Provider): Gson
/**
* Reloads this registry from the deserialized data.
*
* @param data A map of the data associating an instance to the respective identifier from the [ResourceManager].
*/
fun reload(data: Map<ResourceLocation, T>, registry: HolderLookup.Provider)
companion object {
const val JSON_EXTENSION = ".json"
}
}
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.api.events.battles.BattleFaintedEvent
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
import com.cobblemon.mod.common.api.events.battles.BattleFleeAttemptEvent
import com.cobblemon.mod.common.api.events.battles.BattleStartedEvent
import com.cobblemon.mod.common.api.events.battles.BattleEndedEvent
import com.cobblemon.mod.common.api.events.battles.BattleVictoryEvent
import com.cobblemon.mod.common.api.events.battles.instruction.FormeChangeEvent
import com.cobblemon.mod.common.api.events.battles.instruction.MegaEvolutionEvent
@@ -140,9 +139,6 @@ object CobblemonEvents {
@JvmField
val BATTLE_FAINTED = EventObservable<BattleFaintedEvent>()
@JvmField
val BATTLE_ENDED = EventObservable<BattleEndedEvent>()
// instructions
@JvmField
val MEGA_EVOLUTION = EventObservable<MegaEvolutionEvent>()
@@ -1,69 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.battles
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.battles.model.BattleEndResult
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.battles.model.actor.ActorType
import com.cobblemon.mod.common.util.asArrayValue
/**
* Event fired whenever a [PokemonBattle] ends, regardless of how it ended (victory, fleeing, being stopped, etc).
*
* [PokemonBattle.winners] and [PokemonBattle.losers] are only populated if the [result] has a winner,
* that is [BattleEndResult.VICTORY], [BattleEndResult.CAPTURE] or [BattleEndResult.FORFEIT].
*
* @param result How the battle ended.
* @param forfeitedBy The actor that forfeited, if the [result] is [BattleEndResult.FORFEIT].
*/
data class BattleEndedEvent(
override val battle: PokemonBattle,
val result: BattleEndResult,
val forfeitedBy: BattleActor?
) : BattleEvent {
/** Whether one side won the battle, which includes captures and forfeits. */
val hasWinner = battle.winners.isNotEmpty()
val wasVictory = result == BattleEndResult.VICTORY
val wasCapture = result == BattleEndResult.CAPTURE
val wasForfeit = result == BattleEndResult.FORFEIT
val wasFled = result == BattleEndResult.FLED
val wasStopped = result == BattleEndResult.STOPPED
/** Whether the battle was between players and wild Pokémon. */
val isPvW = battle.isPvW
/** Whether the battle was between players and NPCs. */
val isPvN = battle.isPvN
/** Whether the battle was between players. */
val isPvP = battle.isPvP
val context = mutableMapOf<String, MoValue>(
"battle" to battle.struct,
"result" to StringValue(result.name.lowercase()),
"has_winner" to DoubleValue(hasWinner),
"was_victory" to DoubleValue(wasVictory),
"was_capture" to DoubleValue(wasCapture),
"was_forfeit" to DoubleValue(wasForfeit),
"was_fled" to DoubleValue(wasFled),
"was_stopped" to DoubleValue(wasStopped),
"is_pvw" to DoubleValue(isPvW),
"is_pvn" to DoubleValue(isPvN),
"is_pvp" to DoubleValue(isPvP),
"winners" to battle.winners.asArrayValue { it.struct },
"losers" to battle.losers.asArrayValue { it.struct },
"players" to battle.actors.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
"npcs" to battle.actors.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
"wild_pokemon" to battle.actors.filter { it.type == ActorType.WILD }.asArrayValue { it.struct }
).apply {
forfeitedBy?.let { put("forfeited_by", it.struct) }
}
}
@@ -21,8 +21,7 @@ import com.cobblemon.mod.common.pokemon.Pokemon
*/
data class FriendshipUpdatedEvent(
val pokemon: Pokemon,
val newFriendshipInitial: Int,
val earned: Boolean = false
val newFriendshipInitial: Int
) {
var newFriendship: Int = newFriendshipInitial
set(value) {
@@ -40,7 +40,7 @@ object Fossils: JsonDataRegistry<Fossil> {
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter())
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.create()
override val typeToken: TypeToken<Fossil> = TypeToken.get(Fossil::class.java)
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.gui
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import net.minecraft.client.gui.components.AbstractWidget
import net.minecraft.client.gui.components.Renderable
import net.minecraft.client.gui.components.events.GuiEventListener
import net.minecraft.client.gui.narration.NarrationElementOutput
import net.minecraft.network.chat.Component
@@ -54,8 +55,6 @@ abstract class ParentWidget(
}
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (!active) return false
return children.any {
it.mouseClicked(pMouseX, pMouseY, pButton)
}
@@ -1,116 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.gui
import kotlin.math.max
import kotlin.math.min
interface TreeNode<T> {
val children: List<TreeNode<T>>
}
class TreeDrawingNode<T>(
val value: T,
val children: List<TreeDrawingNode<T>>,
) {
var x = 0
var y = 0
var offset = 0
}
fun <T> createDrawingTree(treeNode: TreeNode<T>): TreeDrawingNode<T> {
val root = setup(treeNode, 0, mutableListOf<Int>(), mutableListOf<Int>())
addMods(root, 0)
return root
}
@Suppress("UNCHECKED_CAST")
private fun <T> setup(
treeNode: TreeNode<T>,
depth: Int,
nexts: MutableList<Int>,
offset: MutableList<Int>,
): TreeDrawingNode<T> {
val children = treeNode.children.map { child ->
setup(child, depth + 1, nexts, offset)
}
val treeDrawingNode = TreeDrawingNode<T>(treeNode as T, children)
treeDrawingNode.y = depth
while (nexts.size <= depth) {
nexts.add(0)
}
while (offset.size <= depth) {
offset.add(0)
}
var place =
if (children.isEmpty()) {
nexts[depth]
} else if (children.size == 1) {
// children.first().x - 1
children.first().x
} else {
// (children[0].x + children[1].x) / 2
(children.first().x + children.last().x) / 2
}
offset[depth] = max(offset[depth], nexts[depth] - place)
if (children.isEmpty()) {
treeDrawingNode.x = place
} else {
treeDrawingNode.x = place + offset[depth]
}
nexts[depth] += 2
treeDrawingNode.offset = offset[depth]
return treeDrawingNode
}
private fun <T> addMods(
treeNode: TreeDrawingNode<T>,
modSum: Int,
) {
treeNode.x += modSum
val newModSum = modSum + treeNode.offset
treeNode.children.forEach { child ->
addMods(child, newModSum)
}
}
data class TreeDrawingBounds(val minX: Int, val maxX: Int, val minY: Int, val maxY: Int) {
val width = maxX - minX
val height = maxY - minY
val xCenter = (maxX + minX) / 2.0
val yCenter = (maxY + minY) / 2.0
}
fun <T> calculateBounds(treeDrawingNode: TreeDrawingNode<T>): TreeDrawingBounds {
var minX = treeDrawingNode.x
var maxX = treeDrawingNode.x
var minY = treeDrawingNode.y
var maxY = treeDrawingNode.y
for (child in treeDrawingNode.children) {
val childBounds = calculateBounds(child)
minX = min(minX, childBounds.minX)
maxX = max(maxX, childBounds.maxX)
minY = min(minY, childBounds.minY)
maxY = max(maxY, childBounds.maxY)
}
return TreeDrawingBounds(minX, maxX, minY, maxY)
}
@@ -65,7 +65,7 @@ class HabitatSpawn {
/** The maximum light level that the Pokémon can spawn in. */
var maxLight: Int? = null
fun writeToNBT(nbt: CompoundTag) {
open fun writeToNBT(nbt: CompoundTag) {
nbt.putString(DataKeys.HABITAT_POOL_SPAWN_SPECIES, species.resourceIdentifier.toString())
nbt.putString(DataKeys.HABITAT_POOL_SPAWN_POSITION_TYPE, spawnablePositionType)
nbt.putFloat(DataKeys.HABITAT_POOL_SPAWN_WEIGHT, weight)
@@ -78,7 +78,7 @@ class HabitatSpawn {
nbt.putString(DataKeys.HABITAT_POOL_SPAWN_BUCKET, bucket)
}
fun readFromNBT(nbt: CompoundTag) {
open fun readFromNBT(nbt: CompoundTag) {
species = PokemonSpecies.getByIdentifier(nbt.getString(DataKeys.HABITAT_POOL_SPAWN_SPECIES).asIdentifierDefaultingNamespace())!!
spawnablePositionType = nbt.getString(DataKeys.HABITAT_POOL_SPAWN_POSITION_TYPE)
weight = nbt.getFloat(DataKeys.HABITAT_POOL_SPAWN_WEIGHT)
@@ -91,7 +91,7 @@ class HabitatSpawn {
bucket = if (nbt.contains(DataKeys.HABITAT_POOL_SPAWN_BUCKET)) nbt.getString(DataKeys.HABITAT_POOL_SPAWN_BUCKET) else "common"
}
fun encode(buffer: RegistryFriendlyByteBuf) {
open fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(species.resourceIdentifier)
buffer.writeUtf(spawnablePositionType)
buffer.writeFloat(weight)
@@ -104,7 +104,7 @@ class HabitatSpawn {
buffer.writeUtf(bucket)
}
fun decode(buffer: RegistryFriendlyByteBuf) {
open fun decode(buffer: RegistryFriendlyByteBuf) {
species = PokemonSpecies.getByIdentifier(buffer.readResourceLocation())!!
spawnablePositionType = buffer.readUtf()
weight = buffer.readFloat()
@@ -118,7 +118,7 @@ class HabitatSpawn {
}
/** Creates the [PokemonSpawnDetail] that will be used in the spawner. This is done per habitat block. */
fun createSpawnDetail(habitatBlockEntity: HabitatBlockEntity): PokemonSpawnDetail? {
open fun createSpawnDetail(habitatBlockEntity: HabitatBlockEntity): PokemonSpawnDetail? {
val spawnDetail = PokemonSpawnDetail()
// If the habitat block specifies a level range, it restricts how and if this spawn exists
@@ -10,7 +10,7 @@ package com.cobblemon.mod.common.api.interaction
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.data.ServerJsonDataRegistry
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
@@ -26,7 +26,6 @@ import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.advancements.critereon.ItemPredicate
import net.minecraft.core.HolderLookup
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
@@ -37,31 +36,28 @@ import net.minecraft.world.level.block.Block
import net.minecraft.world.level.levelgen.structure.Structure
import net.minecraft.world.level.material.Fluid
object PokemonInteractions : ServerJsonDataRegistry<PokemonInteractionSet> {
object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
override val id = cobblemonResource("pokemon_interactions")
override val type = PackType.SERVER_DATA
override fun setupParser(registry: HolderLookup.Provider): Gson {
return GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter(registry))
.registerTypeAdapter(Requirement::class.java, CobblemonRequirementAdapter)
.registerTypeAdapter(InteractionEffect::class.java, InteractionEffectAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Structure::class.java).type, StructureLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory())
.setPrettyPrinting()
.create()
}
override val observable = SimpleObservable<PokemonInteractions>()
override val typeToken: TypeToken<PokemonInteractionSet> = TypeToken.get(PokemonInteractionSet::class.java)
override val resourcePath = "pokemon_interactions"
override val gson: Gson = GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(Requirement::class.java, CobblemonRequirementAdapter)
.registerTypeAdapter(InteractionEffect::class.java, InteractionEffectAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Structure::class.java).type, StructureLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory())
.setPrettyPrinting()
.create()
val interactions = mutableListOf<PokemonInteractionSet>()
@@ -69,7 +65,7 @@ object PokemonInteractions : ServerJsonDataRegistry<PokemonInteractionSet> {
PokemonInteractionsSyncPacket(interactions).sendToPlayer(player)
}
override fun reload(data: Map<ResourceLocation, PokemonInteractionSet>, registry: HolderLookup.Provider) {
override fun reload(data: Map<ResourceLocation, PokemonInteractionSet>) {
interactions.clear()
val split = data.entries
.partition {
@@ -91,4 +87,4 @@ object PokemonInteractions : ServerJsonDataRegistry<PokemonInteractionSet> {
.flatMap { it.interactions }
.firstOrNull(interactionCheck)
}
}
}
@@ -83,6 +83,12 @@ import net.minecraft.world.level.Level
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.dimension.DimensionType
import net.minecraft.world.level.levelgen.Heightmap
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.level.storage.loot.LootParams
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets
import net.minecraft.world.level.storage.loot.parameters.LootContextParams
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import java.util.function.Function
@@ -93,31 +99,25 @@ import java.util.function.Function
* @since October 2nd, 2023
*/
object MoLangFunctions {
fun ItemStack.asMoLangValue(registryAccess: RegistryAccess) =
ObjectValue(this)
.addStandardFunctions()
.addFunctions(ItemStackMoLangFunctions.attach(Pair(this, registryAccess)))
fun ItemStack.asMoLangValue(registryAccess: RegistryAccess) = ObjectValue(this)
.addStandardFunctions()
.addFunctions(ItemStackMoLangFunctions.attach(Pair(this, registryAccess)))
fun Holder<Biome>.asBiomeMoLangValue() =
asMoLangValue(Registries.BIOME)
.addFunctions(BiomeMoLangFunctions.attach(this))
fun Holder<Biome>.asBiomeMoLangValue() = asMoLangValue(Registries.BIOME)
.addFunctions(BiomeMoLangFunctions.attach(this))
fun Holder<Level>.asWorldMoLangValue() =
asMoLangValue(Registries.DIMENSION)
.addFunctions(WorldMoLangFunctions.attach(this))
fun Holder<Level>.asWorldMoLangValue() = asMoLangValue(Registries.DIMENSION)
.addFunctions(WorldMoLangFunctions.attach(this))
fun Holder<Block>.asBlockMoLangValue() =
asMoLangValue(Registries.BLOCK)
.addFunctions(BlockMoLangFunctions.attach(this))
fun Holder<Block>.asBlockMoLangValue() = asMoLangValue(Registries.BLOCK)
.addFunctions(BlockMoLangFunctions.attach(this))
fun MinecraftServer.asMoLangValue() =
ObjectValue(this)
.addStandardFunctions()
.addFunctions(ServerMoLangFunctions.attach(this))
fun MinecraftServer.asMoLangValue() = ObjectValue(this)
.addStandardFunctions()
.addFunctions(ServerMoLangFunctions.attach(this))
fun Holder<DimensionType>.asDimensionTypeMoLangValue() =
asMoLangValue(Registries.DIMENSION_TYPE)
.addFunctions(DimensionTypeMoLangFunctions.attach(this))
fun Holder<DimensionType>.asDimensionTypeMoLangValue() = asMoLangValue(Registries.DIMENSION_TYPE)
.addFunctions(DimensionTypeMoLangFunctions.attach(this))
fun Player.asMoLangValue(): ObjectValue<Player> {
if (this is ServerPlayer && uuid in Cobblemon.serverPlayerStructs) {
@@ -129,11 +129,10 @@ object MoLangFunctions {
}
}
val value =
ObjectValue(
obj = this,
stringify = { it.effectiveName().string },
)
val value = ObjectValue(
obj = this,
stringify = { it.effectiveName().string }
)
value.addFunctions(EntityMoLangFunctions.attach(this))
value.addFunctions(LivingEntityMoLangFunctions.attach(this))
@@ -154,63 +153,57 @@ object MoLangFunctions {
}
fun PartyStore.asMoLangValue(): ObjectValue<PartyStore> {
val value =
ObjectValue(
obj = this,
stringify = { it.toString() },
)
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
value.addFunctions(PokemonStoreMoLangFunctions.attach(this))
value.addFunctions(PartyMoLangFunctions.attach(this))
return value
}
fun DropEntry.asMoLangValue(): ObjectValue<DropEntry> {
val value =
ObjectValue(
obj = this,
stringify = { it.toString() },
)
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
value.addFunctions(DropEntryMoLangFunctions.attach(this))
return value
}
fun PokemonProperties.asMoLangValue(): ObjectValue<PokemonProperties> {
val value =
ObjectValue(
obj = this,
stringify = { it.asString() },
)
val value = ObjectValue(
obj = this,
stringify = { it.asString() }
)
value.addFunctions(PokemonPropertiesMoLangFunctions.attach(this))
return value
}
fun Evolution.asMoLangValue(): ObjectValue<Evolution> {
val value =
ObjectValue(
obj = this,
stringify = { it.toString() },
)
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
value.addFunctions(EvolutionMoLangFunctions.attach(this))
return value
}
fun PCStore.asMoLangValue(): ObjectValue<PCStore> {
val value =
ObjectValue(
obj = this,
stringify = { it.toString() },
)
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
value.addFunctions(PokemonStoreMoLangFunctions.attach(this))
value.addFunctions(PCMoLangFunctions.attach(this))
return value
}
fun NPCEntity.asMoLangValue(): ObjectValue<NPCEntity> {
val value =
ObjectValue(
obj = this,
stringify = { it.name.string },
)
val value = ObjectValue(
obj = this,
stringify = { it.name.string }
)
value.addFunctions(EntityMoLangFunctions.attach(this))
value.addFunctions(LivingEntityMoLangFunctions.attach(this))
value.addFunctions(NPCMoLangFunctions.attach(this))
@@ -218,11 +211,10 @@ object MoLangFunctions {
}
fun PokemonEntity.asMoLangValue(): ObjectValue<PokemonEntity> {
val value =
ObjectValue(
obj = this,
stringify = { it.pokemon.uuid.toString() },
)
val value = ObjectValue(
obj = this,
stringify = { it.pokemon.uuid.toString() }
)
value.addFunctions(EntityMoLangFunctions.attach(this))
value.addFunctions(LivingEntityMoLangFunctions.attach(this))
@@ -233,40 +225,36 @@ object MoLangFunctions {
}
fun PokemonBattle.asMoLangValue(): ObjectValue<PokemonBattle> {
val value =
ObjectValue(
obj = this,
stringify = { it.battleId.toString() },
)
val value = ObjectValue(
obj = this,
stringify = { it.battleId.toString() }
)
value.addFunctions(BattleMoLangFunctions.attach(this))
return value
}
fun BattleActor.asMoLangValue(): ObjectValue<BattleActor> {
val value =
ObjectValue(
obj = this,
stringify = { it.toString() },
)
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
value.addFunctions(BattleActorMoLangFunctions.attach(this))
return value
}
fun SpawnablePosition.asMoLangValue(): ObjectValue<SpawnablePosition> {
val value =
ObjectValue(
obj = this,
stringify = { it.toString() },
)
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
value.addFunctions(SpawnablePositionMoLangFunctions.attach(this))
return value
}
fun Vec3.asMoLangValue(): ObjectValue<Vec3> {
val value =
ObjectValue(
obj = this,
)
val value = ObjectValue(
obj = this
)
value.addFunction("x") { this.x }
value.addFunction("y") { this.y }
value.addFunction("z") { this.z }
@@ -277,66 +265,45 @@ object MoLangFunctions {
/**
* Different functions exist depending on the entity type, this tries to make the struct that's most specific to the type.
*/
fun Entity.asMostSpecificMoLangValue(): ObjectValue<out Entity> =
when (this) {
is Player -> {
asMoLangValue()
fun Entity.asMostSpecificMoLangValue(): ObjectValue<out Entity> {
return when (this) {
is Player -> asMoLangValue()
is PokemonEntity -> struct
is NPCEntity -> struct
is ItemEntity -> ObjectValue(this).also { item ->
item.addStandardFunctions()
item.addFunctions(EntityMoLangFunctions.attach(this))
item.addFunctions(ItemStackMoLangFunctions.attach(Pair(this.item, this.registryAccess())))
}
is PokemonEntity -> {
struct
}
is NPCEntity -> {
struct
}
is ItemEntity -> {
ObjectValue(this).also { item ->
item.addStandardFunctions()
item.addFunctions(EntityMoLangFunctions.attach(this))
item.addFunctions(ItemStackMoLangFunctions.attach(Pair(this.item, this.registryAccess())))
}
}
else -> {
ObjectValue(this).also { objectValue ->
objectValue.addStandardFunctions()
objectValue.addFunctions(EntityMoLangFunctions.attach(this))
if (this is LivingEntity) {
objectValue.addFunctions(LivingEntityMoLangFunctions.attach(this))
}
else -> ObjectValue(this).also { objectValue ->
objectValue.addStandardFunctions()
objectValue.addFunctions(EntityMoLangFunctions.attach(this))
if (this is LivingEntity) {
objectValue.addFunctions(LivingEntityMoLangFunctions.attach(this))
}
}
}
}
fun <T> Holder<T>.asMoLangValue(key: ResourceKey<Registry<T>>): ObjectValue<Holder<T>> {
val value =
ObjectValue(
obj = this,
stringify = {
it
.unwrapKey()
.get()
.location()
.toString()
},
)
value.functions["is_in"] =
Function put@{ params: MoParams ->
val tag = TagKey.create(key, ResourceLocation.parse(params.getString(0).replace("#", "")))
return@put DoubleValue(if (value.obj.`is`(tag)) 1.0 else 0.0)
}
value.functions["is_of"] =
Function put@{ params: MoParams ->
val identifier = ResourceLocation.parse(params.getString(0))
return@put DoubleValue(if (value.obj.`is`(identifier)) 1.0 else 0.0)
}
val value = ObjectValue(
obj = this,
stringify = { it.unwrapKey().get().location().toString() }
)
value.functions["is_in"] = Function put@{ params: MoParams ->
val tag = TagKey.create(key, ResourceLocation.parse(params.getString(0).replace("#", "")))
return@put DoubleValue(if (value.obj.`is`(tag)) 1.0 else 0.0)
}
value.functions["is_of"] = Function put@{ params: MoParams ->
val identifier = ResourceLocation.parse(params.getString(0))
return@put DoubleValue(if (value.obj.`is`(identifier)) 1.0 else 0.0)
}
return value
}
fun QueryStruct.addStandardFunctions(): QueryStruct {
addFunctions(GeneralMoLangFunctions.functions)
addFunctions(GeneralMoLangFunctions.holder)
return this
}
@@ -403,17 +370,23 @@ object MoLangFunctions {
return this
}
fun moLangFunctionMap(vararg functions: Pair<String, (MoParams) -> MoValue>): Map<String, (MoParams) -> MoValue> = functions.toMap()
fun moLangFunctionMap(
vararg functions: Pair<String, (MoParams) -> MoValue>
): Map<String, (MoParams) -> MoValue> {
return functions.toMap()
}
fun queryStructOf(vararg functions: Pair<String, (MoParams) -> MoValue>): QueryStruct =
QueryStruct(
fun queryStructOf(
vararg functions: Pair<String, (MoParams) -> MoValue>
): QueryStruct {
return QueryStruct(
hashMapOf<String, Function<MoParams, Any>>(
*functions
.map { (name, func) ->
name to Function<MoParams, Any> { params -> func(params) }
}.toTypedArray(),
),
*functions.map { (name, func) ->
name to Function<MoParams, Any> { params -> func(params) }
}.toTypedArray()
)
)
}
fun MoLangRuntime.setup(): MoLangRuntime {
environment.query.addStandardFunctions()
@@ -422,14 +395,8 @@ object MoLangFunctions {
fun writeMoValueToNBT(value: MoValue): Tag? {
return when (value) {
is DoubleValue -> {
DoubleTag.valueOf(value.value)
}
is StringValue -> {
StringTag.valueOf(value.value)
}
is DoubleValue -> DoubleTag.valueOf(value.value)
is StringValue -> StringTag.valueOf(value.value)
is ArrayStruct -> {
val list = value.map.values
val nbtList = ListTag()
@@ -446,22 +413,14 @@ object MoLangFunctions {
nbt
}
else -> {
null
}
else -> null
}
}
fun readMoValueFromNBT(nbt: Tag): MoValue =
when (nbt) {
is DoubleTag -> {
DoubleValue(nbt.asDouble)
}
is StringTag -> {
StringValue(nbt.asString)
}
fun readMoValueFromNBT(nbt: Tag): MoValue {
return when (nbt) {
is DoubleTag -> DoubleValue(nbt.asDouble)
is StringTag -> StringValue(nbt.asString)
is ListTag -> {
val array = ArrayStruct(hashMapOf())
var index = 0
@@ -482,12 +441,11 @@ object MoLangFunctions {
variable
}
else -> {
VariableStruct()
}
else -> VariableStruct()
}
}
}
// TODO: this one is unused. Should we move it to [MoLangFunctions] object?
fun Either<ResourceLocation, ExpressionLike>.runScript(runtime: MoLangRuntime) =
map({ CobblemonScripts.run(it, runtime) }, { it.resolve(runtime) })
map({ CobblemonScripts.run(it, runtime) }, { it.resolve(runtime) })
@@ -16,32 +16,31 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.util.asArrayValue
object BattleActorMoLangFunctions : AbstractMoLangFunctionHolder<BattleActor>() {
override fun BattleActor.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val actor = this
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["is_npc"] = { DoubleValue(actor.type == ActorType.NPC) }
functions["is_player"] = { DoubleValue(actor.type == ActorType.PLAYER) }
functions["is_wild"] = { DoubleValue(actor.type == ActorType.WILD) }
functions["battle_pokemon"] = { actor.pokemonList.asArrayValue { it.struct } }
val map = mutableMapOf<String, (MoParams) -> Any>(
"is_npc" to { DoubleValue(actor.type == ActorType.NPC) },
"is_player" to { DoubleValue(actor.type == ActorType.PLAYER) },
"is_wild" to { DoubleValue(actor.type == ActorType.WILD) }
)
when (actor) {
is NPCBattleActor -> {
functions["npc"] = { actor.entity.struct }
map["npc"] = { actor.entity.struct }
}
is PlayerBattleActor -> {
functions["player"] = { actor.entity?.asMoLangValue() ?: DoubleValue.ZERO }
map["player"] = { actor.entity?.asMoLangValue() ?: DoubleValue.ZERO }
}
is PokemonBattleActor -> {
functions["pokemon"] = { actor.entity?.asMoLangValue() ?: DoubleValue.ZERO }
map["pokemon"] = { actor.entity?.asMoLangValue() ?: DoubleValue.ZERO }
}
}
return functions
return map
}
}
@@ -16,19 +16,17 @@ import com.cobblemon.mod.common.util.getStringOrNull
object BattleMessageMoLangFunctions : AbstractMoLangFunctionHolder<BattleMessage>() {
override fun BattleMessage.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val battleMessage = this
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["has_argument_at"] = { params ->
val index = params.getInt(0)
val value = params.getStringOrNull(1) ?: ""
DoubleValue(value == battleMessage.argumentAt(index))
}
functions["has_argument"] = { params ->
val argumentName = params.getString(0)
val value = params.getStringOrNull(1) ?: ""
DoubleValue(value == battleMessage.optionalArgument(argumentName))
}
return functions
return mutableMapOf(
"has_argument_at" to { params ->
val index = params.getInt(0)
val value = params.getStringOrNull(1) ?: ""
DoubleValue(value == battleMessage.argumentAt(index))
},
"has_argument" to { params ->
val argumentName = params.getString(0)
val value = params.getStringOrNull(1) ?: ""
DoubleValue(value == battleMessage.optionalArgument(argumentName))
}
)
}
}
@@ -18,22 +18,20 @@ import java.util.*
object BattleMoLangFunctions : AbstractMoLangFunctionHolder<PokemonBattle>() {
override fun PokemonBattle.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val battle = this
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["environment"] = { battle.runtime.environment }
functions["battle_id"] = { StringValue(battle.battleId.toString()) }
functions["battle_type"] = { StringValue(battle.format.toString()) }
functions["is_pvn"] = { DoubleValue(battle.isPvN) }
functions["is_pvp"] = { DoubleValue(battle.isPvP) }
functions["is_pvw"] = { DoubleValue(battle.isPvW) }
functions["stop"] = { battle.stop() }
functions["actors"] = { battle.actors.toList().asArrayValue { it.struct } }
functions["get_actor"] = getActor@{ params ->
val uuid = UUID.fromString(params.getString(0))
val actor = battle.actors.find { it.uuid == uuid } ?: return@getActor DoubleValue.ZERO
actor.struct
}
return functions
return mutableMapOf(
"environment" to { battle.runtime.environment },
"battle_id" to { StringValue(battle.battleId.toString()) },
"battle_type" to { StringValue(battle.format.toString()) },
"is_pvn" to { DoubleValue(battle.isPvN) },
"is_pvp" to { DoubleValue(battle.isPvP) },
"is_pvw" to { DoubleValue(battle.isPvW) },
"stop" to { battle.stop() },
"actors" to { battle.actors.toList().asArrayValue { it.struct } },
"get_actor" to getActor@{ params ->
val uuid = UUID.fromString(params.getString(0))
val actor = battle.actors.find { it.uuid == uuid } ?: return@getActor DoubleValue.ZERO
actor.struct
}
)
}
}
@@ -12,11 +12,8 @@ import com.bedrockk.molang.runtime.MoParams
import net.minecraft.core.Holder
import net.minecraft.world.level.biome.Biome
object BiomeMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Biome>>() {
object BiomeMoLangFunctions: AbstractMoLangFunctionHolder<Holder<Biome>>() {
override fun Holder<Biome>.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val biome = this
val functions = hashMapOf<String, (MoParams) -> Any>()
return functions
return mutableMapOf()
}
}
@@ -12,11 +12,8 @@ import com.bedrockk.molang.runtime.MoParams
import net.minecraft.core.Holder
import net.minecraft.world.level.block.Block
object BlockMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Block>>() {
object BlockMoLangFunctions: AbstractMoLangFunctionHolder<Holder<Block>>() {
override fun Holder<Block>.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val block = this
val functions = hashMapOf<String, (MoParams) -> Any>()
return functions
return mutableMapOf()
}
}
@@ -14,9 +14,6 @@ import net.minecraft.world.level.dimension.DimensionType
object DimensionTypeMoLangFunctions : AbstractMoLangFunctionHolder<Holder<DimensionType>>() {
override fun Holder<DimensionType>.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val dimension = this
val functions = hashMapOf<String, (MoParams) -> Any>()
return functions
return mutableMapOf()
}
}
@@ -18,16 +18,15 @@ import com.cobblemon.mod.common.util.getOrNull
object DropEntryMoLangFunctions : AbstractMoLangFunctionHolder<DropEntry>() {
override fun DropEntry.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val drop = this
val functions = hashMapOf<String, (MoParams) -> Any>()
val map = mutableMapOf<String, (MoParams) -> Any>()
functions["percentage"] = { DoubleValue(drop.percentage.toDouble()) }
functions["quantity"] = { DoubleValue(drop.quantity.toDouble()) }
functions["max_selectable_times"] = { DoubleValue(drop.maxSelectableTimes.toDouble()) }
functions["can_drop"] = { params ->
map["percentage"] = { DoubleValue(drop.percentage.toDouble()) }
map["quantity"] = { DoubleValue(drop.quantity.toDouble()) }
map["max_selectable_times"] = { DoubleValue(drop.maxSelectableTimes.toDouble()) }
map["can_drop"] = { params ->
val pokemon = params.getOrNull<ObjectValue<Pokemon>>(0)?.obj
DoubleValue(if (drop.canDrop(pokemon)) 1.0 else 0.0)
}
return functions
return map
}
}
@@ -56,300 +56,258 @@ import kotlin.math.sqrt
object EntityMoLangFunctions : AbstractMoLangFunctionHolder<Entity>() {
override fun Entity.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val entity = this
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["uuid"] = { StringValue(entity.uuid.toString()) }
functions["name"] = { StringValue(entity.effectiveName().string) }
functions["yaw"] = { DoubleValue(entity.yRot.toDouble()) }
functions["pitch"] = { DoubleValue(entity.xRot.toDouble()) }
functions["x"] = { DoubleValue(entity.x) }
functions["y"] = { DoubleValue(entity.y) }
functions["z"] = { DoubleValue(entity.z) }
functions["velocity_x"] = { DoubleValue(entity.deltaMovement.x) }
functions["velocity_y"] = { DoubleValue(entity.deltaMovement.y) }
functions["velocity_z"] = { DoubleValue(entity.deltaMovement.z) }
functions["width"] = { DoubleValue(entity.boundingBox.xsize) }
functions["height"] = { DoubleValue(entity.boundingBox.ysize) }
functions["this_size"] = { DoubleValue(entity.boundingBox.run { if (xsize > ysize) xsize else ysize }) }
functions["this_width"] = { DoubleValue(entity.boundingBox.xsize) }
functions["this_height"] = { DoubleValue(entity.boundingBox.ysize) }
functions["id_modulo"] = { params -> DoubleValue(entity.uuid.hashCode() % params.getDouble(0)) }
functions["horizontal_velocity"] = { DoubleValue(entity.deltaMovement.horizontalDistance()) }
functions["vertical_velocity"] = { DoubleValue(entity.deltaMovement.y) }
functions["is_on_ground"] = { DoubleValue(entity.onGround()) }
functions["world"] = {
entity
.level()
.worldRegistry
.wrapAsHolder(entity.level())
.asWorldMoLangValue()
}
functions["biome"] = { entity.level().getBiome(entity.blockPosition()).asBiomeMoLangValue() }
functions["is_passenger"] = { DoubleValue(entity.isPassenger) }
functions["discard"] = { entity.discard() }
functions["is_sneaking"] = { DoubleValue(entity.isShiftKeyDown) }
functions["is_sprinting"] = { DoubleValue(entity.isSprinting) }
functions["is_in_water"] = { DoubleValue(entity.isUnderWater) }
functions["is_in_rain"] = { DoubleValue(entity.isInWaterOrRain && !entity.isInWater) }
functions["is_touching_water_or_rain"] = { DoubleValue(entity.isInWaterRainOrBubble) }
functions["is_touching_water"] = { DoubleValue(entity.isInWater) }
functions["is_underwater"] = { DoubleValue(entity.getIsSubmerged()) }
functions["is_in_lava"] = { DoubleValue(entity.isInLava) }
functions["is_on_fire"] = { DoubleValue(entity.isOnFire) }
functions["is_invisible"] = { DoubleValue(entity.isInvisible) }
functions["is_riding"] = { DoubleValue(entity.isPassenger) }
functions["set_name"] = { params ->
val name = params.getString(0)
entity.customName = name.text()
DoubleValue.ONE
}
functions["damage"] = { params ->
val amount = params.getDouble(0)
val source =
DamageSource(
entity
.level()
.registryAccess()
.registryOrThrow(Registries.DAMAGE_TYPE)
.getHolder(DamageTypes.GENERIC)
.get(),
val thisEntity = this
val map = mutableMapOf<String, (MoParams) -> Any>(
"uuid" to { StringValue(thisEntity.uuid.toString()) },
"name" to { _ -> StringValue(thisEntity.effectiveName().string) },
"yaw" to { _ -> DoubleValue(thisEntity.yRot.toDouble()) },
"pitch" to { _ -> DoubleValue(thisEntity.xRot.toDouble()) },
"x" to { _ -> DoubleValue(thisEntity.x) },
"y" to { _ -> DoubleValue(thisEntity.y) },
"z" to { _ -> DoubleValue(thisEntity.z) },
"velocity_x" to { _ -> DoubleValue(thisEntity.deltaMovement.x) },
"velocity_y" to { _ -> DoubleValue(thisEntity.deltaMovement.y) },
"velocity_z" to { _ -> DoubleValue(thisEntity.deltaMovement.z) },
"width" to { DoubleValue(thisEntity.boundingBox.xsize) },
"height" to { DoubleValue(thisEntity.boundingBox.ysize) },
"this_size" to { DoubleValue(thisEntity.boundingBox.run { if (xsize > ysize) xsize else ysize }) },
"this_width" to { DoubleValue(thisEntity.boundingBox.xsize) },
"this_height" to { DoubleValue(thisEntity.boundingBox.ysize) },
"id_modulo" to { params -> DoubleValue(thisEntity.uuid.hashCode() % params.getDouble(0)) },
"horizontal_velocity" to { _ -> DoubleValue(thisEntity.deltaMovement.horizontalDistance()) },
"vertical_velocity" to { DoubleValue(thisEntity.deltaMovement.y) },
"is_on_ground" to { _ -> DoubleValue(thisEntity.onGround()) },
"world" to { _ -> thisEntity.level().worldRegistry.wrapAsHolder(thisEntity.level()).asWorldMoLangValue() },
"biome" to { _ -> thisEntity.level().getBiome(thisEntity.blockPosition()).asBiomeMoLangValue() },
"is_passenger" to { DoubleValue(thisEntity.isPassenger) },
"discard" to { thisEntity.discard() },
"is_sneaking" to { _ -> DoubleValue(thisEntity.isShiftKeyDown) },
"is_sprinting" to { _ -> DoubleValue(thisEntity.isSprinting) },
"is_in_water" to { _ -> DoubleValue(thisEntity.isUnderWater) },
"is_in_rain" to { _ -> DoubleValue(thisEntity.isInWaterOrRain && !thisEntity.isInWater) },
"is_touching_water_or_rain" to { _ -> DoubleValue(thisEntity.isInWaterRainOrBubble) },
"is_touching_water" to { _ -> DoubleValue(thisEntity.isInWater) },
"is_underwater" to { DoubleValue(thisEntity.getIsSubmerged()) },
"is_in_lava" to { _ -> DoubleValue(thisEntity.isInLava) },
"is_on_fire" to { _ -> DoubleValue(thisEntity.isOnFire) },
"is_invisible" to { _ -> DoubleValue(thisEntity.isInvisible) },
"is_riding" to { _ -> DoubleValue(thisEntity.isPassenger) },
"set_name" to { params ->
val name = params.getString(0)
thisEntity.customName = name.text()
DoubleValue.ONE
},
"damage" to { params ->
val amount = params.getDouble(0)
val source = DamageSource(
thisEntity.level().registryAccess().registryOrThrow(Registries.DAMAGE_TYPE)
.getHolder(DamageTypes.GENERIC).get()
)
entity.hurt(source, amount.toFloat())
}
functions["distance_to_owner"] = distanceToOwner@{
if (entity !is TamableAnimal) {
return@distanceToOwner DoubleValue.ZERO
}
val owner = entity.owner ?: return@distanceToOwner DoubleValue.ZERO
DoubleValue(entity.distanceTo(owner))
}
functions["owner"] = owner@{
if (entity !is TamableAnimal) return@owner DoubleValue.ZERO
val owner = entity.owner
owner?.asMostSpecificMoLangValue() ?: DoubleValue.ZERO
}
functions["delta_movement"] = {
listOf(entity.deltaMovement.x, entity.deltaMovement.y, entity.deltaMovement.z)
.asArrayValue(::DoubleValue)
}
functions["tags"] = {
val tags = entity.tags
val array = ArrayStruct(hashMapOf())
tags.forEachIndexed { index, tag -> array.setDirectly("$index", StringValue(tag)) }
array
}
functions["add_tag"] = { params ->
val tag = params.getString(0)
entity.addTag(tag)
DoubleValue.ONE
}
functions["remove_tag"] = { params ->
val tag = params.getString(0)
entity.removeTag(tag)
DoubleValue.ONE
}
functions["has_tag"] = { params ->
val tag = params.getString(0)
DoubleValue(entity.tags.contains(tag))
}
functions["distance_to_pos"] = { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
DoubleValue(sqrt(entity.distanceToSqr(Vec3(x, y, z))))
}
functions["type"] = {
entity
.registryAccess()
.registry(Registries.ENTITY_TYPE)
.get()
.getKey(entity.type)
?.toString()
?.let {
thisEntity.hurt(source, amount.toFloat())
},
"distance_to_owner" to distanceToOwner@{
if (thisEntity !is TamableAnimal) {
return@distanceToOwner DoubleValue.ZERO
}
val owner = thisEntity.owner ?: return@distanceToOwner DoubleValue.ZERO
DoubleValue(thisEntity.distanceTo(owner))
},
"owner" to owner@{
if (thisEntity !is TamableAnimal) return@owner DoubleValue.ZERO
val owner = thisEntity.owner
owner?.asMostSpecificMoLangValue() ?: DoubleValue.ZERO
},
"delta_movement" to {
listOf(thisEntity.deltaMovement.x, thisEntity.deltaMovement.y, thisEntity.deltaMovement.z)
.asArrayValue(::DoubleValue)
},
"tags" to {
val tags = thisEntity.tags
val array = ArrayStruct(hashMapOf())
tags.forEachIndexed { index, tag -> array.setDirectly("$index", StringValue(tag)) }
array
},
"add_tag" to { params ->
val tag = params.getString(0)
thisEntity.addTag(tag)
DoubleValue.ONE
},
"remove_tag" to { params ->
val tag = params.getString(0)
thisEntity.removeTag(tag)
DoubleValue.ONE
},
"has_tag" to { params ->
val tag = params.getString(0)
DoubleValue(thisEntity.tags.contains(tag))
},
"distance_to_pos" to { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
DoubleValue(sqrt(thisEntity.distanceToSqr(Vec3(x, y, z))))
},
"type" to { _ ->
thisEntity.registryAccess().registry(Registries.ENTITY_TYPE).get().getKey(thisEntity.type)?.toString()?.let {
StringValue(it)
} ?: DoubleValue.ZERO
}
functions["find_nearby_block"] = findNearbyBlock@{ params ->
val input = params.getString(0)
val isTag = input.contains("#")
val type = input.replace("#", "").asIdentifierDefaultingNamespace(namespace = "minecraft")
val range = params.getDoubleOrNull(1) ?: 10
val blockPos =
entity
.level()
.getBlockStatesWithPos(
AABB.ofSize(
entity.position(),
range.toDouble(),
range.toDouble(),
range.toDouble(),
),
).filter { blockPosPair ->
blockPosPair.first.blockHolder.let {
if (isTag) {
it.`is`(
TagKey.create(
Registries.BLOCK,
type,
),
)
} else {
it.`is`(type)
}
}
}.minByOrNull { it.second.distSqr(entity.blockPosition()) }
?.second
if (blockPos != null) {
return@findNearbyBlock ArrayStruct(
mapOf(
"0" to DoubleValue(blockPos.x),
"1" to DoubleValue(blockPos.y),
"2" to DoubleValue(blockPos.z),
),
},
"find_nearby_block" to findNearbyBlock@{ params ->
val input = params.getString(0)
val isTag = input.contains("#")
val type = input.replace("#", "").asIdentifierDefaultingNamespace(namespace = "minecraft")
val range = params.getDoubleOrNull(1) ?: 10
val blockPos = thisEntity.level().getBlockStatesWithPos(
AABB.ofSize(
thisEntity.position(),
range.toDouble(),
range.toDouble(),
range.toDouble()
)
)
}
return@findNearbyBlock DoubleValue.ZERO
}
functions["get_nearby_entities"] = { params ->
val distance = params.getDouble(0)
val entities =
entity
.level()
.getEntities(entity, AABB.ofSize(entity.position(), distance, distance, distance))
.filter { blockPosPair ->
blockPosPair.first.blockHolder.let {
if (isTag) it.`is`(
TagKey.create(
Registries.BLOCK,
type
)
) else it.`is`(type)
}
}
.minByOrNull { it.second.distSqr(thisEntity.blockPosition()) }
?.second
if (blockPos != null) {
return@findNearbyBlock ArrayStruct(
mapOf(
"0" to DoubleValue(blockPos.x),
"1" to DoubleValue(blockPos.y),
"2" to DoubleValue(blockPos.z)
)
)
}
return@findNearbyBlock DoubleValue.ZERO
},
"get_nearby_entities" to { params ->
val distance = params.getDouble(0)
val entities =
thisEntity.level().getEntities(thisEntity, AABB.ofSize(thisEntity.position(), distance, distance, distance))
entities
.filterIsInstance<Entity>()
.map { it.asMostSpecificMoLangValue() }
.asArrayValue()
}
functions["is_standing_on_blocks"] = { params ->
val depth = params.getDouble(0).toInt()
val blockStrings: MutableSet<String> = mutableSetOf()
for (blockIndex in 1..<params.params.size) {
blockStrings.add(params.getString(blockIndex))
}
entities
.filterIsInstance<Entity>()
.map { it.asMostSpecificMoLangValue() }
.asArrayValue()
},
"is_standing_on_blocks" to { params ->
val depth = params.getDouble(0).toInt()
val blockStrings: MutableSet<String> = mutableSetOf()
for (blockIndex in 1..<params.params.size) {
blockStrings.add(params.getString(blockIndex))
}
if (entity.isStandingOn(blockStrings, depth)) DoubleValue.ONE else DoubleValue.ZERO
}
if (thisEntity.isStandingOn(blockStrings, depth)) DoubleValue.ONE else DoubleValue.ZERO
},
// q.entity.spawn_bedrock_particles(effect, locator, [player])
// sends to everyone nearby or just to the player if they're set.
// Locator is necessary even if unused on non-posables.
functions["spawn_bedrock_particles"] = { params ->
val particle = params.getString(0).asResource()
val locator = params.getString(1)
val player =
params.getOrNull<MoValue>(2)?.let {
// q.entity.spawn_bedrock_particles(effect, locator, [player])
// sends to everyone nearby or just to the player if they're set.
// Locator is necessary even if unused on non-posables.
"spawn_bedrock_particles" to { params ->
val particle = params.getString(0).asResource()
val locator = params.getString(1)
val player = params.getOrNull<MoValue>(2)?.let {
when (it) {
is StringValue -> entity.level().getPlayerByUUID(UUID.fromString(it.value))
is StringValue -> thisEntity.level().getPlayerByUUID(UUID.fromString(it.value))
is ObjectValue<*> -> it.obj
else -> null
}
} as? ServerPlayer
val packet = SpawnSnowstormEntityParticlePacket(particle, entity.id, listOf(locator))
if (player == null) {
packet.sendToPlayersAround(
entity.x,
entity.y,
entity.z,
64.0,
entity.level().dimension(),
)
} else {
packet.sendToPlayer(player)
val packet = SpawnSnowstormEntityParticlePacket(particle, thisEntity.id, listOf(locator))
if (player == null) {
packet.sendToPlayersAround(thisEntity.x, thisEntity.y, thisEntity.z, 64.0, thisEntity.level().dimension())
} else {
packet.sendToPlayer(player)
}
},
"make_intangible" to { params ->
val intangible = params.getBooleanOrNull(0) ?: true
thisEntity.noPhysics = intangible
DoubleValue.ONE
}
}
functions["make_intangible"] = { params ->
val intangible = params.getBooleanOrNull(0) ?: true
entity.noPhysics = intangible
DoubleValue.ONE
}
)
when (entity) {
is PosableEntity -> {
functions["play_animation"] = playAnimation@{ params ->
val animation = params.getString(0)
val packet = PlayPosableAnimationPacket(entity.id, setOf(animation), emptyList())
val target = params.getStringOrNull(1)
if (target != null) {
val targetPlayer =
if (target.asUUID != null) {
entity.level().getPlayerByUUID(target.asUUID!!) as ServerPlayer
} else if (entity.level() is ServerLevel) {
entity
.level()
.server!!
.playerList
.getPlayerByName(target)
} else {
null
}
if (targetPlayer != null) {
packet.sendToPlayer(targetPlayer)
return@playAnimation DoubleValue.ONE
} else {
return@playAnimation DoubleValue.ZERO
}
if (thisEntity is PosableEntity) {
map["play_animation"] = playAnimation@{ params ->
val animation = params.getString(0)
val packet = PlayPosableAnimationPacket(thisEntity.id, setOf(animation), emptyList())
val target = params.getStringOrNull(1)
if (target != null) {
val targetPlayer = if (target.asUUID != null) {
thisEntity.level().getPlayerByUUID(target.asUUID!!) as ServerPlayer
} else if (thisEntity.level() is ServerLevel) {
thisEntity.level().server!!.playerList.getPlayerByName(target)
} else {
packet.sendToPlayersAround(
entity.x,
entity.y,
entity.z,
64.0,
entity.level().dimension(),
)
null
}
if (targetPlayer != null) {
packet.sendToPlayer(targetPlayer)
return@playAnimation DoubleValue.ONE
}
}
functions["face"] = { params ->
ObjectValue(ReferenceDialogueFaceProvider(entity.id, params.getBooleanOrNull(0) != false))
}
}
is Schedulable -> {
functions["run_molang_after"] = { params ->
val expression = params.getString(0).asExpressionLike()
val delayInSeconds = params.getDouble(1).toFloat()
val runtime = MoLangRuntime()
runtime.environment.cloneFrom(params.environment)
entity.after(delayInSeconds) {
runtime.resolve(expression)
}
}
}
is MoLangScriptingEntity -> {
functions["add_callback"] = { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
val allowDuplicates = params.getBooleanOrNull(2) ?: false
entity.callbacks.addCallback(type, callback, allowDuplicates)
}
functions["remove_callback"] = { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
entity.callbacks.removeCallback(type, callback)
}
functions["has_callback"] = { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
DoubleValue(callback in (entity.callbacks[type] ?: emptySet()))
}
functions["clear_callbacks"] = { params ->
val type = params.getStringOrNull(0)?.asIdentifierDefaultingNamespace()
if (type != null) {
entity.callbacks[type]?.clear()
} else {
entity.callbacks.clear()
return@playAnimation DoubleValue.ZERO
}
DoubleValue.ONE
} else {
packet.sendToPlayersAround(thisEntity.x, thisEntity.y, thisEntity.z, 64.0, thisEntity.level().dimension())
return@playAnimation DoubleValue.ONE
}
}
map["face"] = { params ->
ObjectValue(ReferenceDialogueFaceProvider(thisEntity.id, params.getBooleanOrNull(0) != false))
}
}
if (thisEntity is Schedulable) {
map["run_molang_after"] = { params ->
val expression = params.getString(0).asExpressionLike()
val delayInSeconds = params.getDouble(1).toFloat()
val runtime = MoLangRuntime()
runtime.environment.cloneFrom(params.environment)
thisEntity.after(delayInSeconds) {
runtime.resolve(expression)
}
}
}
return functions
if (thisEntity is MoLangScriptingEntity) {
map["add_callback"] = { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
val allowDuplicates = params.getBooleanOrNull(2) ?: false
thisEntity.callbacks.addCallback(type, callback, allowDuplicates)
}
map["remove_callback"] = { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
thisEntity.callbacks.removeCallback(type, callback)
}
map["has_callback"] = { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
DoubleValue(callback in (thisEntity.callbacks[type] ?: emptySet()))
}
map["clear_callbacks"] = { params ->
val type = params.getStringOrNull(0)?.asIdentifierDefaultingNamespace()
if (type != null) {
thisEntity.callbacks[type]?.clear()
} else {
thisEntity.callbacks.clear()
}
DoubleValue.ONE
}
}
return map
}
}
@@ -19,26 +19,26 @@ import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
object EvolutionMoLangFunctions : AbstractMoLangFunctionHolder<Evolution>() {
override fun Evolution.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val evolution = this
val functions = hashMapOf<String, (MoParams) -> Any>()
val map = hashMapOf<String, (MoParams) -> Any>()
functions["result"] = { evolution.result.asMoLangValue() }
functions["is_optional"] = { DoubleValue(evolution.optional) }
functions["consumes_held_item"] = { DoubleValue(evolution.consumeHeldItem) }
map["result"] = { evolution.result.asMoLangValue() }
map["is_optional"] = { DoubleValue(evolution.optional) }
map["consumes_held_item"] = { DoubleValue(evolution.consumeHeldItem) }
when (evolution) {
is LevelUpEvolution -> {
functions["is_level_up"] = { DoubleValue.ONE }
map["is_level_up"] = { DoubleValue.ONE }
}
is TradeEvolution -> {
functions["is_trade"] = { DoubleValue.ONE }
map["is_trade"] = { DoubleValue.ONE }
}
is ItemInteractionEvolution -> {
functions["is_item"] = { DoubleValue.ONE }
map["is_item"] = { DoubleValue.ONE }
}
}
return functions
return map
}
}
@@ -42,104 +42,101 @@ import net.minecraft.world.item.ItemStack
import java.text.ParseException
object GeneralMoLangFunctions {
val functions: MutableMap<String, (MoParams) -> Any> = mutableMapOf()
init {
functions["print"] = { params ->
val holder: HashMap<String, (MoParams) -> Any> = hashMapOf(
"print" to { params ->
val message = params.get<MoValue>(0).asString()
LOGGER.info(message)
}
functions["delete_variable"] = { params ->
},
"delete_variable" to deleteVar@{ params ->
val struct = params.get<VariableStruct>(0)
val variable = params.getString(1)
struct.map.remove(variable)
DoubleValue.ONE
}
functions["delete_variables"] = { params ->
return@deleteVar DoubleValue.ONE
},
"delete_variables" to { params ->
val struct = params.get<VariableStruct>(0)
struct.map.clear()
DoubleValue.ONE
}
functions["get_variable"] = { params ->
},
"get_variable" to { params ->
val struct = params.get<VariableStruct>(0)
val variable = params.getString(1)
struct.map[variable] ?: DoubleValue.ZERO
}
functions["set_variable"] = { params ->
},
"set_variable" to { params ->
val struct = params.get<VariableStruct>(0)
val variable = params.getString(1)
val value = params.get<MoValue>(2)
struct.map[variable] = value
value
}
functions["set_query"] = { params ->
},
"set_query" to { params ->
val variable = params.getString(0)
val value = params.get<MoValue>(1)
params.environment.query.addFunction(variable) { value }
value
}
functions["replace"] = { params ->
},
"replace" to { params ->
val text = params.getString(0)
val search = params.getString(1)
val replace = params.getString(2)
StringValue(text.replace(search, replace))
}
functions["is_included"] = { params ->
},
"is_included" to { params ->
val text = params.getString(0)
val search = params.getString(1)
DoubleValue(text.contains(search))
}
functions["to_lower"] = { params ->
},
"to_lower" to { params ->
StringValue(params.getString(0).lowercase())
}
functions["to_upper"] = { params ->
},
"to_upper" to { params ->
StringValue(params.getString(0).uppercase())
}
functions["string_length"] = { params ->
},
"string_length" to { params ->
DoubleValue(params.getString(0).length)
}
functions["split_string"] = { params ->
},
"split_string" to { params ->
val text = params.getString(0)
val delimiter = params.getString(1)
val parts = text.split(delimiter).map { StringValue(it) }
val struct = ArrayStruct(hashMapOf())
parts.forEachIndexed { index, moValue -> struct.setDirectly("$index", moValue) }
struct
}
functions["is_blank"] = { params ->
},
"is_blank" to { params ->
val arg = params.get<MoValue>(0)
DoubleValue(
(arg is StringValue && (arg.value.isBlank() || arg.value.toDoubleOrNull() == 0.0)) ||
(arg is DoubleValue && arg.value == 0.0),
)
}
functions["run_command"] = { params ->
DoubleValue((arg is StringValue && (arg.value.isBlank() || arg.value.toDoubleOrNull() == 0.0)) || (arg is DoubleValue && arg.value == 0.0))
},
"run_command" to { params ->
val command = params.getString(0)
try {
server()!!.commands.performPrefixedCommand(server()!!.createCommandSourceStack(), command)
} catch (_: NullPointerException) {
DoubleValue.ZERO
}
}
functions["is_int"] = { params -> DoubleValue(params.get<MoValue>(0).asString().isInt()) }
functions["is_number"] = { params ->
},
"is_int" to { params -> DoubleValue(params.get<MoValue>(0).asString().isInt()) },
"is_number" to { params ->
DoubleValue(
params.get<MoValue>(0).asString().toDoubleOrNull() != null,
params.get<MoValue>(0).asString().toDoubleOrNull() != null
)
}
functions["to_number"] = { params ->
},
"to_number" to { params ->
DoubleValue(
params.get<MoValue>(0).asString().toDoubleOrNull() ?: 0.0,
params.get<MoValue>(0).asString().toDoubleOrNull() ?: 0.0
)
}
functions["to_int"] = { params ->
},
"to_int" to { params ->
DoubleValue(
params.get<MoValue>(0).asString().toIntOrNull() ?: 0,
params.get<MoValue>(0).asString().toIntOrNull() ?: 0
)
}
functions["to_string"] = { params -> StringValue(params.get<MoValue>(0).asString()) }
functions["do_effect_walks"] = { DoubleValue(Cobblemon.config.walkingInBattleAnimations) }
functions["random"] = { params ->
},
"to_string" to { params -> StringValue(params.get<MoValue>(0).asString()) },
"do_effect_walks" to { _ ->
DoubleValue(Cobblemon.config.walkingInBattleAnimations)
},
"random" to { params ->
val options = mutableListOf<MoValue>()
var index = 0
while (params.contains(index)) {
@@ -147,35 +144,35 @@ object GeneralMoLangFunctions {
index++
}
options.random() // Can throw an exception if they specified no args. They'd be idiots though.
}
functions["curve"] = { params ->
},
"curve" to { params ->
val curveName = params.getString(0)
val curve =
WaveFunctions.functions[curveName] ?: throw IllegalArgumentException("Unknown curve: $curveName")
ObjectValue(curve)
}
functions["array"] = { params ->
},
"array" to { params ->
val values = params.params
val array = ArrayStruct(hashMapOf())
values.forEachIndexed { index, moValue -> array.setDirectly("$index", moValue) }
array
}
functions["length"] = { params ->
},
"length" to { params ->
val array = (params.getOrNull<MoValue>(0) as? VariableStruct)
if (array == null) {
DoubleValue.ZERO
} else {
DoubleValue(array.map.size.toDouble())
}
}
functions["append"] = { params ->
},
"append" to { params ->
val array = params.get<ArrayStruct>(0)
val value = params.get<MoValue>(1)
val nextIndex = array.map.size
array.setDirectly("$nextIndex", value)
array
}
functions["insert"] = { params ->
},
"insert" to { params ->
val array = params.get<ArrayStruct>(0)
val index = params.getInt(1)
val value = params.get<MoValue>(2)
@@ -190,40 +187,36 @@ object GeneralMoLangFunctions {
}
array.map[index.toString()] = value
array
}
functions["delete"] = { params ->
},
"delete" to { 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() }
array.map.keys.mapNotNull { it.toIntOrNull() }
.sorted()
.forEachIndexed { i, k -> newMap[i.toString()] = array.map[k.toString()]!! }
array.map.clear()
array.map.putAll(newMap)
}
array
}
functions["run_script"] = { params ->
},
"run_script" to { params ->
val runtime = MoLangRuntime()
runtime.environment.query = params.environment.query
runtime.environment.variable = params.environment.variable
val args = params.params.subList(1, params.params.size)
runtime.environment.context =
ContextStruct(
(
params.environment.context?.map
?: emptyMap()
) + args.mapIndexed { index, value -> "arg_${index + 1}" to value }.toMap(),
)
runtime.environment.context = ContextStruct(
(params.environment.context?.map
?: emptyMap()) + args.mapIndexed { index, value -> "arg_${index + 1}" to value }.toMap()
)
val script = params.getString(0).asIdentifierDefaultingNamespace()
// store the args in the
CobblemonScripts.run(script, runtime) ?: DoubleValue.ZERO
}
functions["run_molang"] = { params ->
},
"run_molang" to { params ->
val runtime = MoLangRuntime()
runtime.environment.query = params.environment.query
runtime.environment.variable = params.environment.variable
@@ -231,16 +224,17 @@ object GeneralMoLangFunctions {
val expression = params.getString(0).asExpressionLike()
val delayInSeconds = params.getDoubleOrNull(1)?.toFloat() ?: 0.0f
fun evaluate(): MoValue =
try {
fun evaluate(): MoValue {
return try {
runtime.resolve(expression)
} catch (ex: Exception) {
LOGGER.warn(
"Failed to evaluate MoLang expression${if (delayInSeconds > 0.0f) " (delayed)" else ""}: $expression",
ex,
ex
)
DoubleValue.ZERO
}
}
if (delayInSeconds > 0.0f) {
val tracker =
@@ -252,55 +246,55 @@ object GeneralMoLangFunctions {
} else {
evaluate()
}
}
functions["system_time_millis"] = { DoubleValue(System.currentTimeMillis()) }
},
"system_time_millis" to { _ ->
DoubleValue(System.currentTimeMillis())
},
// the rest of the world use dd/MM/yyyy grow up america (this comment was generated by copilot)
functions["date_local_time"] = {
"date_local_time" to { _ ->
val time = System.currentTimeMillis()
val date = java.util.Date(time)
val formatted = java.text.SimpleDateFormat("dd/MM/yyyy").format(date)
StringValue(formatted)
}
functions["date_of"] = { params ->
},
"date_of" to { params ->
val time = params.getDouble(0).toLong()
val date = java.util.Date(time)
val formatted = java.text.SimpleDateFormat("dd/MM/yyyy").format(date)
StringValue(formatted)
}
functions["date_is_after"] = { params ->
},
"date_is_after" to { params ->
val dateA = params.getString(0)
val dateB = params.getString(1)
val format = java.text.SimpleDateFormat("dd/MM/yyyy")
val a =
try {
format.parse(dateA)
} catch (_: ParseException) {
throw RuntimeException("Cannot parse date $dateA, expected it to be in dd/MM/yyyy format, other provided")
}
val b =
try {
format.parse(dateB)
} catch (_: ParseException) {
throw RuntimeException("Cannot parse date $dateB, expected it to be in dd/MM/yyyy format, other provided")
}
val a = try {
format.parse(dateA)
} catch (_: ParseException) {
throw RuntimeException("Cannot parse date ${dateA}, expected it to be in dd/MM/yyyy format, other provided")
}
val b = try {
format.parse(dateB)
} catch (_: ParseException) {
throw RuntimeException("Cannot parse date ${dateB}, expected it to be in dd/MM/yyyy format, other provided")
}
DoubleValue(a.after(b))
}
functions["get_move_from_id"] = { params ->
},
"get_move_from_id" to { params ->
val moveId = params.getString(0)
val moveTemplate = Moves.getByName(moveId)
moveTemplate?.struct ?: DoubleValue.ZERO
}
functions["create_simple_party_provider"] = {
},
"create_simple_party_provider" to { _ ->
val partyProvider = SimplePartyProvider()
partyProvider.struct
}
functions["create_pickup_item"] = { params ->
},
"create_pickup_item" to { params ->
val item = params.getOrNull<MoValue>(0)?.asString()?.let(ObtainableItemCondition::parseFromString)
val pickupPriority = params.getIntOrNull(1) ?: 0
val pickupItem = ObtainableItem(item = item, pickupPriority = pickupPriority)
pickupItem.struct
}
functions["create_itemstack"] = { params ->
},
"create_itemstack" to { params ->
val itemId = params.getString(0).asIdentifierDefaultingNamespace()
try {
val item = BuiltInRegistries.ITEM.get(itemId)
@@ -311,7 +305,7 @@ object GeneralMoLangFunctions {
LOGGER.error("Cannot create item stack from identifier: $itemId, item with that ID does not exist. Possible typo?")
DoubleValue.ZERO
}
}
functions["file"] = { MoLangLoadedFilesCache.struct }
}
},
"file" to { MoLangLoadedFilesCache.struct }
)
}
@@ -26,44 +26,43 @@ object ItemStackMoLangFunctions : AbstractMoLangFunctionHolder<Pair<ItemStack, R
val registryAccess = this.second
val itemRegistry = registryAccess.registryOrThrow(Registries.ITEM)
val holder = itemRegistry.wrapAsHolder(stack.item)
val functions = mutableMapOf<String, (MoParams) -> Any>()
functions["item"] = { holder.asMoLangValue(Registries.ITEM) }
functions["count"] = { DoubleValue(stack.count.toDouble()) }
functions["damage_value"] = { DoubleValue(stack.damageValue) }
functions["max_damage"] = { DoubleValue(stack.maxDamage) }
functions["is_empty"] = { DoubleValue(stack.isEmpty) }
functions["shrink"] = { params -> stack.shrink(params.getInt(0)) }
functions["grow"] = { params -> stack.grow(params.getInt(0)) }
functions["is_of"] = { params ->
val map = hashMapOf<String, (MoParams) -> Any>()
map["item"] = { _ -> holder.asMoLangValue(Registries.ITEM) }
map["count"] = { _ -> DoubleValue(stack.count.toDouble()) }
map["damage_value"] = { _ -> DoubleValue(stack.damageValue) }
map["max_damage"] = { _ -> DoubleValue(stack.maxDamage) }
map["is_empty"] = { _ -> DoubleValue(stack.isEmpty) }
map["shrink"] = { params -> stack.shrink(params.getInt(0)) }
map["grow"] = { params -> stack.grow(params.getInt(0)) }
map["is_of"] = { params ->
DoubleValue(
holder.`is`(
params.getString(0).asIdentifierDefaultingNamespace(),
),
params.getString(0).asIdentifierDefaultingNamespace()
)
)
}
functions["is_in"] = { params ->
map["is_in"] = { params ->
DoubleValue(
holder.`is`(
TagKey.create(
Registries.ITEM,
params.getString(0).replace("#", "").asIdentifierDefaultingNamespace(),
),
),
params.getString(0).replace("#", "").asIdentifierDefaultingNamespace()
)
)
)
}
functions["is_food"] = { DoubleValue(stack.has(DataComponents.FOOD)) }
functions["is_enchanted"] = { DoubleValue(stack.hasFoil()) }
functions["has_enchantment"] = hasEnchantment@{ params ->
map["is_food"] = { _ -> DoubleValue(stack.has(DataComponents.FOOD)) }
map["is_enchanted"] = { _ -> DoubleValue(stack.hasFoil()) }
map["has_enchantment"] = put@{ params ->
val enchantmentId = params.getString(0).asIdentifierDefaultingNamespace()
val minLevel = params.getIntOrNull(1) ?: 1
val enchantmentRegistry = registryAccess.registryOrThrow(Registries.ENCHANTMENT)
val holder =
enchantmentRegistry.getHolder(enchantmentId).orElse(null) ?: return@hasEnchantment DoubleValue.ZERO
enchantmentRegistry.getHolder(enchantmentId).orElse(null) ?: return@put DoubleValue.ZERO
val level = EnchantmentHelper.getItemEnchantmentLevel(holder, stack)
DoubleValue(level >= minLevel)
return@put DoubleValue(level >= minLevel)
}
return functions
return map
}
}
@@ -53,27 +53,26 @@ import java.util.*
object LivingEntityMoLangFunctions : AbstractMoLangFunctionHolder<LivingEntity>() {
override fun LivingEntity.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val entity = this
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["is_player"] = { DoubleValue(entity is Player) }
functions["is_npc"] = { DoubleValue(entity is NPCEntity) }
functions["is_mob"] = { DoubleValue(entity is Mob) }
functions["is_pokemon"] = { DoubleValue(entity is PokemonEntity) }
functions["is_animal"] = { DoubleValue(entity is Animal) }
functions["is_tamable"] = { DoubleValue(entity is TamableAnimal) }
functions["is_tamed"] = { DoubleValue(entity is TamableAnimal && entity.isTame) }
functions["is_hostile"] = { DoubleValue(entity is Monster) }
functions["is_baby"] = { DoubleValue(entity.isBaby) }
functions["is_adult"] = { DoubleValue(!entity.isBaby) }
functions["remove_effect"] = removeEffect@{ params ->
val map = mutableMapOf<String, (MoParams) -> Any>()
map["is_player"] = { _ -> DoubleValue(entity is Player) }
map["is_npc"] = { _ -> DoubleValue(entity is NPCEntity) }
map["is_mob"] = { _ -> DoubleValue(entity is Mob) }
map["is_pokemon"] = { _ -> DoubleValue(entity is PokemonEntity) }
map["is_animal"] = { _ -> DoubleValue(entity is Animal) }
map["is_tamable"] = { _ -> DoubleValue(entity is TamableAnimal) }
map["is_tamed"] = { _ -> DoubleValue(entity is TamableAnimal && entity.isTame) }
map["is_hostile"] = { _ -> DoubleValue(entity is Monster) }
map["is_baby"] = { _ -> DoubleValue(entity.isBaby) }
map["is_adult"] = { _ -> DoubleValue(!entity.isBaby) }
map["remove_effect"] = put@{ params ->
val effectId = params.getString(0).asIdentifierDefaultingNamespace()
val effectHolder = BuiltInRegistries.MOB_EFFECT.getHolder(effectId).orElse(null)
if (effectHolder != null) {
entity.removeEffect(effectHolder)
return@removeEffect DoubleValue.ONE
return@put DoubleValue.ONE
}
}
functions["add_effect"] = addEffect@{ params ->
map["add_effect"] = put@{ params ->
val effectId = params.getString(0).asIdentifierDefaultingNamespace()
val duration = params.getInt(1)
val amplifier = params.getIntOrNull(2) ?: 0
@@ -82,37 +81,36 @@ object LivingEntityMoLangFunctions : AbstractMoLangFunctionHolder<LivingEntity>(
val effectHolder = BuiltInRegistries.MOB_EFFECT.getHolder(effectId).orElse(null)
if (effectHolder != null) {
entity.addEffect(MobEffectInstance(effectHolder, duration, amplifier, ambient, visible))
return@addEffect DoubleValue.ONE
return@put DoubleValue.ONE
}
}
functions["has_effect"] = { params ->
map["has_effect"] = put@{ params ->
val effectId = params.getString(0).asIdentifierDefaultingNamespace()
val effectHolder = BuiltInRegistries.MOB_EFFECT.getHolder(effectId).orElse(null)
DoubleValue(effectHolder != null && entity.hasEffect(effectHolder))
return@put DoubleValue(if (effectHolder != null) entity.hasEffect(effectHolder) else false)
}
functions["heal"] = { params ->
map["heal"] = { params ->
val amount = params.getDouble(0)
entity.heal(amount.toFloat())
}
functions["is_looking_at"] = isLookingAt@{ params ->
map["is_looking_at"] = put@{ params ->
val targetEntity = params.get<MoValue>(0)
val maxDistance = params.getDoubleOrNull(1)?.toFloat() ?: 2.0F
val target =
if (targetEntity is ObjectValue<*> && targetEntity.obj is Entity) {
targetEntity.obj as Entity
} else {
return@isLookingAt DoubleValue.ZERO
}
val entity = if (targetEntity is ObjectValue<*> && targetEntity.obj is Entity) {
targetEntity.obj as Entity
} else {
return@put DoubleValue.ZERO
}
DoubleValue(entity.isLookingAt(target, maxDistance))
return@put DoubleValue(entity.isLookingAt(entity, maxDistance))
}
functions["is_living_entity"] = { DoubleValue.ONE }
functions["is_flying"] = { DoubleValue(entity.isFallFlying) }
functions["is_sleeping"] = { DoubleValue(entity.isSleeping) }
functions["health"] = { DoubleValue(entity.health) }
functions["max_health"] = { DoubleValue(entity.maxHealth) }
functions["look_at_position"] = { params ->
map["is_living_entity"] = { DoubleValue.ONE }
map["is_flying"] = { _ -> DoubleValue(entity.isFallFlying) }
map["is_sleeping"] = { _ -> DoubleValue(entity.isSleeping) }
map["health"] = { _ -> DoubleValue(entity.health) }
map["max_health"] = { _ -> DoubleValue(entity.maxHealth) }
map["look_at_position"] = { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
@@ -122,174 +120,159 @@ object LivingEntityMoLangFunctions : AbstractMoLangFunctionHolder<LivingEntity>(
brain.setMemoryWithExpiry(
MemoryModuleType.LOOK_TARGET,
CobblemonBlockPosTracker(Vec3(x, y, z), flags.toSet()),
duration.toLong(),
duration.toLong()
)
}
functions["get_wander_control_memory"] = {
val value =
entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
?: CobblemonWanderControl()
map["get_wander_control_memory"] = put@{
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.checkMemory(CobblemonMemories.WANDER_CONTROL, MemoryStatus.VALUE_PRESENT)
&& entity.brain.checkMemory(CobblemonMemories.WANDER_CONTROL, MemoryStatus.REGISTERED)
) {
entity.brain.setMemory(CobblemonMemories.WANDER_CONTROL, value)
}
value.struct
return@put value.struct
}
functions["get_position_memory"] = getPosMemory@{ params ->
map["get_position_memory"] = put@{ params ->
val id = params.getString(0).asIdentifierDefaultingNamespace()
val memoryType = BuiltInRegistries.MEMORY_MODULE_TYPE.get(id)
if (entity.brain.checkMemory(memoryType, MemoryStatus.VALUE_PRESENT)) {
return@getPosMemory when (val memory = entity.brain.getMemory(memoryType).get()) {
is Vec3i -> {
VariableStruct(
mapOf(
"x" to DoubleValue(memory.x),
"y" to DoubleValue(memory.y),
"z" to DoubleValue(memory.z),
),
return@put when (val memory = entity.brain.getMemory(memoryType).get()) {
is Vec3i -> VariableStruct(
mapOf(
"x" to DoubleValue(memory.x),
"y" to DoubleValue(memory.y),
"z" to DoubleValue(memory.z)
)
}
)
is Position -> {
VariableStruct(
mapOf(
"x" to DoubleValue(memory.x()),
"y" to DoubleValue(memory.y()),
"z" to DoubleValue(memory.z()),
),
is Position -> VariableStruct(
mapOf(
"x" to DoubleValue(memory.x()),
"y" to DoubleValue(memory.y()),
"z" to DoubleValue(memory.z())
)
}
)
else -> {
DoubleValue.ZERO
}
else -> DoubleValue.ZERO
}
} else {
return@put DoubleValue.ZERO
}
DoubleValue.ZERO
}
functions["set_uuid_memory"] = setUuidMemory@{ params ->
val memory =
params
.getString(0)
.asIdentifierDefaultingNamespace()
.let(BuiltInRegistries.MEMORY_MODULE_TYPE::get) as MemoryModuleType<UUID>
val uuid = params.getString(1).asUUID ?: return@setUuidMemory DoubleValue.ZERO
map["set_uuid_memory"] = put@{ params ->
val memory = params.getString(0).asIdentifierDefaultingNamespace()
.let(BuiltInRegistries.MEMORY_MODULE_TYPE::get) as MemoryModuleType<UUID>
val uuid = params.getString(1).asUUID ?: return@put DoubleValue.ZERO
val expiry = params.getIntOrNull(2) ?: -1
if (expiry != -1) {
entity.brain.setMemoryWithExpiry(memory, uuid, expiry.toLong())
} else {
entity.brain.setMemory(memory, uuid)
}
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["erase_memory"] = { params ->
val memories =
params.params
.map { it.asString().asIdentifierDefaultingNamespace() }
.map(BuiltInRegistries.MEMORY_MODULE_TYPE::get)
map["erase_memory"] = put@{ params ->
val memories = params.params.map { it.asString().asIdentifierDefaultingNamespace() }
.map(BuiltInRegistries.MEMORY_MODULE_TYPE::get)
memories.forEach { entity.brain.eraseMemory(it) }
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["has_memory_value"] = hasMemoryValue@{ params ->
map["has_memory_value"] = put@{ params ->
for (param in params.params) {
if (!entity.hasMemoryFromString(param.asString())) {
return@hasMemoryValue DoubleValue.ZERO
return@put DoubleValue.ZERO
}
}
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["lacks_memory_value"] = lacksMemoryValue@{ params ->
map["lacks_memory_value"] = put@{ params ->
for (param in params.params) {
if (entity.hasMemoryFromString(param.asString())) {
return@lacksMemoryValue DoubleValue.ZERO
return@put DoubleValue.ZERO
}
}
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["is_doing_activity"] = isDoingActivity@{ params ->
map["is_doing_activity"] = put@{ params ->
for (param in params.params) {
if (entity.isDoingActivityFromString(param.asString())) {
return@isDoingActivity DoubleValue.ONE
return@put DoubleValue.ONE
}
}
DoubleValue.ZERO
return@put DoubleValue.ZERO
}
functions["can_see"] = canSee@{ params ->
map["can_see"] = put@{ params ->
val target = params.get<MoValue>(0)
val range = params.getDoubleOrNull(1) ?: 32.0
if (target is ObjectValue<*>) {
val targetEntity = target.obj as? LivingEntity ?: return@canSee DoubleValue.ZERO
val targetEntity = target.obj as? LivingEntity ?: return@put DoubleValue.ZERO
val vector = targetEntity.eyePosition.subtract(entity.eyePosition).normalize()
val trace =
entity.traceEntityCollision(
maxDistance = range.toFloat(),
stepDistance = 0.1F,
direction = vector,
entityClass = targetEntity.javaClass,
collideBlock = ClipContext.Fluid.NONE,
val trace = entity.traceEntityCollision(
maxDistance = range.toFloat(),
stepDistance = 0.1F,
direction = vector,
entityClass = targetEntity.javaClass,
collideBlock = ClipContext.Fluid.NONE
)
return@put DoubleValue(targetEntity in (trace?.entities ?: emptyList()))
} else {
return@put DoubleValue.ZERO
}
}
if (entity is PathfinderMob) {
map["walk_to"] = { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val speedMultiplier = params.getDoubleOrNull(3) ?: 0.35
if (entity.brain.checkMemory(MemoryModuleType.WALK_TARGET, MemoryStatus.REGISTERED)) {
entity.brain.setMemory(
MemoryModuleType.WALK_TARGET,
WalkTarget(Vec3(x, y, z), speedMultiplier.toFloat(), 1)
)
return@canSee DoubleValue(targetEntity in (trace?.entities ?: emptyList()))
}
DoubleValue.ZERO
}
when (entity) {
is PathfinderMob -> {
functions["walk_to"] = { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val speedMultiplier = params.getDoubleOrNull(3) ?: 0.35
if (entity.brain.checkMemory(MemoryModuleType.WALK_TARGET, MemoryStatus.REGISTERED)) {
entity.brain.setMemory(
MemoryModuleType.WALK_TARGET,
WalkTarget(Vec3(x, y, z), speedMultiplier.toFloat(), 1),
)
entity.brain.setMemory(
MemoryModuleType.LOOK_TARGET,
BlockPosTracker(Vec3(x, y + entity.eyeHeight, z)),
)
} else {
entity.navigation.moveTo(x, y, z, speedMultiplier)
entity.lookControl.setLookAt(Vec3(x, y + entity.eyeHeight, z))
}
}
functions["has_walk_target"] = {
DoubleValue(entity.brain.getMemorySafely(MemoryModuleType.WALK_TARGET).isPresent || entity.isPathFinding)
}
functions["set_can_float"] = { params ->
val canFloat = params.getBooleanOrNull(0) != false
entity.navigation.setCanFloat(canFloat)
DoubleValue.ONE
}
functions["get_pathfinding_malus"] = getPfMalus@{ params ->
val type = PathType.entries.find { it.name == params.getString(0).uppercase() }
if (type != null) {
return@getPfMalus DoubleValue(entity.getPathfindingMalus(type))
}
Cobblemon.LOGGER.error("Cannot get pathfinding type, unknown value: ${params.getString(0)}")
DoubleValue.ZERO
}
functions["set_pathfinding_malus"] = setPfMalus@{ params ->
val type = PathType.entries.find { it.name == params.getString(0).uppercase() }
val malus = params.getDouble(1).toFloat()
if (type != null) {
entity.setPathfindingMalus(type, malus)
return@setPfMalus DoubleValue.ONE
}
Cobblemon.LOGGER.error("Cannot set pathfinding type, unknown value: ${params.getString(0)}")
DoubleValue.ZERO
entity.brain.setMemory(
MemoryModuleType.LOOK_TARGET,
BlockPosTracker(Vec3(x, y + entity.eyeHeight, z))
)
} else {
entity.navigation.moveTo(x, y, z, speedMultiplier)
entity.lookControl.setLookAt(Vec3(x, y + entity.eyeHeight, z))
}
}
}
return functions
map["has_walk_target"] = { _ ->
DoubleValue(entity.brain.getMemorySafely(MemoryModuleType.WALK_TARGET).isPresent || entity.isPathFinding)
}
map["set_can_float"] = { params ->
val canFloat = params.getBooleanOrNull(0) != false
entity.navigation.setCanFloat(canFloat)
DoubleValue.ONE
}
map["get_pathfinding_malus"] = put@{ params ->
val type = PathType.entries.find { it.name == params.getString(0).uppercase() }
if (type != null) {
return@put DoubleValue(entity.getPathfindingMalus(type))
}
Cobblemon.LOGGER.error("Unknown pathfinding type: ${params.getString(0)}")
return@put DoubleValue.ZERO
}
map["set_pathfinding_malus"] = put@{ params ->
val type = PathType.entries.find { it.name == params.getString(0).uppercase() }
val malus = params.getDouble(1).toFloat()
if (type != null) {
entity.setPathfindingMalus(type, malus)
return@put DoubleValue.ONE
}
Cobblemon.LOGGER.error("Unknown pathfinding type: ${params.getString(0)}")
return@put DoubleValue.ZERO
}
}
return map
}
}
@@ -50,23 +50,18 @@ import kotlin.collections.any
object NPCMoLangFunctions : AbstractMoLangFunctionHolder<NPCEntity>() {
override fun NPCEntity.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val npc = this
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["class"] = { StringValue(npc.npc.id.toString()) }
functions["name"] = { StringValue(npc.name.string) }
functions["level"] = { DoubleValue(npc.level) }
functions["has_aspect"] = { params -> DoubleValue(npc.aspects.contains(params.getString(0))) }
functions["in_battle"] = { DoubleValue(npc.isInBattle()) }
functions["battles"] = {
ArrayStruct(
npc.battleIds
.mapNotNull { BattleRegistry.getBattle(it)?.struct }
.mapIndexed { index, value -> "$index" to value }
.toMap(),
)
val map = hashMapOf<String, (MoParams) -> Any>()
map["class"] = { StringValue(npc.npc.id.toString()) }
map["name"] = { StringValue(npc.name.string) }
map["level"] = { DoubleValue(npc.level) }
map["has_aspect"] = { params -> DoubleValue(npc.aspects.contains(params.getString(0))) }
map["in_battle"] = { DoubleValue(npc.isInBattle()) }
map["battles"] = {
ArrayStruct(npc.battleIds.mapNotNull { BattleRegistry.getBattle(it)?.struct }
.mapIndexed { index, value -> "$index" to value }.toMap())
}
functions["stop_battles"] = { npc.battleIds.forEach { BattleRegistry.getBattle(it)?.stop() } }
functions["run_script_on_client"] = { params ->
map["stop_battles"] = { _ -> npc.battleIds.forEach { BattleRegistry.getBattle(it)?.stop() } }
map["run_script_on_client"] = { params ->
val world = npc.level()
if (world is ServerLevel) {
val script = params.getString(0)
@@ -75,142 +70,141 @@ object NPCMoLangFunctions : AbstractMoLangFunctionHolder<NPCEntity>() {
}
Unit
}
functions["run_script"] = { params ->
map["run_script"] = { params ->
val script = params.getString(0).asIdentifierDefaultingNamespace()
val runtime = MoLangRuntime()
runtime.environment.cloneFrom(params.environment)
CobblemonScripts.run(script, runtime) ?: DoubleValue(0)
}
functions["set_movable"] = { params ->
map["set_movable"] = put@{ params ->
val movable = params.getBooleanOrNull(0) != false
npc.isMovable = movable
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["set_invulnerable"] = { params ->
map["set_invulnerable"] = put@{ params ->
val invulnerable = params.getBooleanOrNull(0) != false
npc.isInvulnerable = invulnerable
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["set_leashable"] = { params ->
map["set_leashable"] = put@{ params ->
val leashable = params.getBooleanOrNull(0) != false
npc.isLeashable = leashable
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["set_allow_projectile_hits"] = { params ->
map["set_allow_projectile_hits"] = put@{ params ->
val allowProjectileHits = params.getBooleanOrNull(0) != false
npc.allowProjectileHits = allowProjectileHits
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["set_name_tag_visible"] = { params ->
map["set_name_tag_visible"] = put@{ params ->
val nameTagVisible = params.getBooleanOrNull(0) != false
npc.hideNameTag = !nameTagVisible
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["unset_interaction"] = {
map["unset_interaction"] = put@{
npc.interaction = null
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["set_dialogue_interaction"] = { params ->
map["set_dialogue_interaction"] = put@{ params ->
val dialogue = params.getString(0).asIdentifierDefaultingNamespace()
npc.interaction =
DialogueNPCInteractionConfiguration().also {
it.dialogue = dialogue
}
DoubleValue.ONE
npc.interaction = DialogueNPCInteractionConfiguration().also {
it.dialogue = dialogue
}
return@put DoubleValue.ONE
}
functions["set_script_interaction"] = { params ->
map["set_script_interaction"] = put@{ params ->
val script = params.getString(0).asIdentifierDefaultingNamespace()
npc.interaction =
ScriptNPCInteractionConfiguration().also {
it.script = script
}
DoubleValue.ONE
npc.interaction = ScriptNPCInteractionConfiguration().also {
it.script = script
}
return@put DoubleValue.ONE
}
functions["set_player_texture"] = setPlayerTexture@{ params ->
map["set_player_texture"] = put@{ params ->
val username = params.getString(0)
// Re-applying it would be unnecessarily laggy.
if (username == npc.data.map["player_texture_username"]?.asString()) {
return@setPlayerTexture DoubleValue.ZERO
return@put DoubleValue.ZERO
}
npc.loadTextureFromGameProfileName(username)
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["unset_player_texture"] = {
map["unset_player_texture"] = put@{
npc.unloadTexture()
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["set_resource_identifier"] = { params ->
map["set_resource_identifier"] = put@{ params ->
val identifier = params.getStringOrNull(0)?.asIdentifierDefaultingNamespace()
npc.forcedResourceIdentifier = identifier
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["unset_resource_identifier"] = {
map["unset_resource_identifier"] = put@{
npc.forcedResourceIdentifier = null
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["set_class"] = setClass@{ params ->
map["set_class"] = put@{ params ->
val identifier = params.getString(0).asIdentifierDefaultingNamespace()
val npcClass = NPCClasses.getByIdentifier(identifier)
if (npcClass != null) {
npc.npc = npcClass
return@setClass DoubleValue.ONE
return@put DoubleValue.ONE
} else {
Cobblemon.LOGGER.error("Unknown NPC class: $identifier")
return@put DoubleValue.ZERO
}
Cobblemon.LOGGER.error("Unknown NPC class: $identifier")
DoubleValue.ZERO
}
functions["set_render_scale"] = { params ->
map["set_render_scale"] = put@{ params ->
val scale = params.getDouble(0)
npc.renderScale = scale.toFloat()
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["render_scale"] = { DoubleValue(npc.renderScale) }
functions["set_hitbox_scale"] = { params ->
map["render_scale"] = { _ -> DoubleValue(npc.renderScale) }
map["set_hitbox_scale"] = put@{ params ->
val scale = params.getDouble(0).toFloat()
npc.hitboxScale = scale
npc.refreshDimensions()
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["hitbox_scale"] = { DoubleValue(npc.hitboxScale) }
functions["set_hitbox"] = setHitbox@{ params ->
map["hitbox_scale"] = { _ -> DoubleValue(npc.hitboxScale) }
map["set_hitbox"] = put@{ params ->
if (params.params.isEmpty()) {
npc.hitbox = null
return@setHitbox DoubleValue.ONE
return@put DoubleValue.ONE
}
val width = params.getDouble(0).toFloat()
val height = params.getDouble(1).toFloat()
val eyeHeight = params.getDoubleOrNull(2)?.toFloat() ?: (height * 0.85F)
npc.hitbox = EntityDimensions.scalable(width, height).withEyeHeight(eyeHeight)
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["unset_hitbox"] = {
map["unset_hitbox"] = put@{
npc.hitbox = null
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["aspects"] = {
map["aspects"] = put@{
val aspects = npc.aspects
aspects.asArrayValue { StringValue(it) }
return@put aspects.asArrayValue { StringValue(it) }
}
functions["add_aspect"] = { params ->
map["add_aspect"] = put@{ params ->
val aspects = params.params.map { it.asString() }
npc.appliedAspects.addAll(aspects)
npc.updateAspects()
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["remove_aspect"] = { params ->
map["remove_aspect"] = put@{ params ->
val aspects = params.params.map { it.asString() }
npc.appliedAspects.removeAll(aspects.toSet())
npc.updateAspects()
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["party"] = party@{
val party = npc.party ?: return@party DoubleValue.ZERO
party.asMoLangValue()
map["party"] = put@{
val party = npc.party ?: return@put DoubleValue.ZERO
return@put party.asMoLangValue()
}
functions["create_npc_party"] = {
map["create_npc_party"] = put@{
val party = NPCPartyStore(npc)
party.asMoLangValue()
return@put party.asMoLangValue()
}
functions["set_npc_party"] = { params ->
map["set_npc_party"] = { params ->
val party = params.get<MoValue>(0)
if (party is ObjectValue<*>) {
npc.party = party.obj as NPCPartyStore
@@ -219,19 +213,18 @@ object NPCMoLangFunctions : AbstractMoLangFunctionHolder<NPCEntity>() {
}
DoubleValue.ONE
}
functions["run_action_effect"] = runActionEffect@{ params ->
map["run_action_effect"] = put@{ params ->
val runtime = MoLangRuntime().setup()
runtime.environment.cloneFrom(params.environment)
runtime.withNPCValue(value = npc)
val actionEffect = ActionEffects.actionEffects[params.getString(0).asIdentifierDefaultingNamespace()]
if (actionEffect != null) {
val context =
ActionEffectContext(
actionEffect = actionEffect,
providers = mutableListOf(NPCProvider(npc)),
runtime = runtime,
level = npc.level(),
)
val context = ActionEffectContext(
actionEffect = actionEffect,
providers = mutableListOf(NPCProvider(npc)),
runtime = runtime,
level = npc.level()
)
npc.actionEffect = context
npc.brain.setMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT, context)
npc.brain.setActiveActivityIfPossible(CobblemonActivities.ACTION_EFFECT)
@@ -243,40 +236,37 @@ object NPCMoLangFunctions : AbstractMoLangFunctionHolder<NPCEntity>() {
}
}
return@runActionEffect DoubleValue.ONE
return@put DoubleValue.ONE
}
DoubleValue.ZERO
return@put DoubleValue.ZERO
}
functions["put_pokemon_in_healer"] = putPokeInHealer@{ params ->
val healer =
npc
.level()
.getBlockEntity(params.get<ArrayStruct>(0).asBlockPos(), CobblemonBlockEntities.HEALING_MACHINE)
.orElse(null) ?: return@putPokeInHealer DoubleValue.ZERO
val party = npc.party ?: return@putPokeInHealer DoubleValue.ZERO
map["put_pokemon_in_healer"] = put@{ params ->
val healer = npc.level()
.getBlockEntity(params.get<ArrayStruct>(0).asBlockPos(), CobblemonBlockEntities.HEALING_MACHINE)
.orElse(null) ?: return@put DoubleValue.ZERO
val party = npc.party ?: return@put DoubleValue.ZERO
if (healer.canHeal(party)) {
healer.activate(npc.uuid, party)
return@putPokeInHealer DoubleValue.ONE
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
DoubleValue.ZERO
}
functions["look_at_position"] = { params ->
map["look_at_position"] = { params ->
val pos = Vec3(params.getDouble(0), params.getDouble(1), params.getDouble(2))
npc.lookAt(EntityAnchorArgument.Anchor.EYES, pos)
}
functions["environment"] = { npc.runtime.environment }
functions["party"] = { npc.party?.struct ?: DoubleValue.ZERO }
functions["has_party"] = { DoubleValue(npc.party != null) }
functions["is_npc"] = { DoubleValue.ONE }
functions["can_battle"] = {
DoubleValue(npc.party?.any { it.currentHealth > 0 } == true || npc.npc.party?.isStatic == false)
}
functions["set_battle_theme"] = { params ->
map["environment"] = { _ -> npc.runtime.environment }
map["party"] = { npc.party?.struct ?: DoubleValue.ZERO }
map["has_party"] = { DoubleValue(npc.party != null) }
map["is_npc"] = { DoubleValue.ONE }
map["can_battle"] =
{ DoubleValue(npc.party?.any { it.currentHealth > 0 } == true || npc.npc.party?.isStatic == false) }
map["set_battle_theme"] = put@{ params ->
val soundId = params.getString(0).asResource()
npc.npc.battleTheme = soundId
DoubleValue.ONE
return@put DoubleValue.ONE
}
return functions
return map
}
}
@@ -23,40 +23,40 @@ import com.cobblemon.mod.common.util.getBooleanOrNull
object PCMoLangFunctions : AbstractMoLangFunctionHolder<PCStore>() {
override fun PCStore.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val pc = this
val functions = hashMapOf<String, (MoParams) -> Any>()
val map = mutableMapOf<String, (MoParams) -> Any>()
functions["get_pokemon"] = getPokemon@{ params ->
map["get_pokemon"] = getPokemon@{ params ->
val box = params.getInt(0)
val slot = params.getInt(1)
val pokemon = pc[PCPosition(box, slot)] ?: return@getPokemon DoubleValue.ZERO
pokemon.struct
return@getPokemon pokemon.struct
}
functions["set_pokemon"] = { params ->
map["set_pokemon"] = { params ->
val box = params.getInt(0)
val slot = params.getInt(1)
val pokemon = params.get<ObjectValue<Pokemon>>(2).obj
pc[PCPosition(box, slot)] = pokemon
DoubleValue.ONE
}
functions["resize"] = { params ->
map["resize"] = { params ->
val newSize = params.getInt(0)
val lockNewSize = params.getBooleanOrNull(1) == true
pc.resize(newSize, lockNewSize)
DoubleValue.ONE
}
functions["get_box_count"] = { DoubleValue(pc.boxes.size.toDouble()) }
functions["has_unlocked_wallpaper"] = { params ->
map["get_box_count"] = { DoubleValue(pc.boxes.size.toDouble()) }
map["has_unlocked_wallpaper"] = { params ->
val wallpaper = params.getString(0).asIdentifierDefaultingNamespace()
DoubleValue(pc.unlockedWallpapers.contains(wallpaper))
}
functions["get_unlocked_wallpapers"] = { pc.unlockedWallpapers.asArrayValue { StringValue(it.toString()) } }
functions["unlock_wallpaper"] = unlockWallpaper@{
map["get_unlocked_wallpapers"] = { pc.unlockedWallpapers.asArrayValue { StringValue(it.toString()) } }
map["unlock_wallpaper"] = unlockWallpaper@{
val wallpaper = it.getString(0).asIdentifierDefaultingNamespace()
val playSound = it.getBooleanOrNull(1) != false
CobblemonUnlockableWallpapers.unlockableWallpapers[wallpaper] ?: return@unlockWallpaper DoubleValue.ZERO
DoubleValue(pc.unlockWallpaper(wallpaper, playSound))
return@unlockWallpaper DoubleValue(pc.unlockWallpaper(wallpaper, playSound))
}
return functions
return map
}
}
@@ -14,23 +14,21 @@ import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.pokemon.Pokemon
object PartyMoLangFunctions : AbstractMoLangFunctionHolder<PartyStore>() {
object PartyMoLangFunctions: AbstractMoLangFunctionHolder<PartyStore>() {
override fun PartyStore.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val party = this
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["get_pokemon"] = getPokemon@{ params ->
val map = hashMapOf<String, (MoParams) -> Any>()
map["get_pokemon"] = getPokemon@{ params ->
val index = params.getInt(0)
val pokemon = party.get(index) ?: return@getPokemon DoubleValue.ZERO
pokemon.struct
}
functions["set_pokemon"] = { params ->
map["set_pokemon"] = { params ->
val index = params.getInt(0)
val pokemon = params.get<ObjectValue<Pokemon>>(1).obj
party.set(index, pokemon)
DoubleValue.ONE
}
return functions
return map
}
}
@@ -17,12 +17,12 @@ import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.api.dialogue.PlayerDialogueFaceProvider
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.battles.BattleBuilder
import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.entity.npc.NPCEntity
@@ -52,389 +52,355 @@ import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundSource
import net.minecraft.stats.Stats
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.GameType
import kotlin.collections.iterator
object PlayerMoLangFunctions : AbstractMoLangFunctionHolder<Player>() {
override fun Player.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val player = this
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["username"] = { StringValue(player.gameProfile.name) }
functions["uuid"] = { StringValue(player.gameProfile.id.toString()) }
functions["main_held_item"] = { player.mainHandItem.asMoLangValue(player.registryAccess()) }
functions["off_held_item"] = { player.offhandItem.asMoLangValue(player.registryAccess()) }
functions["inventory"] = {
val map = mutableMapOf<String, (MoParams) -> Any>()
map["username"] = { _ -> StringValue(player.gameProfile.name) }
map["uuid"] = { _ -> StringValue(player.gameProfile.id.toString()) }
map["main_held_item"] = { _ -> player.mainHandItem.asMoLangValue(player.registryAccess()) }
map["off_held_item"] = { _ -> player.offhandItem.asMoLangValue(player.registryAccess()) }
map["inventory"] = put@{ _ ->
val inventory = player.inventory
val items = ArrayStruct(hashMapOf())
for (i in 0 until inventory.containerSize) {
items.setDirectly("$i", inventory.getItem(i).asMoLangValue(player.registryAccess()))
}
items
return@put items
}
functions["has_inventory_space"] = {
map["has_inventory_space"] = put@{ _ ->
val inventory = player.inventory
if (inventory.freeSlot != -1) DoubleValue.ONE else DoubleValue.ZERO
return@put if (inventory.getFreeSlot() != -1) DoubleValue.ONE else DoubleValue.ZERO
}
functions["set_inventory_slot"] = setInvSlot@{ params ->
map["set_inventory_slot"] = put@{ params ->
val slot = params.getInt(0)
val inventory = player.inventory
if (slot !in 0 until inventory.containerSize) return@setInvSlot DoubleValue.ZERO
if (slot !in 0 until inventory.containerSize) return@put DoubleValue.ZERO
val value = params.getOrNull<MoValue>(1) ?: return@setInvSlot DoubleValue.ZERO
val value = params.getOrNull<MoValue>(1) ?: return@put DoubleValue.ZERO
val stack: ItemStack? =
when (value) {
is ObjectValue<*> -> {
value.obj as? ItemStack
}
is StringValue -> {
val id = ResourceLocation.parse(value.value)
val item =
player.registryAccess().registryOrThrow(Registries.ITEM).get(id)
?: return@setInvSlot DoubleValue.ZERO
ItemStack(item)
}
else -> {
null
}
val stack: ItemStack? = when (value) {
is ObjectValue<*> -> value.obj as? ItemStack
is StringValue -> {
val id = ResourceLocation.parse(value.value)
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(id)
?: return@put DoubleValue.ZERO
ItemStack(item)
}
if (stack == null) return@setInvSlot DoubleValue.ZERO
else -> null
}
if (stack == null) return@put DoubleValue.ZERO
inventory.setItem(slot, stack)
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["give_item"] = giveItem@{ params ->
map["give_item"] = put@{ params ->
val inventory = player.inventory
val freeSlot = inventory.freeSlot
val value = params.getOrNull<MoValue>(0) ?: return@giveItem DoubleValue.ZERO
val freeSlot = inventory.getFreeSlot()
val value = params.getOrNull<MoValue>(0) ?: return@put DoubleValue.ZERO
val dropIfFull = params.getBooleanOrNull(1) ?: false
val stack: ItemStack? =
when (value) {
is ObjectValue<*> -> {
value.obj as? ItemStack
}
is StringValue -> {
val id = ResourceLocation.parse(value.value)
val item =
player.registryAccess().registryOrThrow(Registries.ITEM).get(id)
?: return@giveItem DoubleValue.ZERO
ItemStack(item)
}
else -> {
null
}
val stack: ItemStack? = when (value) {
is ObjectValue<*> -> value.obj as? ItemStack
is StringValue -> {
val id = ResourceLocation.parse(value.value)
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(id)
?: return@put DoubleValue.ZERO
ItemStack(item)
}
if (stack == null) return@giveItem DoubleValue.ZERO
else -> null
}
if (stack == null) return@put DoubleValue.ZERO
if (freeSlot == -1) {
if (dropIfFull) {
player.drop(stack, false)
return@giveItem DoubleValue.ONE
return@put DoubleValue.ONE
} else {
return@giveItem DoubleValue.ZERO
return@put DoubleValue.ZERO
}
}
inventory.setItem(freeSlot, stack)
DoubleValue.ONE
return@put DoubleValue.ONE
}
functions["face"] = { params ->
map["face"] = { params ->
ObjectValue(
PlayerDialogueFaceProvider(
player.uuid,
params.getBooleanOrNull(0) != false,
),
params.getBooleanOrNull(0) != false
)
)
}
functions["swing_hand"] = { player.swing(player.usedItemHand) }
functions["food_level"] = { DoubleValue(player.foodData.foodLevel) }
functions["saturation_level"] = { DoubleValue(player.foodData.saturationLevel) }
functions["tell"] = { params ->
map["swing_hand"] = { _ -> player.swing(player.usedItemHand) }
map["food_level"] = { _ -> DoubleValue(player.foodData.foodLevel) }
map["saturation_level"] = { _ -> DoubleValue(player.foodData.saturationLevel) }
map["tell"] = { params ->
val message = params.getString(0).text()
val overlay = params.getBooleanOrNull(1) == true
player.displayClientMessage(message, overlay)
}
functions["teleport"] = { params ->
map["teleport"] = { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val playParticleOptions = params.getBooleanOrNull(3) ?: false
player.randomTeleport(x, y, z, playParticleOptions)
val playParticleOptionss = params.getBooleanOrNull(3) ?: false
player.randomTeleport(x, y, z, playParticleOptionss)
}
functions["heal"] = { params ->
map["heal"] = { params ->
val amount = params.getDoubleOrNull(0) ?: player.maxHealth
player.heal(amount.toFloat())
}
functions["environment"] = {
map["environment"] = {
val environment = MoLangEnvironment()
environment.query = player.asMoLangValue()
environment
}
functions["is_player"] = { DoubleValue.ONE }
functions["riding_pokemon"] = ridingPokemon@{
map["is_player"] = { DoubleValue.ONE }
map["riding_pokemon"] = put@{
val vehicle = player.vehicle
if (vehicle is PokemonEntity) {
return@ridingPokemon vehicle.struct
return@put vehicle.struct
} else {
return@put DoubleValue.ZERO
}
DoubleValue.ZERO
}
when (player) {
is ServerPlayer -> {
functions["seen_credits"] = { DoubleValue(player.seenCredits) }
functions["is_in_dialogue"] = { DoubleValue(player.isInDialogue) }
functions["active_dialogue"] = activeDialogue@{
if (player.isInDialogue) {
return@activeDialogue player.activeDialogue?.toMoLangStruct() ?: DoubleValue.ZERO
}
if (player is ServerPlayer) {
map["seen_credits"] = { _ ->
DoubleValue(player.seenCredits)
}
map["is_in_dialogue"] = { _ ->
DoubleValue(player.isInDialogue)
}
map["active_dialogue"] = put@{ _ ->
if (player.isInDialogue) {
return@put player.activeDialogue?.toMoLangStruct() ?: DoubleValue.ZERO
} else {
DoubleValue.ZERO
}
functions["is_spectator"] = { DoubleValue(player.isSpectator) }
functions["is_creative"] = { DoubleValue(player.isCreative) }
functions["is_survival"] = { DoubleValue(player.gameMode.isSurvival) }
functions["is_adventure"] = { DoubleValue(player.gameMode.gameModeForPlayer == GameType.ADVENTURE) }
functions["run_command"] = { params ->
val command = params.getString(0)
player.server.commands.performPrefixedCommand(player.createCommandSourceStack(), command)
}
functions["set_battle_theme"] = { params ->
val soundId = params.getString(0).asResource()
Cobblemon.playerDataManager.getGenericData(player).battleTheme = soundId
DoubleValue.ONE
}
functions["battle_music"] = battleMusic@{ params ->
val soundId = params.getString(0).asResource()
val volume = params.getDoubleOrNull(1)?.toFloat() ?: 1.0f
val pitch = params.getDoubleOrNull(2)?.toFloat() ?: 1.0f
val restart = params.getBooleanOrNull(3) ?: true
}
map["is_spectator"] = { DoubleValue(player.isSpectator) }
map["is_creative"] = { DoubleValue(player.isCreative) }
map["is_survival"] = { DoubleValue(player.gameMode.isSurvival) }
map["is_adventure"] = { DoubleValue(player.gameMode.gameModeForPlayer == GameType.ADVENTURE) }
map["run_command"] = { params ->
val command = params.getString(0)
player.server.commands.performPrefixedCommand(player.createCommandSourceStack(), command)
}
map["set_battle_theme"] = put@{ params ->
val soundId = params.getString(0).asResource()
Cobblemon.playerDataManager.getGenericData(player).battleTheme = soundId
return@put DoubleValue.ONE
}
map["battle_music"] = put@{ params ->
val soundId = params.getString(0).asResource()
val volume = params.getDoubleOrNull(1)?.toFloat() ?: 1.0f
val pitch = params.getDoubleOrNull(2)?.toFloat() ?: 1.0f
val restart = params.getBooleanOrNull(3) ?: true
if (soundId != null) {
val packet = BattleMusicPacket(soundId, volume, pitch, restart)
packet.sendToPlayer(player)
return@battleMusic DoubleValue.ONE
}
DoubleValue.ZERO
}
functions["stop_battle_music"] = {
val packet = BattleMusicPacket(null)
if (soundId != null) {
val packet = BattleMusicPacket(soundId, volume, pitch, restart)
packet.sendToPlayer(player)
DoubleValue.ONE
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
functions["play_sound_on_server"] = { params ->
val sound = params.getString(0).asResource()
val soundSource = SoundSource.valueOf(params.getString(1).uppercase())
val volume = params.getDoubleOrNull(2)?.toFloat() ?: 1.0f
val pitch = params.getDoubleOrNull(3)?.toFloat() ?: 1.0f
}
map["stop_battle_music"] = put@{ _ ->
val packet = BattleMusicPacket(null)
packet.sendToPlayer(player)
return@put DoubleValue.ONE
}
map["play_sound_on_server"] = { params ->
val sound = params.getString(0).asResource()
val soundSource = SoundSource.valueOf(params.getString(1).uppercase())
val volume = params.getDoubleOrNull(2)?.toFloat() ?: 1.0f
val pitch = params.getDoubleOrNull(3)?.toFloat() ?: 1.0f
val packet =
UnvalidatedPlaySoundS2CPacket(
sound,
soundSource,
player.x,
player.y,
player.z,
volume,
pitch,
)
packet.sendToPlayer(player)
val packet =
UnvalidatedPlaySoundS2CPacket(sound, soundSource, player.x, player.y, player.z, volume, pitch)
packet.sendToPlayer(player)
}
map["is_party_at_full_health"] = { _ ->
DoubleValue(player.party().none(Pokemon::canBeHealed))
}
map["can_heal_at_healer"] = put@{ params ->
val pos = params.get<ArrayStruct>(0).asBlockPos()
val healer = player.level().getBlockEntity(pos, CobblemonBlockEntities.HEALING_MACHINE).orElse(null)
?: return@put DoubleValue.ZERO
val party = player.party()
return@put DoubleValue(healer.canHeal(party))
}
map["put_pokemon_in_healer"] = put@{ params ->
val healer = player.level()
.getBlockEntity(params.get<ArrayStruct>(0).asBlockPos(), CobblemonBlockEntities.HEALING_MACHINE)
.orElse(null) ?: return@put DoubleValue.ZERO
val party = player.party()
if (healer.canHeal(party)) {
healer.activate(player.uuid, party)
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
functions["is_party_at_full_health"] = { DoubleValue(player.party().none(Pokemon::canBeHealed)) }
functions["can_heal_at_healer"] = canHealAtHealer@{ params ->
val pos = params.get<ArrayStruct>(0).asBlockPos()
val healer =
player.level().getBlockEntity(pos, CobblemonBlockEntities.HEALING_MACHINE).orElse(null)
?: return@canHealAtHealer DoubleValue.ZERO
val party = player.party()
DoubleValue(healer.canHeal(party))
}
functions["put_pokemon_in_healer"] = putPokemonAtHealer@{ params ->
val healer =
player
.level()
.getBlockEntity(
params.get<ArrayStruct>(0).asBlockPos(),
CobblemonBlockEntities.HEALING_MACHINE,
).orElse(null) ?: return@putPokemonAtHealer DoubleValue.ZERO
val party = player.party()
if (healer.canHeal(party)) {
healer.activate(player.uuid, party)
return@putPokemonAtHealer DoubleValue.ONE
}
DoubleValue.ZERO
}
functions["party"] = { player.party().struct }
functions["pc"] = { player.pc().struct }
functions["has_permission"] = { params ->
DoubleValue(
Cobblemon.permissionValidator.hasPermission(
player,
params.getString(0),
params.getIntOrNull(1) ?: 4,
),
}
map["party"] = { player.party().struct }
map["pc"] = { player.pc().struct }
map["has_permission"] = { params ->
DoubleValue(
Cobblemon.permissionValidator.hasPermission(
player,
params.getString(0),
params.getIntOrNull(1) ?: 4
)
}
functions["data"] = { params -> Cobblemon.molangData.load(player.uuid, params.getStringOrNull(0)) }
functions["save_data"] = { params -> Cobblemon.molangData.save(player.uuid, params.getStringOrNull(0)) }
functions["in_battle"] = { DoubleValue(player.isInBattle()) }
functions["battle"] = { player.getBattleState()?.first?.struct ?: DoubleValue.ZERO }
functions["get_npc_data"] = getNpcData@{ params ->
val npcId =
((params.get<MoValue>(0) as? ObjectValue<*>)?.obj as? NPCEntity)?.stringUUID
?: params.getString(0)
val data = Cobblemon.molangData.load(player.uuid, params.getStringOrNull(1))
if (data.map.containsKey(npcId)) {
return@getNpcData data.map[npcId]!!
}
)
}
map["data"] = { params -> Cobblemon.molangData.load(player.uuid, params.getStringOrNull(0)) }
map["save_data"] = { params -> Cobblemon.molangData.save(player.uuid, params.getStringOrNull(0)) }
map["in_battle"] = { DoubleValue(player.isInBattle()) }
map["battle"] = { player.getBattleState()?.first?.struct ?: DoubleValue.ZERO }
map["get_npc_data"] = put@{ params ->
val npcId = ((params.get<MoValue>(0) as? ObjectValue<*>)?.obj as? NPCEntity)?.stringUUID
?: params.getString(0)
val data = Cobblemon.molangData.load(player.uuid, params.getStringOrNull(1))
if (data.map.containsKey(npcId)) {
return@put data.map[npcId]!!
} else {
val vars = VariableStruct()
data.map[npcId] = vars
vars
}
functions["get_npc_variable"] = getNpcVariable@{ params ->
val npcId =
((params.get<MoValue>(0) as? ObjectValue<*>)?.obj as? NPCEntity)?.stringUUID
?: params.getString(0)
val variable = params.getString(1)
val data = Cobblemon.molangData.load(player.uuid, params.getStringOrNull(2))
if (data.map.containsKey(npcId)) {
return@getNpcVariable (data.map[npcId] as VariableStruct).map[variable] ?: DoubleValue.ZERO
}
DoubleValue.ZERO
}
functions["set_npc_variable"] = { params ->
val npcId =
((params.get<MoValue>(0) as? ObjectValue<*>)?.obj as? NPCEntity)?.stringUUID
?: params.getString(0)
val variable = params.getString(1)
val value = params.get<MoValue>(2)
val saveAfterwards = params.getBooleanOrNull(3) != false
val path = params.getStringOrNull(4)
val data = Cobblemon.molangData.load(player.uuid, path)
val npcData = data.map.getOrPut(npcId) { VariableStruct() } as VariableStruct
npcData.map[variable] = value
if (saveAfterwards) {
Cobblemon.molangData.save(player.uuid, path)
}
DoubleValue.ONE
}
functions["pokedex"] = { player.pokedex().struct }
functions["has_advancement"] = hasAdvancement@{ params ->
val requiredAdvancement = ResourceLocation.parse(params.getString(0))
player.advancements.progress.forEach { (holder, progress) ->
if (holder.id == requiredAdvancement && progress.isDone) {
return@hasAdvancement DoubleValue.ONE
}
}
DoubleValue.ZERO
}
functions["has_tm_move_unlocked"] = hasTmMoveUnlocked@{ params ->
val move = Moves.getByName(params.getString(0)) ?: return@hasTmMoveUnlocked DoubleValue.ZERO
val tm = TechnicalMachines.moveToTM[move] ?: return@hasTmMoveUnlocked DoubleValue.ZERO
DoubleValue(if (player.tmList().learnedTMs.contains(tm.id)) 1.0 else 0.0)
}
functions["unlock_tm_move"] = unlockTmMove@{ params ->
val move = Moves.getByName(params.getString(0)) ?: return@unlockTmMove DoubleValue.ZERO
val tm = TechnicalMachines.moveToTM[move] ?: return@unlockTmMove DoubleValue.ZERO
player.tmList().syncTMFromMove(move)
DoubleValue(if (player.tmList().learnedTMs.contains(tm.id)) 1.0 else 0.0)
}
functions["lock_tm_move"] = lockTmMove@{ params ->
val move = Moves.getByName(params.getString(0)) ?: return@lockTmMove DoubleValue.ZERO
val tm = TechnicalMachines.moveToTM[move] ?: return@lockTmMove DoubleValue.ZERO
player.tmList().syncTMLockFromMove(move)
DoubleValue(if (player.tmList().learnedTMs.contains(tm.id)) 0.0 else 1.0)
}
functions["start_battle"] = startBattle@{ 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@startBattle DoubleValue.ZERO
} else {
server()?.playerList?.getPlayerByName(paramString)
?: return@startBattle 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 }
returnValue
}
functions["get_custom_stat"] = getCustomStat@{ params ->
val statName = params.getString(0)
val resourceLocation = ResourceLocation.tryParse(statName) ?: return@getCustomStat DoubleValue.ZERO
val statResourceLocation =
BuiltInRegistries.CUSTOM_STAT.get(resourceLocation) ?: return@getCustomStat DoubleValue.ZERO
val exists = Stats.CUSTOM.contains(statResourceLocation)
if (!exists) return@getCustomStat DoubleValue.ZERO
val stat = Stats.CUSTOM.get(statResourceLocation)
val value = player.stats.getValue(stat)
DoubleValue(value)
}
functions["get_starter_uuid"] = {
Cobblemon.playerDataManager.getGenericData(player).starterUUID?.let {
StringValue(
it.toString(),
)
} ?: DoubleValue.ZERO
}
functions["has_chosen_starter"] = {
DoubleValue(Cobblemon.playerDataManager.getGenericData(player).starterSelected)
return@put vars
}
}
map["get_npc_variable"] = put@{ params ->
val npcId = ((params.get<MoValue>(0) as? ObjectValue<*>)?.obj as? NPCEntity)?.stringUUID
?: params.getString(0)
val variable = params.getString(1)
val data = Cobblemon.molangData.load(player.uuid, params.getStringOrNull(2))
if (data.map.containsKey(npcId)) {
return@put (data.map[npcId] as VariableStruct).map[variable] ?: DoubleValue.ZERO
} else {
return@put DoubleValue.ZERO
}
}
map["set_npc_variable"] = put@{ params ->
val npcId = ((params.get<MoValue>(0) as? ObjectValue<*>)?.obj as? NPCEntity)?.stringUUID
?: params.getString(0)
val variable = params.getString(1)
val value = params.get<MoValue>(2)
val saveAfterwards = params.getBooleanOrNull(3) != false
val path = params.getStringOrNull(4)
val data = Cobblemon.molangData.load(player.uuid, path)
val npcData = data.map.getOrPut(npcId) { VariableStruct() } as VariableStruct
npcData.map[variable] = value
if (saveAfterwards) {
Cobblemon.molangData.save(player.uuid, path)
}
return@put DoubleValue.ONE
}
map["pokedex"] = { player.pokedex().struct }
map["has_advancement"] = put@{ params ->
val requiredAdvancement = ResourceLocation.parse(params.getString(0))
for (entry in player.advancements.progress) {
if (entry.key.id == requiredAdvancement && entry.value.isDone) {
return@put DoubleValue.ONE
}
}
return@put DoubleValue.ZERO
}
map["has_tm_move_unlocked"] = put@{ params ->
val move = Moves.getByName(params.getString(0)) ?: return@put DoubleValue.ZERO
val tm = TechnicalMachines.moveToTM[move] ?: return@put DoubleValue.ZERO
DoubleValue(if (player.tmList().learnedTMs.contains(tm.id)) 1.0 else 0.0)
}
map["unlock_tm_move"] = put@{ params ->
val move = Moves.getByName(params.getString(0)) ?: return@put DoubleValue.ZERO
val tm = TechnicalMachines.moveToTM[move] ?: return@put DoubleValue.ZERO
player.tmList().syncTMFromMove(move)
DoubleValue(if (player.tmList().learnedTMs.contains(tm.id)) 1.0 else 0.0)
}
map["lock_tm_move"] = put@{ params ->
val move = Moves.getByName(params.getString(0)) ?: return@put DoubleValue.ZERO
val tm = TechnicalMachines.moveToTM[move] ?: return@put DoubleValue.ZERO
player.tmList().syncTMLockFromMove(move)
DoubleValue(if (player.tmList().learnedTMs.contains(tm.id)) 0.0 else 1.0)
}
map["start_battle"] = put@{ 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["get_custom_stat"] = put@{ params ->
val statName = params.getString(0)
val resourceLocation = ResourceLocation.tryParse(statName) ?: return@put DoubleValue.ZERO
val statResourceLocation =
BuiltInRegistries.CUSTOM_STAT.get(resourceLocation) ?: return@put DoubleValue.ZERO
val exists = net.minecraft.stats.Stats.CUSTOM.contains(statResourceLocation)
if (!exists) return@put DoubleValue.ZERO
val stat = net.minecraft.stats.Stats.CUSTOM.get(statResourceLocation)
val value = player.stats.getValue(stat)
DoubleValue(value)
}
map["get_starter_uuid"] = { _ ->
Cobblemon.playerDataManager.getGenericData(player).starterUUID?.let {
StringValue(
it.toString()
)
} ?: DoubleValue.ZERO
}
map["has_chosen_starter"] = { _ -> DoubleValue(Cobblemon.playerDataManager.getGenericData(player).starterSelected) }
}
return functions
return map
}
}
@@ -16,7 +16,6 @@ import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
import com.cobblemon.mod.common.api.pokedex.CaughtCount
import com.cobblemon.mod.common.api.pokedex.CaughtPercent
import com.cobblemon.mod.common.api.pokedex.Dexes
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.PokedexManager
import com.cobblemon.mod.common.api.pokedex.SeenCount
@@ -28,90 +27,57 @@ import com.cobblemon.mod.common.util.getStringOrNull
object PokedexMoLangFunctions : AbstractMoLangFunctionHolder<AbstractPokedexManager>() {
override fun AbstractPokedexManager.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val pokedex = this
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["get_species_record"] = { params ->
val map = mutableMapOf<String, (MoParams) -> Any>()
map["get_species_record"] = { params ->
val speciesId = params.getString(0).asIdentifierDefaultingNamespace()
pokedex.speciesRecords[speciesId]?.struct ?: QueryStruct(hashMapOf())
}
functions["has_seen"] = hasSeen@{ params ->
map["has_seen"] = put@{ params ->
val speciesId = params.getString(0).asIdentifierDefaultingNamespace()
val formName =
params.getStringOrNull(1)
?: return@hasSeen DoubleValue(
pokedex.getHighestKnowledgeForSpecies(speciesId).ordinal >= PokedexEntryProgress.SEEN.ordinal,
)
val formName = params.getStringOrNull(1)
DoubleValue(
(
pokedex
.getSpeciesRecord(speciesId)
?.getFormRecord(formName)
?.knowledge
?.ordinal
?: 0
) >= PokedexEntryProgress.SEEN.ordinal,
)
}
functions["has_caught"] = hasCaught@{ params ->
val speciesId = params.getString(0).asIdentifierDefaultingNamespace()
val formName =
params.getStringOrNull(1) ?: return@hasCaught DoubleValue(
pokedex.getHighestKnowledgeForSpecies(
speciesId,
) == PokedexEntryProgress.OWNED,
if (formName == null) {
return@put DoubleValue(pokedex.getHighestKnowledgeForSpecies(speciesId).ordinal >= PokedexEntryProgress.SEEN.ordinal)
} else {
return@put DoubleValue(
(pokedex.getSpeciesRecord(speciesId)?.getFormRecord(formName)?.knowledge?.ordinal
?: 0) >= PokedexEntryProgress.SEEN.ordinal
)
DoubleValue(
pokedex
.getSpeciesRecord(speciesId)
?.getFormRecord(formName)
?.knowledge == PokedexEntryProgress.OWNED,
)
}
functions["caught_count"] = { DoubleValue(pokedex.getGlobalCalculatedValue(CaughtCount)) }
functions["seen_count"] = { DoubleValue(pokedex.getGlobalCalculatedValue(SeenCount)) }
functions["caught_percent"] = { DoubleValue(pokedex.getGlobalCalculatedValue(CaughtPercent)) }
functions["seen_percent"] = { DoubleValue(pokedex.getGlobalCalculatedValue(SeenPercent)) }
functions["dex_caught_count"] = { params ->
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, CaughtCount))
else DoubleValue.ZERO
}
functions["dex_seen_count"] = { params ->
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, SeenCount))
else DoubleValue.ZERO
}
functions["dex_caught_percent"] = { params ->
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, CaughtPercent).toDouble())
else DoubleValue.ZERO
}
functions["dex_seen_percent"] = { params ->
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, SeenPercent).toDouble())
else DoubleValue.ZERO
}
when (pokedex) {
is PokedexManager -> {
functions["player_id"] = { StringValue(pokedex.uuid.toString()) }
functions["see"] = { params ->
val pokemon = params.get<ObjectValue<Pokemon>>(0).obj
pokedex.encounter(pokemon)
DoubleValue.ONE
}
functions["catch"] = { params ->
val pokemon = params.get<ObjectValue<Pokemon>>(0).obj
pokedex.obtain(pokemon)
DoubleValue.ONE
}
}
}
return functions
map["has_caught"] = put@{ params ->
val speciesId = params.getString(0).asIdentifierDefaultingNamespace()
val formName = params.getStringOrNull(1)
if (formName == null) {
return@put DoubleValue(pokedex.getHighestKnowledgeForSpecies(speciesId) == PokedexEntryProgress.OWNED)
} else {
return@put DoubleValue(
pokedex.getSpeciesRecord(speciesId)
?.getFormRecord(formName)?.knowledge == PokedexEntryProgress.OWNED
)
}
}
map["caught_count"] = { DoubleValue(pokedex.getGlobalCalculatedValue(CaughtCount)) }
map["seen_count"] = { DoubleValue(pokedex.getGlobalCalculatedValue(SeenCount)) }
map["caught_percent"] = { DoubleValue(pokedex.getGlobalCalculatedValue(CaughtPercent)) }
map["seen_percent"] = { DoubleValue(pokedex.getGlobalCalculatedValue(SeenPercent)) }
if (pokedex is PokedexManager) {
map["player_id"] = { StringValue(pokedex.uuid.toString()) }
map["see"] = put@{ params ->
val pokemon = params.get<ObjectValue<Pokemon>>(0).obj
pokedex.encounter(pokemon)
return@put DoubleValue.ONE
}
map["catch"] = put@{ params ->
val pokemon = params.get<ObjectValue<Pokemon>>(0).obj
pokedex.obtain(pokemon)
return@put DoubleValue.ONE
}
}
return map
}
}
@@ -32,113 +32,100 @@ import com.cobblemon.mod.common.util.withQueryValue
object PokemonEntityMoLangFunctions : AbstractMoLangFunctionHolder<PokemonEntity>() {
override fun PokemonEntity.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val pokemonEntity = this
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["is_busy"] = { DoubleValue(pokemonEntity.isBusy) }
functions["in_battle"] = { DoubleValue(pokemonEntity.isBattling) }
functions["is_moving"] =
{ DoubleValue((pokemonEntity.moveControl as? PokemonMoveControl)?.hasWanted() == true) }
functions["is_flying"] = { DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.FLYING)) }
functions["get_riding_state"] = { params ->
val map = mutableMapOf<String, (MoParams) -> Any>()
map["is_busy"] = { DoubleValue(pokemonEntity.isBusy) }
map["in_battle"] = { DoubleValue(pokemonEntity.isBattling) }
map["is_moving"] = { DoubleValue((pokemonEntity.moveControl as? PokemonMoveControl)?.hasWanted() == true) }
map["is_flying"] = { DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.FLYING)) }
map["get_riding_state"] = { params ->
val name = params.getStringOrNull(0)
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
return@ifRidingAvailableSupply moValue.functions[name]?.apply(MoParams.EMPTY) as? MoValue ?: moValue
}
}
functions["is_gliding"] = {
map["is_gliding"] = {
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
return@ifRidingAvailableSupply moValue.functions["gliding"]?.apply(MoParams.EMPTY) as? MoValue
?: DoubleValue.ZERO
}
}
functions["is_sprinting"] = {
map["is_sprinting"] = {
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
return@ifRidingAvailableSupply moValue.functions["sprinting"]?.apply(MoParams.EMPTY) as? MoValue
?: DoubleValue.ZERO
}
}
functions["is_drifting"] = {
map["is_drifting"] = {
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
return@ifRidingAvailableSupply moValue.functions["drifting"]?.apply(MoParams.EMPTY) as? MoValue
?: DoubleValue.ZERO
}
}
functions["is_powered_drifting"] = {
map["is_powered_drifting"] = {
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
return@ifRidingAvailableSupply moValue.functions["powered_drifting"]?.apply(MoParams.EMPTY) as? MoValue
?: DoubleValue.ZERO
}
}
functions["in_air"] = {
map["in_air"] = {
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
return@ifRidingAvailableSupply moValue.functions["in_air"]?.apply(MoParams.EMPTY) as? MoValue
?: DoubleValue.ZERO
}
}
functions["is_wild"] = { DoubleValue(pokemonEntity.ownerUUID == null) }
functions["is_in_party"] = {
DoubleValue(
pokemonEntity.pokemon.storeCoordinates
.get()
?.store is PartyStore,
)
map["is_wild"] = { DoubleValue(pokemonEntity.ownerUUID == null) }
map["is_in_party"] = { DoubleValue(pokemonEntity.pokemon.storeCoordinates.get()?.store is PartyStore) }
map["is_ridden"] = { DoubleValue(pokemonEntity.hasControllingPassenger()) }
map["has_aspect"] = { DoubleValue(it.getString(0) in pokemonEntity.aspects) }
map["is_pokemon"] = { DoubleValue.ONE }
map["is_holding_item"] = {
DoubleValue(!pokemonEntity.entityData.get(PokemonEntity.SHOWN_HELD_ITEM).let {
it.isEmpty || it.`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS) || it.`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS)
})
}
functions["is_ridden"] = { DoubleValue(pokemonEntity.hasControllingPassenger()) }
functions["has_aspect"] = { DoubleValue(it.getString(0) in pokemonEntity.aspects) }
functions["is_pokemon"] = { DoubleValue.ONE }
functions["is_holding_item"] = {
DoubleValue(
!pokemonEntity.entityData.get(PokemonEntity.SHOWN_HELD_ITEM).let {
it.isEmpty || it.`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS) || it.`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS)
},
)
map["riding_style"] = {
StringValue(pokemonEntity.ifRidingAvailableSupply("") { behaviour, settings, state ->
behaviour.getRidingStyle(settings, state).name
})
}
functions["riding_style"] = {
StringValue(
pokemonEntity.ifRidingAvailableSupply("") { behaviour, settings, state ->
behaviour.getRidingStyle(settings, state).name
},
)
}
functions["is_wearing_hat"] = {
map["is_wearing_hat"] = {
DoubleValue(
pokemonEntity.entityData
.get(PokemonEntity.SHOWN_HELD_ITEM)
.`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS),
.`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS)
)
}
functions["is_wearing_face"] = {
map["is_wearing_face"] = {
DoubleValue(
pokemonEntity.entityData
.get(PokemonEntity.SHOWN_HELD_ITEM)
.`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS),
.`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS)
)
}
functions["is_pastured"] = {
map["is_pastured"] = {
DoubleValue((pokemonEntity.tethering != null))
}
functions["pasture_conflict_enabled"] = {
map["pasture_conflict_enabled"] = {
DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT))
}
functions["run_action_effect"] = runActionEffect@{ params ->
map["run_action_effect"] = runActionEffect@{ params ->
val runtime = MoLangRuntime().setup()
runtime.environment.cloneFrom(params.environment)
runtime.withQueryValue("entity", pokemonEntity.struct)
val actionEffect = ActionEffects.actionEffects[params.getString(0).asIdentifierDefaultingNamespace()]
if (actionEffect != null) {
val context =
ActionEffectContext(
actionEffect = actionEffect,
providers = mutableListOf(TargetsProvider(pokemonEntity)),
runtime = runtime,
level = pokemonEntity.level(),
)
val context = ActionEffectContext(
actionEffect = actionEffect,
providers = mutableListOf(TargetsProvider(pokemonEntity)),
runtime = runtime,
level = pokemonEntity.level()
)
// This was taken from NPC run_action_effect but this variable isn't used, so I just commented it out.
// pokemonEntity.actionEffect = context
pokemonEntity.brain.setMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT, context)
@@ -148,16 +135,14 @@ object PokemonEntityMoLangFunctions : AbstractMoLangFunctionHolder<PokemonEntity
pokemonEntity.brain.getMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT).orElse(null)
if (pokemonActionEffect == context && pokemonEntity.brain.isActive(CobblemonActivities.ACTION_EFFECT)) {
pokemonEntity.brain.eraseMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT)
// pokemonEntity.actionEffect = null
//pokemonEntity.actionEffect = null
}
}
return@runActionEffect DoubleValue.ONE
}
DoubleValue.ZERO
return@runActionEffect DoubleValue.ZERO
}
return functions
return map
}
}
@@ -33,214 +33,218 @@ import com.cobblemon.mod.common.util.getDoubleOrNull
import com.cobblemon.mod.common.util.getIntOrNull
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.util.toProperties
import net.minecraft.resources.ResourceLocation
import kotlin.random.Random
import net.minecraft.resources.ResourceLocation
object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
override fun Pokemon.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val pokemon = this
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["id"] = { StringValue(pokemon.uuid.toString()) }
functions["nickname"] = { StringValue(pokemon.nickname.toString()) }
functions["level"] = { DoubleValue(pokemon.level.toDouble()) }
functions["max_hp"] = { DoubleValue(pokemon.maxHealth.toDouble()) }
functions["current_hp"] = { DoubleValue(pokemon.currentHealth.toDouble()) }
functions["friendship"] = { DoubleValue(pokemon.friendship.toDouble()) }
functions["max_fullness"] = { DoubleValue(pokemon.getMaxFullness().toDouble()) }
functions["fullness"] = { DoubleValue(pokemon.currentFullness.toDouble()) }
functions["lose_fullness"] = { params ->
val map = mutableMapOf<String, (MoParams) -> Any>()
map["id"] = { StringValue(pokemon.uuid.toString()) }
map["nickname"] = { StringValue(pokemon.nickname.toString()) }
map["level"] = { DoubleValue(pokemon.level.toDouble()) }
map["max_hp"] = { DoubleValue(pokemon.maxHealth.toDouble()) }
map["current_hp"] = { DoubleValue(pokemon.currentHealth.toDouble()) }
map["friendship"] = { DoubleValue(pokemon.friendship.toDouble()) }
map["max_fullness"] = { DoubleValue(pokemon.getMaxFullness().toDouble()) }
map["fullness"] = { DoubleValue(pokemon.currentFullness.toDouble()) }
map["lose_fullness"] = { params ->
val amount = params.getDouble(0)
pokemon.loseFullness(amount.toInt())
}
functions["feed_pokemon"] = { params ->
map["feed_pokemon"] = { params ->
val amount = params.getDouble(0)
val playSound = params.getBooleanOrNull(1) ?: true
pokemon.feedPokemon(amount.toInt(), playSound)
}
functions["behaviour"] = { pokemon.form.behaviour.struct }
functions["behavior"] = { pokemon.form.behaviour.struct } // Inferior
functions["pokeball"] = { StringValue(pokemon.caughtBall.toString()) }
functions["ability"] = { StringValue(pokemon.ability.name) }
functions["has_learned"] = hasLearned@{ params ->
map["behaviour"] = { pokemon.form.behaviour.struct }
map["behavior"] = { pokemon.form.behaviour.struct } // Inferior
map["pokeball"] = { StringValue(pokemon.caughtBall.toString()) }
map["ability"] = { StringValue(pokemon.ability.name) }
map["has_learned"] = put@{ params ->
val moveName = params.getString(0)
val move = pokemon.allAccessibleMoves.find { it.name == moveName }
if (move != null) return@hasLearned DoubleValue.ONE
DoubleValue.ZERO
if (move != null) {
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
}
functions["moveset"] = {
map["moveset"] = {
val struct = QueryStruct(hashMapOf())
for ((index, move) in pokemon.moveSet.withIndex()) {
struct.addFunction(index.toString()) { move.struct }
}
struct
}
functions["evs"] = {
map["evs"] = {
val struct = QueryStruct(hashMapOf())
for (stat in Stats.PERMANENT) {
struct.addFunction(stat.showdownId) { DoubleValue(pokemon.evs.getOrDefault(stat).toDouble()) }
}
struct
}
functions["ivs"] = {
map["ivs"] = {
val struct = QueryStruct(hashMapOf())
for (stat in Stats.PERMANENT) {
struct.addFunction(stat.showdownId) { DoubleValue(pokemon.ivs.getOrDefault(stat).toDouble()) }
}
struct
}
functions["hyper_trained_ivs"] = {
map["hyper_trained_ivs"] = {
val struct = QueryStruct(hashMapOf())
for (stat in Stats.PERMANENT) {
struct.addFunction(stat.showdownId) { DoubleValue(pokemon.ivs.hyperTrainedIVs[stat] ?: -1.0) }
}
struct
}
functions["ride_boosts"] = {
map["ride_boosts"] = {
val struct = QueryStruct(hashMapOf())
for (stat in RidingStat.entries) {
struct.addFunction(stat.name.lowercase()) { DoubleValue(pokemon.getRideBoost(stat)) }
}
struct
}
functions["natdex_number"] = {
map["natdex_number"] = {
DoubleValue(pokemon.species.nationalPokedexNumber.toDouble())
}
functions["types"] = {
pokemon.form.types
.map { it.showdownId }
.asArrayValue(::StringValue)
map["types"] = {
pokemon.form.types.map { it.showdownId }.asArrayValue(::StringValue)
}
functions["gender_ratio"] = {
map["gender_ratio"] = {
DoubleValue(pokemon.form.maleRatio.toDouble())
}
functions["ev_yield"] = {
map["ev_yield"] = {
val struct = QueryStruct(hashMapOf())
for (stat in Stats.PERMANENT) {
struct.addFunction(stat.showdownId) { DoubleValue(pokemon.form.evYield[stat]?.toDouble()) }
}
struct
}
functions["base_stats"] = {
map["base_stats"] = {
val struct = QueryStruct(hashMapOf())
for (stat in Stats.PERMANENT) {
struct.addFunction(stat.showdownId) { DoubleValue(pokemon.form.baseStats[stat]?.toDouble()) }
}
struct
}
functions["catch_rate"] = {
map["catch_rate"] = {
DoubleValue(pokemon.form.catchRate.toDouble())
}
functions["base_experience_yield"] = {
map["base_experience_yield"] = {
DoubleValue(pokemon.form.baseExperienceYield.toDouble())
}
functions["drops"] = {
map["drops"] = {
val struct = QueryStruct(hashMapOf())
for ((index, drop) in pokemon.form.drops.entries
.withIndex()) {
for ((index, drop) in pokemon.form.drops.entries.withIndex()) {
struct.addFunction(index.toString()) { drop }
}
struct
}
functions["tm_learnset"] = {
map["tm_learnset"] = {
val struct = QueryStruct(hashMapOf())
for ((index, move) in pokemon.form.moves.tmMoves
.withIndex()) {
for ((index, move) in pokemon.form.moves.tmMoves.withIndex()) {
struct.addFunction(index.toString()) { move.struct }
}
struct
}
functions["egg_learnset"] = {
map["egg_learnset"] = {
val struct = QueryStruct(hashMapOf())
for ((index, move) in pokemon.form.moves.eggMoves
.withIndex()) {
for ((index, move) in pokemon.form.moves.eggMoves.withIndex()) {
struct.addFunction(index.toString()) { move.struct }
}
struct
}
functions["tutor_learnset"] = {
map["tutor_learnset"] = {
val struct = QueryStruct(hashMapOf())
for ((index, move) in pokemon.form.moves.tutorMoves
.withIndex()) {
for ((index, move) in pokemon.form.moves.tutorMoves.withIndex()) {
struct.addFunction(index.toString()) { move.struct }
}
struct
}
functions["level_learnset"] = {
map["level_learnset"] = {
val struct = QueryStruct(hashMapOf())
for ((index, move) in pokemon.form.moves.levelUpMoves) {
struct.addFunction(index.toString()) { move }
}
struct
}
functions["ability_pool"] = {
map["ability_pool"] = {
val struct = QueryStruct(hashMapOf())
for ((index, ability) in pokemon.form.abilities.withIndex()) {
struct.addFunction(index.toString()) { StringValue(ability.toString()) }
}
struct
}
functions["egg_groups"] = {
map["egg_groups"] = {
val struct = QueryStruct(hashMapOf())
for ((index, group) in pokemon.form.eggGroups.withIndex()) {
struct.addFunction(index.toString()) { StringValue(group.toString()) }
}
struct
}
functions["egg_cycles"] = {
map["egg_cycles"] = {
DoubleValue(pokemon.species.eggCycles.toDouble())
}
functions["labels"] = {
map["labels"] = {
val struct = QueryStruct(hashMapOf())
for ((index, label) in pokemon.form.labels.withIndex()) {
struct.addFunction(index.toString()) { StringValue(label) }
}
struct
}
functions["aspects"] = {
map["aspects"] = {
val aspects = pokemon.aspects
aspects.asArrayValue { StringValue(it) }
}
functions["form_aspects"] = {
map["form_aspects"] = {
val aspects = pokemon.form.aspects
aspects.asArrayValue { StringValue(it) }
}
functions["form_name"] = {
map["form_name"] = {
StringValue(pokemon.form.name)
}
// Yes, this is the lazy call for a single hardcoded pre-evolution.
// Lol. Lmao, even.
// TO-DO: Subscribe to [PokemonSpecies.observable].
functions["pre_evolution"] = { pokemon.species.preEvolution ?: DoubleValue.ZERO }
functions["nature"] = { StringValue(pokemon.nature.name.toString()) }
functions["is_wild"] = { DoubleValue(pokemon.entity?.let { it.ownerUUID == null } == true) }
functions["is_shiny"] = { DoubleValue(pokemon.shiny) }
functions["is_in_party"] = { DoubleValue(pokemon.storeCoordinates.get()?.store is PartyStore) }
functions["species"] = { pokemon.species.struct }
functions["form"] = { StringValue(pokemon.form.name) }
functions["weight"] = { DoubleValue(pokemon.species.weight.toDouble()) }
functions["matches"] = { params -> DoubleValue(params.getString(0).toProperties().matches(pokemon)) }
functions["apply"] = { params ->
// Gito: surely this is just a typo, right? not touching the function name just in case
map["prevolution"] = preEvolution@{
val preEvolution = pokemon.species.preEvolution ?: return@preEvolution DoubleValue.ZERO
return@preEvolution preEvolution
}
map["nature"] = { StringValue(pokemon.nature.toString()) }
map["is_wild"] = { DoubleValue(pokemon.entity?.let { it.ownerUUID == null } == true) }
map["is_shiny"] = { DoubleValue(pokemon.shiny) }
map["is_in_party"] = { DoubleValue(pokemon.storeCoordinates.get()?.store is PartyStore) }
map["species"] = { pokemon.species.struct }
map["form"] = { StringValue(pokemon.form.name) }
map["weight"] = { DoubleValue(pokemon.species.weight.toDouble()) }
map["matches"] = { params -> DoubleValue(params.getString(0).toProperties().matches(pokemon)) }
map["apply"] = { params ->
params.getString(0).toProperties().apply(pokemon)
DoubleValue.ONE
}
functions["owner"] = { pokemon.getOwnerPlayer()?.asMoLangValue() ?: DoubleValue.ZERO }
functions["held_item"] = { pokemon.heldItem().asMoLangValue(server()!!.registryAccess()) }
functions["remove_held_item"] = { pokemon.removeHeldItem() }
functions["add_aspects"] = { params ->
map["owner"] = { pokemon.getOwnerPlayer()?.asMoLangValue() ?: DoubleValue.ZERO }
map["held_item"] = { pokemon.heldItem().asMoLangValue(server()!!.registryAccess()) }
map["remove_held_item"] = { _ ->
pokemon.removeHeldItem()
}
map["add_aspects"] = { params ->
for (aspect in params.params) pokemon.forcedAspects += aspect.asString()
pokemon.updateAspects()
}
functions["remove_aspects"] = { params ->
map["remove_aspects"] = { params ->
for (aspect in params.params) pokemon.forcedAspects -= aspect.asString()
pokemon.updateAspects()
}
functions["cosmetic_item"] = {
map["cosmetic_item"] = {
pokemon.cosmeticItem().asMoLangValue(server()!!.registryAccess())
}
functions["remove_cosmetic_item"] = { pokemon.removeCosmeticItem() }
functions["marks"] = { pokemon.marks.asArrayValue { StringValue(it.identifier.toString()) } }
functions["has_mark"] = { params ->
map["remove_cosmetic_item"] = { _ ->
pokemon.removeCosmeticItem()
}
map["marks"] = { _ -> pokemon.marks.asArrayValue { StringValue(it.identifier.toString()) } }
map["has_mark"] = { params ->
var hasMark = false
val identifier = params.getString(0).asIdentifierDefaultingNamespace()
val mark = Marks.getByIdentifier(identifier)
@@ -250,7 +254,7 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
DoubleValue(hasMark)
}
functions["remove_marks"] = { params ->
map["remove_marks"] = { params ->
var removedMark = false
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
@@ -263,7 +267,7 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
DoubleValue(removedMark)
}
functions["add_marks"] = { params ->
map["add_marks"] = { params ->
var appliedMark = false
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
@@ -276,7 +280,7 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
DoubleValue(appliedMark)
}
functions["add_marks_with_chance"] = { params ->
map["add_marks_with_chance"] = { params ->
var appliedMark = false
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
@@ -294,15 +298,17 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
DoubleValue(appliedMark)
}
functions["add_potential_marks"] = { params ->
map["add_potential_marks"] = { params ->
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
val mark = Marks.getByIdentifier(identifier)
if (mark != null) pokemon.addPotentialMark(mark)
}
}
functions["apply_potential_marks"] = { DoubleValue(pokemon.applyPotentialMarks()) }
functions["hyper_train_iv"] = hyperTrainIv@{ params ->
map["apply_potential_marks"] = put@{
return@put DoubleValue(pokemon.applyPotentialMarks())
}
map["hyper_train_iv"] = hyperTrainIv@{ params ->
val statId = params.getString(0)
val stat = Stats.getStat(statId)
val value = params.getIntOrNull(1) ?: IVs.MAX_VALUE
@@ -315,12 +321,12 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
return@hyperTrainIv DoubleValue.ZERO
}
}
functions["add_exp"] = { params ->
map["add_exp"] = { params ->
val exp = params.getDouble(0).toInt()
pokemon.addExperience(SidemodExperienceSource("molang"), exp)
DoubleValue.ONE
}
functions["set_iv"] = setIv@{ params ->
map["set_iv"] = setIv@{ params ->
val statId = params.getString(0)
val stat = Stats.getStat(statId)
val value = params.getIntOrNull(1)?.coerceIn(0, IVs.MAX_VALUE) ?: IVs.MAX_VALUE
@@ -332,7 +338,7 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
return@setIv DoubleValue.ZERO
}
}
functions["set_ev"] = setEv@{ params ->
map["set_ev"] = setEv@{ params ->
val statId = params.getString(0)
val stat = Stats.getStat(statId)
val value = (params.getIntOrNull(1) ?: 0).coerceIn(0, 255)
@@ -344,54 +350,49 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
return@setEv DoubleValue.ZERO
}
}
functions["set_ride_boost"] = setRideBoost@{ params ->
map["set_ride_boost"] = setRideBoost@{ params ->
val statName = params.getString(0).uppercase()
val stat =
RidingStat.entries.find { it.name.equals(statName, ignoreCase = true) }
?: return@setRideBoost DoubleValue.ZERO
val stat = RidingStat.entries.find { it.name.equals(statName, ignoreCase = true) }
?: return@setRideBoost DoubleValue.ZERO
val value = params.getDoubleOrNull(1)?.toFloat() ?: return@setRideBoost DoubleValue.ZERO
pokemon.setRideBoost(stat, value)
DoubleValue.ONE
return@setRideBoost DoubleValue.ONE
}
functions["add_ride_boost"] = addRideBoost@{ params ->
map["add_ride_boost"] = addRideBoost@{ params ->
val statName = params.getString(0).uppercase()
val stat =
RidingStat.entries.find { it.name.equals(statName, ignoreCase = true) }
?: return@addRideBoost DoubleValue.ZERO
val stat = RidingStat.entries.find { it.name.equals(statName, ignoreCase = true) }
?: return@addRideBoost DoubleValue.ZERO
val value = params.getDoubleOrNull(1)?.toFloat() ?: return@addRideBoost DoubleValue.ZERO
DoubleValue(if (pokemon.addRideBoost(stat, value)) 1.0 else 0.0)
return@addRideBoost DoubleValue(if (pokemon.addRideBoost(stat, value)) 1.0 else 0.0)
}
functions["initialize_moveset"] = initializeMoveset@{ params ->
map["initialize_moveset"] = put@{ params ->
val param = params.get<MoValue>(0) ?: StringValue(pokemon.form.defaultWildMovesetBuilder.toString())
if (param is DoubleValue) {
pokemon.initializeMoveset(param == DoubleValue.ONE)
} else {
val movesetBuilderId = ResourceLocation.parse(param.asString())
val movesetBuilder =
CobblemonMovesetBuilders.movesetBuilders[movesetBuilderId]
?: run {
Cobblemon.LOGGER.error(
"Tried initializing moveset from moveset builder ${param.asString()} but it does not exist.",
)
return@initializeMoveset DoubleValue.ZERO
}
val movesetBuilder = CobblemonMovesetBuilders.movesetBuilders[movesetBuilderId]
?: run {
Cobblemon.LOGGER.error("Tried initializing moveset from moveset builder ${param.asString()} but it does not exist.")
return@put DoubleValue.ZERO
}
pokemon.initializeMovesetFrom(movesetBuilder)
}
DoubleValue.ONE
}
functions["validate_moveset"] = { params ->
map["validate_moveset"] = { params ->
val includeLegacy = params.getBooleanOrNull(0) ?: true
pokemon.validateMoveset(includeLegacy)
DoubleValue.ONE
}
functions["teach_learnable_moves"] = { params ->
map["teach_learnable_moves"] = { params ->
val includeLegacy = params.getBooleanOrNull(0) ?: true
pokemon.teachLearnableMoves(includeLegacy)
DoubleValue.ONE
}
functions["teach_move"] = teachMove@{ params ->
map["teach_move"] = teachMove@{ params ->
val moveName = params.getString(0)
val moveTemplate = Moves.getByName(moveName) ?: return@teachMove DoubleValue.ZERO
val bypass = params.getBooleanOrNull(1) ?: false
@@ -401,9 +402,8 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
return@teachMove DoubleValue.ZERO
}
val alreadyKnows =
pokemon.moveSet.getMoves().any { it.template == moveTemplate } ||
pokemon.benchedMoves.any { it.moveTemplate == moveTemplate }
val alreadyKnows = pokemon.moveSet.getMoves().any { it.template == moveTemplate } ||
pokemon.benchedMoves.any { it.moveTemplate == moveTemplate }
if (alreadyKnows) {
return@teachMove DoubleValue.ZERO
}
@@ -416,43 +416,42 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
DoubleValue.ONE
}
functions["can_learn_move"] = canLearnMove@{ params ->
map["can_learn_move"] = canLearnMove@{ params ->
val moveName = params.getString(0)
val moveTemplate = Moves.getByName(moveName) ?: return@canLearnMove DoubleValue.ZERO
val includeLegacy = params.getBooleanOrNull(1) ?: true
val canLearn =
if (includeLegacy) {
LearnsetQuery.ANY.canLearn(moveTemplate, pokemon.form.moves)
} else {
LearnsetQuery.LEGAL.canLearn(moveTemplate, pokemon.form.moves)
}
DoubleValue(if (canLearn) 1.0 else 0.0)
val canLearn = if (includeLegacy) {
LearnsetQuery.ANY.canLearn(moveTemplate, pokemon.form.moves)
} else {
LearnsetQuery.LEGAL.canLearn(moveTemplate, pokemon.form.moves)
}
return@canLearnMove DoubleValue(if (canLearn) 1.0 else 0.0)
}
functions["unlearn_move"] = unlearnMove@{ params ->
map["unlearn_move"] = unlearnMove@{ params ->
val moveName = params.getString(0)
val moveTemplate = Moves.getByName(moveName)
if (moveTemplate != null) {
pokemon.unlearnMove(moveTemplate)
return@unlearnMove DoubleValue.ONE
} else {
return@unlearnMove DoubleValue.ZERO
}
DoubleValue.ZERO
}
functions["can_evolve"] = { DoubleValue(pokemon.evolutions.any()) }
functions["force_evolve"] = forceEvolve@{ params ->
map["can_evolve"] = { _ ->
DoubleValue(pokemon.evolutions.any())
}
map["force_evolve"] = forceEvolve@{ params ->
val idx = params.getInt(0)
val evolution =
if (idx >= 0) pokemon.evolutions.elementAtOrNull(idx) else return@forceEvolve DoubleValue.ZERO
val evolution = if (idx >= 0) pokemon.evolutions.elementAtOrNull(idx) else return@forceEvolve DoubleValue.ZERO
evolution?.forceEvolve(pokemon)
StringValue(evolution.toString())
}
functions["entity"] = { pokemon.entity?.asMoLangValue() ?: DoubleValue.ZERO }
functions["is_alpha"] = { DoubleValue(pokemon.isAlpha) }
functions["size_category"] = { StringValue(pokemon.getSizeCategory().name) }
map["entity"] = { pokemon.entity?.asMoLangValue() ?: DoubleValue.ZERO }
map["is_alpha"] = { DoubleValue(pokemon.isAlpha) }
map["size_category"] = { _ -> StringValue(pokemon.getSizeCategory().name) }
return functions
return map
}
}
@@ -20,50 +20,52 @@ import com.cobblemon.mod.common.util.getStringOrNull
object PokemonPropertiesMoLangFunctions : AbstractMoLangFunctionHolder<PokemonProperties>() {
override fun PokemonProperties.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val props = this
val functions = hashMapOf<String, (MoParams) -> Any>()
val map = mutableMapOf<String, (MoParams) -> Any>()
functions["level"] = { DoubleValue(props.level?.toDouble() ?: 0) }
functions["set_level"] = { params ->
map["level"] = { DoubleValue(props.level?.toDouble() ?: 0) }
map["set_level"] = { params ->
props.level = params.getIntOrNull(0)
DoubleValue.ONE
}
functions["shiny"] = { DoubleValue(props.shiny) }
functions["set_shiny"] = { params ->
map["shiny"] = { DoubleValue(props.shiny) }
map["set_shiny"] = { params ->
props.shiny = params.getBooleanOrNull(0)
DoubleValue.ONE
}
functions["species"] = { props.species?.let { StringValue(it) } ?: DoubleValue.ZERO }
functions["set_species"] = { params ->
map["species"] = { props.species?.let { StringValue(it) } ?: DoubleValue.ZERO }
map["set_species"] = { params ->
props.species = params.getStringOrNull(0)
DoubleValue.ONE
}
functions["gender"] = { props.gender?.let { StringValue(it.name) } ?: DoubleValue.ZERO }
functions["set_gender"] = { params ->
map["gender"] = { props.gender?.let { StringValue(it.name) } ?: DoubleValue.ZERO }
map["set_gender"] = { params ->
props.gender = params.getStringOrNull(0)?.let { Gender.valueOf(it) }
DoubleValue.ONE
}
functions["form"] = { props.form?.let { StringValue(it) } ?: DoubleValue.ZERO }
functions["ivs"] = ivs@{
map["form"] = { props.form?.let { StringValue(it) } ?: DoubleValue.ZERO }
map["ivs"] = ivs@{
val ivs = props.ivs ?: return@ivs DoubleValue.ZERO
ivs.struct
return@ivs ivs.struct
}
functions["evs"] = evs@{
map["evs"] = evs@{
val evs = props.evs ?: return@evs DoubleValue.ZERO
evs.struct
return@evs evs.struct
}
functions["friendship"] = { DoubleValue(props.friendship?.toDouble() ?: DoubleValue.ZERO) }
functions["set_friendship"] = { params ->
map["friendship"] = { DoubleValue(props.friendship?.toDouble() ?: DoubleValue.ZERO) }
map["set_friendship"] = { params ->
props.friendship = params.getIntOrNull(0)
DoubleValue.ONE
}
functions["create"] = { (props.create()).struct }
functions["to_string"] = { params ->
map["create"] = {
val pokemon = props.create()
pokemon.struct
}
map["to_string"] = { params ->
val separator = params.getStringOrNull(0) ?: " "
StringValue(props.asString(separator = separator))
}
return functions
return map
}
}
@@ -22,84 +22,83 @@ import com.cobblemon.mod.common.util.asUUID
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.toProperties
object PokemonStoreMoLangFunctions : AbstractMoLangFunctionHolder<PokemonStore<*>>() {
object PokemonStoreMoLangFunctions: AbstractMoLangFunctionHolder<PokemonStore<*>>() {
override fun PokemonStore<*>.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val store = this
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["uuid"] = { StringValue(store.uuid.toString()) }
functions["add"] = { params ->
val map = mutableMapOf<String, (MoParams) -> Any>()
map["uuid"] = { StringValue(store.uuid.toString()) }
map["add"] = { params ->
val pokemon = params.get<ObjectValue<Pokemon>>(0)
DoubleValue(store.add(pokemon.obj))
}
functions["add_by_properties"] = { params ->
map["add_by_properties"] = { params ->
val props = params.getString(0).toProperties()
val player =
(store as? PlayerPartyStore)?.playerUUID?.getPlayer() // Only really know for sure when it's a player party store
val player = (store as? PlayerPartyStore)?.playerUUID?.getPlayer() // Only really know for sure when it's a player party store
val pokemon = props.create(player = player)
DoubleValue(store.add(pokemon))
}
functions["find_by_properties"] = { params ->
map["find_by_properties"] = { params ->
val props = params.getString(0).toProperties()
val pokemon = store.find { props.matches(it) }
pokemon?.asStruct() ?: DoubleValue.ZERO
}
functions["find_all_by_properties"] = { params ->
map["find_all_by_properties"] = { params ->
val props = params.getString(0).toProperties()
val pokemon = store.filter { props.matches(it) }
ArrayStruct(pokemon.mapIndexed { index, value -> "$index" to value.asStruct() }.toMap())
}
functions["find_by_id"] = { params ->
map["find_by_id"] = { params ->
val id = params.getString(0).asUUID
val pokemon = store.find { it.uuid == id }
pokemon?.asStruct() ?: DoubleValue.ZERO
}
functions["remove_by_id"] = removeById@{ params ->
map["remove_by_id"] = removeById@{ params ->
val id = params.getString(0).asUUID
val pokemon = store.find { it.uuid == id } ?: return@removeById DoubleValue.ZERO
DoubleValue(store.remove(pokemon))
return@removeById DoubleValue(store.remove(pokemon))
}
functions["average_level"] = averageLevel@{
map["average_level"] = averageLevel@{ _ ->
var numberOfPokemon = 0
var totalLevel = 0
for (pokemon in store) {
totalLevel += pokemon.level
numberOfPokemon++
}
if (numberOfPokemon == 0) return@averageLevel DoubleValue.ZERO
DoubleValue(totalLevel.toDouble() / numberOfPokemon)
if (numberOfPokemon == 0) {
return@averageLevel DoubleValue.ZERO
}
return@averageLevel DoubleValue(totalLevel.toDouble() / numberOfPokemon)
}
functions["count"] = { DoubleValue(store.count()) }
functions["count_by_properties"] = { params ->
map["count"] = { _ -> DoubleValue(store.count()) }
map["count_by_properties"] = { params ->
val props = params.getString(0).toProperties()
DoubleValue(store.count { props.matches(it) })
}
functions["highest_level"] = {
map["highest_level"] = {
val highest = store.maxOfOrNull { it.level } ?: 0
DoubleValue(highest)
}
functions["lowest_level"] = {
map["lowest_level"] = {
val lowest = store.minOfOrNull { it.level } ?: 0
DoubleValue(lowest)
}
functions["heal"] = {
map["heal"] = {
for (pokemon in store) {
pokemon.heal()
}
DoubleValue.ONE
}
functions["healing_remainder_percent"] = {
map["healing_remainder_percent"] = { _ ->
var totalPercent = 0.0f
for (pokemon in store) {
totalPercent += (1.0f - (pokemon.currentHealth.toFloat() / pokemon.maxHealth))
}
DoubleValue(totalPercent)
}
functions["has_usable_pokemon"] = { DoubleValue(store.any { !it.isFainted() }) }
functions["pokemon"] = { store.map { it.asStruct() }.asArrayValue() }
return functions
map["has_usable_pokemon"] = { _ -> DoubleValue(store.any { !it.isFainted() }) }
map["pokemon"] = {
store.map { it.asStruct() }.asArrayValue()
}
return map
}
}
@@ -25,63 +25,62 @@ import java.util.*
object ServerMoLangFunctions : AbstractMoLangFunctionHolder<MinecraftServer>() {
override fun MinecraftServer.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val server = this
val functions = hashMapOf<String, (MoParams) -> Any>()
val map = mutableMapOf<String, (MoParams) -> Any>()
functions["get_world"] = getWorld@{ params ->
val world =
server.getLevel(
ResourceKey.create(
Registries.DIMENSION,
params.getString(0).asIdentifierDefaultingNamespace(namespace = "minecraft"),
),
) ?: return@getWorld DoubleValue.ZERO
map["get_world"] = getWorld@{ params ->
val world = server.getLevel(
ResourceKey.create(
Registries.DIMENSION,
params.getString(0).asIdentifierDefaultingNamespace(namespace = "minecraft")
)
) ?: return@getWorld DoubleValue.ZERO
world
return@getWorld world
.registryAccess()
.registryOrThrow(Registries.DIMENSION)
.wrapAsHolder(world)
.asWorldMoLangValue()
}
functions["broadcast"] = { params ->
map["broadcast"] = { params ->
val message = params.getString(0)
server.playerList.broadcastSystemMessage(message.text(), params.getBooleanOrNull(1) == true)
DoubleValue.ONE
}
functions["get_player_by_uuid"] = getPlayerByUuid@{ params ->
map["get_player_by_uuid"] = getPlayerByUuid@{ params ->
val uuid = UUID.fromString(params.getString(0))
val player = server.playerList.getPlayer(uuid) ?: return@getPlayerByUuid DoubleValue.ZERO
player.asMoLangValue()
return@getPlayerByUuid player.asMoLangValue()
}
functions["get_player_by_username"] = getPlayerByUsername@{ params ->
map["get_player_by_username"] = getPlayerByUsername@{ params ->
val username = params.getString(0)
val player = server.playerList.getPlayerByName(username) ?: return@getPlayerByUsername DoubleValue.ZERO
player.asMoLangValue()
return@getPlayerByUsername player.asMoLangValue()
}
functions["data"] = { params ->
map["data"] = { params ->
Cobblemon.molangData.load(
UUID(0L, 0L),
params.getStringOrNull(0),
params.getStringOrNull(0)
)
}
functions["save_data"] = { params ->
map["save_data"] = { params ->
Cobblemon.molangData.save(
UUID(0L, 0L),
params.getStringOrNull(index = 0),
params.getStringOrNull(index = 0)
)
DoubleValue.ONE
}
// Maybe later...
// functions.put("stop") { params ->
// map.put("stop") { params ->
// Cobblemon.LOGGER.warn("Server is being stopped from a MoLang script.")
// server.stopServer()
// }
return functions
return map
}
}
@@ -20,34 +20,27 @@ import net.minecraft.server.level.ServerPlayer
object SpawnablePositionMoLangFunctions : AbstractMoLangFunctionHolder<SpawnablePosition>() {
override fun SpawnablePosition.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val spawningContext = this
val worldValue =
spawningContext
.world
.registryAccess()
.registryOrThrow(Registries.DIMENSION)
.wrapAsHolder(spawningContext.world)
.asWorldMoLangValue()
val biomeValue =
spawningContext
.biomeHolder
.asBiomeMoLangValue()
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["biome"] = { biomeValue }
functions["world"] = { worldValue }
functions["light"] = { DoubleValue(spawningContext.light.toDouble()) }
functions["x"] = { DoubleValue(spawningContext.position.x.toDouble()) }
functions["y"] = { DoubleValue(spawningContext.position.y.toDouble()) }
functions["z"] = { DoubleValue(spawningContext.position.z.toDouble()) }
functions["moon_phase"] = { DoubleValue(spawningContext.moonPhase.toDouble()) }
functions["can_see_sky"] = { DoubleValue(spawningContext.canSeeSky) }
functions["sky_light"] = { DoubleValue(spawningContext.skyLight.toDouble()) }
functions["player"] = player@{
val causeEntity = spawningContext.cause.entity ?: return@player DoubleValue.ZERO
if (causeEntity is ServerPlayer) return@player causeEntity.asMoLangValue()
DoubleValue.ZERO
val map = mutableMapOf<String, (MoParams) -> Any>()
val worldValue = spawningContext.world.registryAccess().registryOrThrow(Registries.DIMENSION)
.wrapAsHolder(spawningContext.world).asWorldMoLangValue()
val biomeValue = spawningContext.biomeHolder.asBiomeMoLangValue()
map["biome"] = { _ -> biomeValue }
map["world"] = { _ -> worldValue }
map["light"] = { _ -> DoubleValue(spawningContext.light.toDouble()) }
map["x"] = { _ -> DoubleValue(spawningContext.position.x.toDouble()) }
map["y"] = { _ -> DoubleValue(spawningContext.position.y.toDouble()) }
map["z"] = { _ -> DoubleValue(spawningContext.position.z.toDouble()) }
map["moon_phase"] = { _ -> DoubleValue(spawningContext.moonPhase.toDouble()) }
map["can_see_sky"] = { _ -> DoubleValue(spawningContext.canSeeSky) }
map["sky_light"] = { _ -> DoubleValue(spawningContext.skyLight.toDouble()) }
map["player"] = put@{ _ ->
val causeEntity = spawningContext.cause.entity ?: return@put DoubleValue.ZERO
if (causeEntity is ServerPlayer) {
return@put causeEntity.asMoLangValue()
} else {
return@put DoubleValue.ZERO
}
}
return functions
return map
}
}
@@ -17,23 +17,23 @@ import com.cobblemon.mod.common.util.asArrayValue
object SpeciesMoLangFunctions : AbstractMoLangFunctionHolder<Species>() {
override fun Species.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val species = this
val functions = hashMapOf<String, (MoParams) -> Any>()
val map = mutableMapOf<String, (MoParams) -> Any>(
"identifier" to { StringValue(species.resourceIdentifier.toString()) },
"name" to { StringValue(species.name) },
"primary_type" to { StringValue(species.primaryType.showdownId) },
"secondary_type" to { StringValue(species.secondaryType?.showdownId ?: "null") },
"experience_group" to { StringValue(species.experienceGroup.name) },
"height" to { DoubleValue(species.height) },
"weight" to { DoubleValue(species.weight) },
"base_scale" to { DoubleValue(species.baseScale) },
"hitbox_width" to { DoubleValue(species.hitbox.width) },
"hitbox_height" to { DoubleValue(species.hitbox.height) },
"hitbox_fixed" to { DoubleValue(species.hitbox.fixed) },
"catch_rate" to { DoubleValue(species.catchRate) },
"labels" to { species.labels.asArrayValue { StringValue(it) } },
"has_label" to { params -> DoubleValue(species.labels.contains(params.getString(0))) }
)
functions["identifier"] = { StringValue(species.resourceIdentifier.toString()) }
functions["name"] = { StringValue(species.name) }
functions["primary_type"] = { StringValue(species.primaryType.showdownId) }
functions["secondary_type"] = { StringValue(species.secondaryType?.showdownId ?: "null") }
functions["experience_group"] = { StringValue(species.experienceGroup.name) }
functions["height"] = { DoubleValue(species.height) }
functions["weight"] = { DoubleValue(species.weight) }
functions["base_scale"] = { DoubleValue(species.baseScale) }
functions["hitbox_width"] = { DoubleValue(species.hitbox.width) }
functions["hitbox_height"] = { DoubleValue(species.hitbox.height) }
functions["hitbox_fixed"] = { DoubleValue(species.hitbox.fixed) }
functions["catch_rate"] = { DoubleValue(species.catchRate) }
functions["labels"] = { species.labels.asArrayValue { StringValue(it) } }
functions["has_label"] = { params -> DoubleValue(species.labels.contains(params.getString(0))) }
return functions
return map
}
}
@@ -24,6 +24,7 @@ import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.net.messages.client.sound.UnvalidatedPlaySoundS2CPacket
import com.cobblemon.mod.common.util.*
import java.util.UUID
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.core.registries.Registries
@@ -45,89 +46,85 @@ import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets
import net.minecraft.world.level.storage.loot.parameters.LootContextParams
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import java.util.UUID
object WorldMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Level>>() {
override fun Holder<Level>.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val world = this.value()
val functions = hashMapOf<String, (MoParams) -> Any>()
val map = mutableMapOf<String, (MoParams) -> Any>(
)
functions["is_time_of_day"] = isTimeOfDay@{ params ->
val timeOfDay =
TimeRange.timeRanges[params.getString(0).lowercase()]
?: return@isTimeOfDay DoubleValue.ZERO
map["is_time_of_day"] = put@{ params ->
val timeOfDay = TimeRange.timeRanges[params.getString(0).lowercase()]
?: return@put DoubleValue.ZERO
val time = world.dayTime % 24000
DoubleValue(timeOfDay.contains(time.toInt()))
return@put DoubleValue(timeOfDay.contains(time.toInt()))
}
functions["game_time"] = { DoubleValue(world.gameTime.toDouble()) }
functions["time_of_day"] = { DoubleValue((world.dayTime % 24000).toDouble()) }
functions["server"] = { server()?.asMoLangValue() ?: DoubleValue.ZERO }
functions["is_raining_at"] = { params ->
map["game_time"] = { _ -> DoubleValue(world.gameTime.toDouble()) }
map["time_of_day"] = put@{
val time = world.dayTime % 24000
return@put DoubleValue(time.toDouble())
}
map["server"] = { _ -> server()?.asMoLangValue() ?: DoubleValue.ZERO }
map["is_raining_at"] = put@{ params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
DoubleValue(world.isRainingAt(BlockPos(x, y, z)))
return@put DoubleValue(world.isRainingAt(BlockPos(x, y, z)))
}
functions["is_snowing_at"] = { params ->
map["is_snowing_at"] = put@{ params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
val blockPos = BlockPos(x, y, z)
when {
!world.isRaining -> {
DoubleValue.ZERO
}
!world.canSeeSky(blockPos) -> {
DoubleValue.ZERO
}
!world.isRaining -> return@put DoubleValue.ZERO
!world.canSeeSky(blockPos) -> return@put DoubleValue.ZERO
(world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, blockPos).y > blockPos.y) -> {
DoubleValue.ZERO
return@put DoubleValue.ZERO
}
else -> {
val biome = world.getBiome(blockPos).value() as Biome
DoubleValue(biome.getPrecipitationAt(blockPos) == Biome.Precipitation.SNOW)
return@put DoubleValue(biome.getPrecipitationAt(blockPos) == Biome.Precipitation.SNOW)
}
}
}
functions["is_chunk_loaded_at"] = { params ->
map["is_chunk_loaded_at"] = put@{ params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
DoubleValue(world.isLoaded(BlockPos(x, y, z)))
return@put DoubleValue(world.isLoaded(BlockPos(x, y, z)))
}
functions["is_thundering"] = { DoubleValue(world.isThundering) }
functions["is_raining"] = { DoubleValue(world.isRaining) }
functions["set_block"] = setBlock@{ params ->
map["is_thundering"] = { _ -> DoubleValue(world.isThundering) }
map["is_raining"] = { _ -> DoubleValue(world.isRaining) }
map["set_block"] = put@{ params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
val block =
world.blockRegistry.get(params.getString(3).asIdentifierDefaultingNamespace())
?: run {
Cobblemon.LOGGER.error("Unknown block: ${params.getString(3)}")
return@setBlock DoubleValue.ZERO
}
val block = world.blockRegistry.get(params.getString(3).asIdentifierDefaultingNamespace())
?: run {
Cobblemon.LOGGER.error("Unknown block: ${params.getString(3)}")
return@put DoubleValue.ZERO
}
world.setBlock(BlockPos(x, y, z), block.defaultBlockState(), Block.UPDATE_ALL)
}
functions["is_air"] = { params ->
map["is_air"] = put@{ params ->
val x = params.getDouble(0).toInt()
val y = params.getDouble(1).toInt()
val z = params.getDouble(2).toInt()
val blockState = world.getBlockState(BlockPos(x, y, z))
DoubleValue(blockState.isAir)
return@put DoubleValue(blockState.isAir)
}
functions["get_block"] = { params ->
map["get_block"] = put@{ params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
val block = world.getBlockState(BlockPos(x, y, z)).block
world.blockRegistry.wrapAsHolder(block).asMoLangValue(Registries.BLOCK)
return@put world.blockRegistry.wrapAsHolder(block).asMoLangValue(Registries.BLOCK)
}
functions["spawn_explosion"] = { params ->
map["spawn_explosion"] = { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
@@ -139,33 +136,32 @@ object WorldMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Level>>() {
z,
range,
ExplosionInteraction.valueOf(
params.getStringOrNull(4)?.uppercase() ?: ExplosionInteraction.TNT.name,
),
params.getStringOrNull(4)?.uppercase() ?: ExplosionInteraction.TNT.name
)
)
}
functions["spawn_lightning"] = { params ->
map["spawn_lightning"] = put@{ params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val lightning = LightningBolt(EntityType.LIGHTNING_BOLT, world)
lightning.setPos(x, y, z)
world.addFreshEntity(lightning)
DoubleValue.ONE
return@put DoubleValue.ONE
}
// q.entity.world.spawn_bedrock_particles(effect, x, y, z, [player]) - sends to everyone nearby or just to the player if they're set.
functions["spawn_bedrock_particles"] = { params ->
map["spawn_bedrock_particles"] = { params ->
val particle = params.getString(0).asResource()
val x = params.getDouble(1)
val y = params.getDouble(2)
val z = params.getDouble(3)
val player =
params.getOrNull<MoValue>(4)?.let {
when (it) {
is StringValue -> world.getPlayerByUUID(UUID.fromString(it.value))
is ObjectValue<*> -> it.obj
else -> null
}
} as? ServerPlayer
val player = params.getOrNull<MoValue>(4)?.let {
when (it) {
is StringValue -> world.getPlayerByUUID(UUID.fromString(it.value))
is ObjectValue<*> -> it.obj
else -> null
}
} as? ServerPlayer
val pos = Vec3(x, y, z)
val packet = SpawnSnowstormParticlePacket(particle, pos)
@@ -175,7 +171,7 @@ object WorldMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Level>>() {
packet.sendToPlayersAround(x, y, z, 64.0, world.dimension())
}
}
functions["spawn_pokemon"] = spawnPokemon@{ params ->
map["spawn_pokemon"] = put@{ params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
@@ -183,50 +179,49 @@ object WorldMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Level>>() {
val pos = BlockPos(x, y, z)
if (!Level.isInSpawnableBounds(pos)) return@spawnPokemon DoubleValue.ZERO
if (!Level.isInSpawnableBounds(pos)) {
return@put DoubleValue.ZERO
}
val pokemon = props.createEntity(world)
pokemon.moveTo(pos, pokemon.yRot, pokemon.xRot)
if (world.addFreshEntity(pokemon)) return@spawnPokemon pokemon.struct
DoubleValue.ZERO
if (world.addFreshEntity(pokemon)) {
return@put pokemon.struct
} else {
return@put DoubleValue.ZERO
}
}
functions["spawn_npc"] = spawnNpc@{ params ->
map["spawn_npc"] = put@{ params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val npcClass =
params
.getStringOrNull(3)
?.let { NPCClasses.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
val npcClass = params.getStringOrNull(3)
?.let { NPCClasses.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
val level = params.getInt(4)
if (npcClass == null) return@spawnNpc DoubleValue.ZERO
if (npcClass == null) return@put DoubleValue.ZERO
val npc = NPCEntity(world)
npc.moveTo(x, y, z, npc.yRot, npc.xRot)
npc.npc = npcClass
npc.initialize(level)
if (world.addFreshEntity(npc)) return@spawnNpc npc.asMoLangValue()
DoubleValue.ZERO
if (world.addFreshEntity(npc)) {
return@put npc.asMoLangValue()
}
return@put DoubleValue.ZERO
}
functions["play_sound_on_server"] = { params ->
map["play_sound_on_server"] = { params ->
val sound = params.getString(0).asResource()
val soundSource = params.getString(1).uppercase()
val x = params.getDouble(2)
val y = params.getDouble(3)
val z = params.getDouble(4)
val player =
params.getOrNull<MoValue>(5)?.let {
when (it) {
is StringValue -> world.getPlayerByUUID(UUID.fromString(it.value))
is ObjectValue<*> -> it.obj
else -> null
}
} as? ServerPlayer
val player = params.getOrNull<MoValue>(5)?.let {
when (it) {
is StringValue -> world.getPlayerByUUID(UUID.fromString(it.value))
is ObjectValue<*> -> it.obj
else -> null
}
} as? ServerPlayer
val volume = params.getDoubleOrNull(6)?.toFloat() ?: 1.0f
val pitch = params.getDoubleOrNull(7)?.toFloat() ?: 1.0f
@@ -238,30 +233,29 @@ object WorldMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Level>>() {
packet.sendToPlayersAround(x, y, z, 16.0, world.dimension())
}
}
functions["get_entities_around"] = { params ->
map["get_entities_around"] = put@{ params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val range = params.getDouble(3) * 2
val entities = world.getEntities(null, AABB.ofSize(Vec3(x, y, z), range, range, range))
entities
return@put entities
.filterIsInstance<LivingEntity>()
.map { it.asMostSpecificMoLangValue() }
.asArrayValue()
}
functions["is_healer_in_use"] = isHealerInUse@{ params ->
map["is_healer_in_use"] = put@{ params ->
val pos = params.get<ArrayStruct>(0).asBlockPos()
val healer =
world.getBlockEntity(pos, CobblemonBlockEntities.HEALING_MACHINE).orElse(null)
?: return@isHealerInUse DoubleValue.ONE
return@isHealerInUse DoubleValue(healer.isInUse)
val healer = world.getBlockEntity(pos, CobblemonBlockEntities.HEALING_MACHINE).orElse(null)
?: return@put DoubleValue.ONE
return@put DoubleValue(healer.isInUse)
}
functions["spawn_loot_table_items"] = spawnLootTableItems@{ params ->
map["spawn_loot_table_items"] = put@{ params ->
val serverLevel = world as? ServerLevel
if (serverLevel == null) {
Cobblemon.LOGGER.warn("spawn_loot_table_items: world is not a ServerLevel")
return@spawnLootTableItems DoubleValue.ZERO
return@put DoubleValue.ZERO
}
val lootTableId = params.getString(0).asIdentifierDefaultingNamespace()
val x = params.getDouble(1)
@@ -271,11 +265,9 @@ object WorldMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Level>>() {
val lootTableKey = ResourceKey.create(Registries.LOOT_TABLE, lootTableId)
val lootTable = serverLevel.server.reloadableRegistries().getLootTable(lootTableKey)
val lootParams =
LootParams
.Builder(serverLevel)
.withParameter(LootContextParams.ORIGIN, Vec3(x, y, z))
.create(LootContextParamSets.COMMAND)
val lootParams = LootParams.Builder(serverLevel)
.withParameter(LootContextParams.ORIGIN, Vec3(x, y, z))
.create(LootContextParamSets.COMMAND)
val items = lootTable.getRandomItems(lootParams)
val spawnedItems = ArrayStruct(hashMapOf())
@@ -293,10 +285,9 @@ object WorldMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Level>>() {
}
}
}
spawnedItems
return@put spawnedItems
}
return functions
return map
}
}
@@ -8,10 +8,8 @@
package com.cobblemon.mod.common.api.orientation
import com.cobblemon.mod.common.util.math.QuatSpring
import com.cobblemon.mod.common.util.math.geometry.toDegrees
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.math.simpleSpringDamperQuat
import net.minecraft.client.Minecraft
import net.minecraft.util.Mth
import net.minecraft.world.entity.LivingEntity
@@ -28,9 +26,6 @@ open class OrientationController(val entity: LivingEntity) {
val FORWARDS: Vector3f = Vector3f(0.0f, 0.0f, -1.0f)
val UP: Vector3f = Vector3f(0.0f, 1.0f, 0.0f)
val LEFT: Vector3f = Vector3f(-1.0f, 0.0f, 0.0f)
private const val TICK_DT = 1f / 20f // Minecraft server tick rate
// Seconds for the render orientation to close half the gap to the real one. Raise for smoother but laggier visuals
private const val RENDER_SMOOTH_HALFLIFE = 0.15f
}
open var active: Boolean = false
@@ -38,8 +33,6 @@ open class OrientationController(val entity: LivingEntity) {
var orientation: Matrix3f? = null
private set
private val renderSpring = QuatSpring()
private var renderOrientationO: Matrix3f? = null
private var renderOrientation: Matrix3f? = null
@@ -61,8 +54,6 @@ open class OrientationController(val entity: LivingEntity) {
orientation = null
renderOrientationO = null
renderOrientation = null
renderSpring.quat.identity()
renderSpring.angVel.set(0f, 0f, 0f)
active = false
}
@@ -86,26 +77,18 @@ open class OrientationController(val entity: LivingEntity) {
return oldQuat
}
/** Every tick move the current render orientation towards the last known true orientation **/
fun tick() {
if (!active || orientation == null) return
renderOrientationO = renderOrientation
val current = orientation!!
val targetQuat = Quaternionf().setFromUnnormalized(current)
if (renderOrientation == null) {
// First tick: snap the spring to the target instead of easing in from identity
renderSpring.quat.set(targetQuat)
renderSpring.angVel.set(0f, 0f, 0f)
}
// Use a "quat spring" to smoothly move the render orientation towards the goal orientation.
// It takes into account the orientations angular velocities and should be much more resilient
// to sharp changes in goal orientation or late/missed packets.
simpleSpringDamperQuat(renderSpring, targetQuat, RENDER_SMOOTH_HALFLIFE, TICK_DT)
val current = orientation
val renderMatrix = this.renderOrientation ?: current
val renderQuat = Quaternionf().setFromUnnormalized(renderMatrix)
val targetQuat = Quaternionf().setFromUnnormalized(current)
val dampingFactor = 0.66f // Smooth interpolation to reduce jitter of orientations recieved from the server.
renderQuat.slerp(targetQuat, dampingFactor)
val newRenderOrientation = Matrix3f()
renderSpring.quat.get(newRenderOrientation)
renderQuat.get(newRenderOrientation)
renderOrientation = newRenderOrientation
}
@@ -269,7 +269,7 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
createDefault("dive_ball", CatchRateModifiers.SUBMERGED_IN_WATER, waterDragValue = 0.99F)
createDefault("nest_ball", CatchRateModifiers.NEST)
createDefault("repeat_ball", CatchRateModifiers.REPEAT)
createDefault("timer_ball", CatchRateModifiers.turnBased { turn -> (1F + turn * (1229F / 4096F)).coerceAtMost(4F) })
createDefault("timer_ball", CatchRateModifiers.turnBased { turn -> (1F * turn * (1229F / 4096F)).coerceAtMost(4F) })
createDefault("luxury_ball", effects = listOf(FriendshipEarningBoostEffect(2F)))
createDefault("premier_ball")
createDefault("dusk_ball", CatchRateModifiers.LIGHT_LEVEL)
@@ -278,7 +278,7 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
createDefault("cherish_ball")
createDefault("park_ball", CatchRateModifiers.PARK)
createDefault("dream_ball", CatchRateModifiers.statusBoosting(4F, Statuses.SLEEP))
createDefault("beast_ball", DynamicMultiplierModifier({ _, pokemon -> if (pokemon.hasLabels(CobblemonPokemonLabels.ULTRA_BEAST)) 5F else 0.1F }) { _, _ -> true })
createDefault("beast_ball", LabelModifier(5F, true, CobblemonPokemonLabels.ULTRA_BEAST)/*, LabelModifier(0.1F, false, CobblemonPokemonLabels.ULTRA_BEAST))*/)
createDefault("ancient_poke_ball", ancient = true)
createDefault("ancient_citrine_ball", ancient = true)
createDefault("ancient_verdant_ball", ancient = true)
@@ -297,15 +297,13 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
createDefault("ancient_origin_ball", GuaranteedModifier(), ancient = true)
// Luxury ball effect, low priority as it must be triggered before soothe bell as of gen 4
CobblemonEvents.FRIENDSHIP_UPDATED.subscribe(priority = Priority.LOW) { event ->
if (!event.earned) {
return@subscribe
}
var increment = (event.newFriendship - event.pokemon.friendship).toFloat()
if (increment <= 0) {
if (increment <= 0) //these affects are only meant to affect positive gains
return@subscribe
if (increment <= 1F) {
event.pokemon.caughtBall.effects.filterIsInstance<FriendshipEarningBoostEffect>()
.forEach { increment *= it.multiplier }
}
event.pokemon.caughtBall.effects.filterIsInstance<FriendshipEarningBoostEffect>()
.forEach { increment *= it.multiplier }
event.newFriendship = event.pokemon.friendship + increment.roundToInt()
}
}
@@ -340,6 +338,7 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
ancient: Boolean = false
): PokeBall {
val identifier = cobblemonResource(name)
//val finalModifiers = if (appendUltraBeastPenalty) modifiers + listOf(LabelModifier(0.1F, true, CobblemonPokemonLabels.ULTRA_BEAST)) else modifiers
val pokeball = PokeBall(identifier, modifier, effects, waterDragValue, model2d, model3d, throwPower, ancient)
this.defaults[identifier] = pokeball
return pokeball
@@ -54,20 +54,22 @@ object CatchRateModifiers {
/**
* Used by [PokeBalls.LOVE_BALL].
* Boosts the catch rate 2.5× if any of the player's active Pokémon is of the opposite gender,
* or 8× if that Pokémon is also of the same species.
* Boosts the catch rate if the Pokémon is of the same species and opposite gender
*/
val LOVE = BattleModifier { _, playerPokemon, pokemon ->
if (pokemon.gender == Gender.GENDERLESS) {
return@BattleModifier 1F
if (
pokemon.gender != Gender.GENDERLESS
&& playerPokemon.mapNotNull { it.battlePokemon?.originalPokemon }
.any {
it.gender != Gender.GENDERLESS &&
it.species.name.equals(pokemon.species.name, true) &&
it.gender != pokemon.gender
}
) {
if (playerPokemon.any { it.battlePokemon?.originalPokemon?.species?.resourceIdentifier == pokemon.species.resourceIdentifier }) 8F else 2.5F
}
val oppositeGender = playerPokemon
.mapNotNull { it.battlePokemon?.originalPokemon }
.filter { it.gender != Gender.GENDERLESS && it.gender != pokemon.gender }
when {
oppositeGender.any { it.species.resourceIdentifier == pokemon.species.resourceIdentifier } -> 8F
oppositeGender.isNotEmpty() -> 2.5F
else -> 1F
else {
1F
}
}
@@ -79,7 +81,7 @@ object CatchRateModifiers {
* 1× otherwise.
*/
val MOON_PHASES: CatchRateModifier = WorldStateModifier { _, entity ->
if (entity.level().dayTime % 24000 < 12000)
if (entity.level().gameTime in 12000..24000)
return@WorldStateModifier 1F
when (entity.level().moonPhase) {
2, 6 -> 1.5F
@@ -108,7 +110,7 @@ object CatchRateModifiers {
* Used by [PokeBalls.SAFARI_BALL].
* Checks if the target is not battling, if true boost by *1.5
*/
val SAFARI = MultiplierModifier(1.5f) { _, poke -> !poke.entity?.isBattling!! }
val SAFARI = WorldStateModifier { _, entity -> if (!entity.isBattling) 1.5F else 1F }
/**
* Used by [PokeBalls.PARK_BALL].
@@ -13,10 +13,6 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.feature.ChoiceSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.WeightedChoiceSpeciesFeatureProvider
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData
import com.cobblemon.mod.common.pokemon.Gender
import net.minecraft.resources.ResourceLocation
@@ -76,57 +72,6 @@ abstract class AbstractPokedexManager {
} ?: PokedexEntryProgress.UNREGISTERED
}
fun getHighestKnowledgeFor(entry: PokedexEntry, form: PokedexForm?): PokedexEntryProgress {
if (form == null) return getHighestKnowledgeFor(entry)
val speciesRecord = getSpeciesRecord(entry.speciesId) ?: return PokedexEntryProgress.UNREGISTERED
val hasAllAspects = entry.conditionAspects.all(speciesRecord::hasAspect)
if (!hasAllAspects) {
return PokedexEntryProgress.UNREGISTERED
}
return speciesRecord.getFormRecord(form.displayForm)?.knowledge ?: PokedexEntryProgress.UNREGISTERED
}
fun getHighestKnowledgeFor(entry: PokedexEntry, form: PokedexForm?, aspects: Iterable<String>): PokedexEntryProgress {
val formKnowledge = getHighestKnowledgeFor(entry, form)
if (formKnowledge == PokedexEntryProgress.UNREGISTERED) return PokedexEntryProgress.UNREGISTERED
val seenAspects = getSeenAspects(entry)
// Aspects that select one of the species' own forms are already covered by the form knowledge check above,
// so exempt them (and every other value of the same feature, since defaults like "phony" never appear on a form directly).
val species = PokemonSpecies.getByIdentifier(entry.speciesId)
val formAspects = species?.forms?.flatMapTo(hashSetOf()) { it.aspects } ?: emptySet()
val formSelectingFeatureAspects = species?.let(SpeciesFeatures::getFeaturesFor).orEmpty()
.mapNotNull { provider ->
val allAspects: Collection<String> = when (provider) {
is ChoiceSpeciesFeatureProvider -> provider.getAllAspects()
is WeightedChoiceSpeciesFeatureProvider -> provider.getAllAspects().keys
else -> return@mapNotNull null
}
allAspects.takeIf { it.any { aspect -> aspect in formAspects } }
}
.flatten()
.toHashSet()
val allSeenAspects = aspects
.filter { aspect ->
if (Dexes.equivalentFormsLookup[aspect] != null) return@filter false
if (aspect in formAspects || aspect in formSelectingFeatureAspects) return@filter false
if (aspect.uppercase() in setOf(Gender.MALE.name, Gender.FEMALE.name, Gender.GENDERLESS.name)) return@filter false
if ("shiny".equals(aspect, ignoreCase = true)) return@filter false
return@filter true
}
.all { aspect -> seenAspects.contains(aspect) }
if (!allSeenAspects) return PokedexEntryProgress.UNREGISTERED
return formKnowledge
}
fun getEncounteredForms(entry: PokedexEntry) : List<PokedexForm> {
return getFormsWithKnowledge(entry, PokedexEntryProgress.SEEN)
}
@@ -40,17 +40,6 @@ object Dexes : JsonDataRegistry<PokedexDef> {
//Maps a dex id to its PokedexDef
val dexEntryMap = linkedMapOf<ResourceLocation, PokedexDef>()
val equivalentForms = mapOf(
"galar" to listOf("region-bias-galar", "bias-galar", "galar-bias", "galarian", "galar"),
"alola" to listOf("region-bias-alola", "bias-alola", "alola-bias", "alolan", "alola"),
"hisui" to listOf("region-bias-hisui", "bias-hisui", "hisui-bias", "hisuian", "hisui"),
"paldea" to listOf("region-bias-paldea", "bias-paldea", "paldea-bias", "paldean", "paldea"),
)
val equivalentFormsLookup = equivalentForms.flatMap { (key, values) ->
values.map { it to key }
}.toMap()
override fun reload(data: Map<ResourceLocation, PokedexDef>) {
dexEntryMap.clear()
data.entries.sortedBy { it.value.sortOrder }.forEach { (id, def) -> dexEntryMap[id] = def }
@@ -60,9 +49,4 @@ object Dexes : JsonDataRegistry<PokedexDef> {
override fun sync(player: ServerPlayer) {
PokedexDexSyncPacket(dexEntryMap.values).sendToPlayer(player)
}
fun getFormEquivalents(formName: String): List<String> {
val key = equivalentFormsLookup[formName.lowercase()]
return equivalentForms[key] ?: listOf(formName.lowercase())
}
}
@@ -42,7 +42,7 @@ class PokedexManager(
}
// Java interop isn't great lol
@Deprecated("Use obtain(Pokemon) instead")
@Deprecated("Use encounter(PokedexEntityData) instead")
fun catch(pokemon: Pokemon) {
obtain(pokemon)
}
@@ -121,7 +121,6 @@ class SpeciesDexRecord {
fun addAspects(addedAspects: Set<String>) {
aspects.addAll(addedAspects)
pokedexManager.onSpeciesRecordUpdated(this)
}
/** Returns true if the given Pokémon contains new information. Internal because it's only to be called from [FormDexRecord.wouldBeDifferent]. */
@@ -8,20 +8,13 @@
package com.cobblemon.mod.common.api.pokedex.entry
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.DexEntrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.DexEvolutionDisplaySyncPacket
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
@@ -45,8 +38,6 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
val entries = mutableMapOf<ResourceLocation, PokedexEntry>()
val evolutionDisplays = mutableListOf<DexEvolutionDisplay>()
override fun reload(data: Map<ResourceLocation, PokedexEntry>) {
data.forEach { _, entry ->
entries[entry.id] = entry
@@ -59,7 +50,6 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
}
}
}
computeEvolutionDisplays()
observable.emit(this)
}
@@ -67,58 +57,5 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
override fun sync(player: ServerPlayer) {
DexEntrySyncPacket(entries.values).sendToPlayer(player)
DexEvolutionDisplaySyncPacket(evolutionDisplays).sendToPlayer(player)
}
fun computeEvolutionDisplays() {
evolutionDisplays.clear()
PokemonSpecies.species.forEach {
try {
evolutionDisplays.addAll(buildDisplays(it.standardForm) + it.forms.flatMap { form -> buildDisplays(form) })
} catch (e: Exception) {
Cobblemon.LOGGER.error("Failed to build dex evolution displays for ${it.resourceIdentifier}", e)
}
}
}
fun buildDisplays(form: FormData): List<DexEvolutionDisplay> {
val result = mutableListOf<DexEvolutionDisplay>()
(form.ownEvolutions ?: form.evolutions).forEach { evolution ->
val requirements = mutableListOf<DexEvolutionRequirementDisplay>()
getToolTipHeaderIcon(evolution)?.let { header ->
requirements.add(header)
}
evolution.requirements.mapNotNull { resolveRequirementData(it) }.forEach { requirement ->
requirements.add(requirement)
}
listOfNotNull(evolution.result, evolution.shedder).forEach resultLoop@{ resultProperties ->
val resultSpecies = resultProperties.species
?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
?: return@resultLoop
// The result's own aspects win, otherwise carry the source form's aspects over
// like a real evolution does (Alola-Bias Pichu -> Alola-Bias Pikachu)
val resultForm = resultSpecies.getForm(resultProperties.aspects).takeIf { it != resultSpecies.standardForm }
?: resultSpecies.getForm(form.aspects.toSet() + resultProperties.aspects)
val toAspects = resultProperties.aspects + resultForm.aspects
val resultRequirements = if (resultProperties === evolution.shedder) {
requirements + CobblemonDexEvolutionRequirementDisplay(CobblemonItems.POKE_BALL, lang("ui.pokedex.evolution.shedder.tooltip"))
} else {
requirements
}
result.add(DexEvolutionDisplay(
fromSpecies = form.species.resourceIdentifier,
fromForm = form.name,
fromAspects = form.aspects.toSet(),
toSpecies = resultSpecies.resourceIdentifier,
toForm = resultForm.name,
toAspects = toAspects,
requirements = resultRequirements,
))
}
}
return result
}
}
}
@@ -1,466 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokedex.entry
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeIdentifierCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeTagCondition
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.text.add
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
import com.cobblemon.mod.common.pokemon.requirements.*
import com.cobblemon.mod.common.util.adapters.RegistryAccessResolver
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.lang
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.ExtraCodecs
import net.minecraft.world.item.Item
import net.minecraft.world.item.Items
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.levelgen.structure.Structure
import kotlin.jvm.optionals.getOrNull
data class DexEvolutionDisplay(
val fromSpecies: ResourceLocation,
val fromForm: String,
val fromAspects: Set<String>,
val toSpecies: ResourceLocation,
val toForm: String,
val toAspects: Set<String>,
val requirements: List<DexEvolutionRequirementDisplay>,
)
interface DexEvolutionRequirementDisplay {
val icon: Item
val tooltip: MutableComponent
}
data class CobblemonDexEvolutionRequirementDisplay(
override val icon: Item,
override val tooltip: MutableComponent,
) : DexEvolutionRequirementDisplay
data class CobblemonBiomeDexEvolutionRequirementDisplay(
override val icon: Item,
override val tooltip: MutableComponent,
val condition: ExtraCodecs.TagOrElementLocation? = null,
val antiCondition: ExtraCodecs.TagOrElementLocation? = null,
) : DexEvolutionRequirementDisplay
fun getToolTipHeaderIcon(evolution: Evolution): DexEvolutionRequirementDisplay? {
when (evolution) {
is ItemInteractionEvolution -> return getToolTipHeaderIcon(evolution)
is TradeEvolution -> return getToolTipHeaderIcon(evolution)
}
return null
}
fun getToolTipHeaderIcon(itemInteractionEvolution: ItemInteractionEvolution): CobblemonDexEvolutionRequirementDisplay? {
val item = itemInteractionEvolution.requiredContext.items.orElse(null)?.firstOrNull()?.value() ?: return null
return CobblemonDexEvolutionRequirementDisplay(item, lang("ui.pokedex.evolution.item_interact.tooltip", item.description.plainCopy().green()))
}
fun getToolTipHeaderIcon(tradeEvolution: TradeEvolution): CobblemonDexEvolutionRequirementDisplay {
val species = tradeEvolution.requiredContext.species
val tooltip = if (species == null) {
lang("ui.pokedex.evolution.trade.tooltip.with_any_species")
} else {
PokemonSpecies.getByIdentifier(species.asIdentifierDefaultingNamespace())
?.let { lang("ui.pokedex.evolution.trade.tooltip.with_species", it.translatedName.plainCopy().green()) }
?: lang("ui.pokedex.evolution.trade.tooltip.with_any_species")
}
return CobblemonDexEvolutionRequirementDisplay(CobblemonItems.LINK_CABLE, tooltip)
}
fun resolveRequirementData(requirement: Requirement): DexEvolutionRequirementDisplay? {
val icon = requirement.pokedexIcon?.let {
RegistryAccessResolver.getAccess().registryOrThrow(Registries.ITEM).get(it)
}
val tooltip = requirement.pokedexTooltip?.let {
Component.translatable(it)
}
if (icon != null && tooltip != null) {
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
return buildRequirementData(requirement)
}
fun buildRequirementData(requirement: Requirement): DexEvolutionRequirementDisplay? {
when (requirement) {
is AdvancementRequirement -> return buildAdvancementRequirement(requirement)
is BattleCriticalHitsRequirement -> return buildBattleCriticalHitsRequirement(requirement)
is BiomeRequirement -> return buildBiomeRequirement(requirement)
is BlocksTraveledRequirement -> return buildBlocksTraveledRequirement(requirement)
is DamageTakenRequirement -> return buildDamageTakenRequirement(requirement)
is DefeatRequirement -> return buildDefeatRequirement(requirement)
is FriendshipRequirement -> return buildFriendshipRequirement(requirement)
is HeldItemRequirement -> return buildHeldItemRequirement(requirement)
is LevelRequirement -> return buildLevelRequirement(requirement)
is MoonPhaseRequirement -> return buildMoonPhaseRequirement(requirement)
is MoveSetRequirement -> return buildMoveSetRequirement(requirement)
is MoveTypeRequirement -> return buildMoveTypeRequirement(requirement)
is NegateRequirement -> return buildNegateRequirement(requirement)
is OwnerHoldsItemRequirement -> return buildOwnerHoldsItemRequirement(requirement)
is PartyMemberRequirement -> return buildPartyMemberRequirement(requirement)
is PokemonPropertiesRequirement -> return buildPokemonPropertiesRequirement(requirement)
is PropertyRangeRequirement -> return buildPropertyRangeRequirement(requirement)
is RecoilRequirement -> return buildRecoilRequirement(requirement)
is StatCompareRequirement -> return buildStatCompareRequirement(requirement)
is StatEqualRequirement -> return buildStatEqualRequirement(requirement)
is StructureRequirement -> return buildStructureRequirement(requirement)
is TimeRangeRequirement -> return buildTimeRangeRequirement(requirement)
is UseMoveRequirement -> return buildUseMoveRequirement(requirement)
is WeatherRequirement -> return buildWeatherRequirement(requirement)
}
return null
}
fun buildAdvancementRequirement(advancementRequirement: AdvancementRequirement): DexEvolutionRequirementDisplay {
val icon = Items.BOOK
val identifier = advancementRequirement.requiredAdvancement
val advancementLanguageKey = "${identifier.namespace}.${identifier.path.substringAfterLast('/')}"
val advancementComponent = Component.translatable("advancements.$advancementLanguageKey").green()
val tooltip = lang("ui.pokedex.requirement.advancement.tooltip", advancementComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildBattleCriticalHitsRequirement(battleCriticalHitsRequirement: BattleCriticalHitsRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.MEDICINAL_LEEK
val criticalsComponent = Component.literal(battleCriticalHitsRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.battle_critical_hits.tooltip", criticalsComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
private fun tagDisplayName(registry: String, id: ResourceLocation): MutableComponent {
return Component.translatableWithFallback("tag.$registry.${id.namespace}.${id.path.replace('/', '.')}", prettifyPath(id.path))
}
private fun prettifyPath(path: String): String {
return path.substringAfterLast('/').removePrefix("is_").split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) }
}
fun buildNegateRequirement(negateRequirement: NegateRequirement): DexEvolutionRequirementDisplay? {
val inner = negateRequirement.inner
when (inner) {
is BiomeRequirement -> buildBiomeRequirement(inner, negated = true)?.let { return it }
is StructureRequirement -> buildStructureRequirement(inner, negated = true)?.let { return it }
}
val innerDisplay = resolveRequirementData(inner) ?: return null
return CobblemonDexEvolutionRequirementDisplay(innerDisplay.icon, lang("ui.pokedex.requirement.negate.tooltip", innerDisplay.tooltip))
}
fun buildBiomeRequirement(biomeRequirement: BiomeRequirement, negated: Boolean = false): DexEvolutionRequirementDisplay? {
// Negating "in X" is "not in X"; negating both a condition and an anticondition isn't expressible as one line
if (negated && biomeRequirement.biomeCondition != null && biomeRequirement.biomeAnticondition != null) return null
val condition = if (negated) biomeRequirement.biomeAnticondition else biomeRequirement.biomeCondition
val antiCondition = if (negated) biomeRequirement.biomeCondition else biomeRequirement.biomeAnticondition
if (condition == null && antiCondition == null) {
return null
}
val icon = Items.COMPASS
val displayName: (RegistryLikeCondition<Biome>) -> MutableComponent = {
when (it) {
is RegistryLikeTagCondition -> tagDisplayName("worldgen.biome", it.tag.location())
is RegistryLikeIdentifierCondition -> Component.translatable("biome.${it.identifier.namespace}.${it.identifier.path}")
else -> Component.literal(it.toString())
}
}
val tooltip = if (condition != null && antiCondition != null) {
lang("ui.pokedex.requirement.biome.both.tooltip", displayName(condition).green(), displayName(antiCondition).green())
} else if (condition != null) {
lang("ui.pokedex.requirement.biome.tooltip", displayName(condition).green())
} else {
lang("ui.pokedex.requirement.biome.anticondition.tooltip", displayName(antiCondition!!).green())
}
var conditionConverted: ExtraCodecs.TagOrElementLocation? = null
if (condition != null) {
val conditionIdentifier = if (condition is RegistryLikeIdentifierCondition) condition.identifier else (condition as RegistryLikeTagCondition).tag.location
conditionConverted = ExtraCodecs.TagOrElementLocation(conditionIdentifier, condition is RegistryLikeTagCondition)
}
var antiConditionConverted: ExtraCodecs.TagOrElementLocation? = null
if (antiCondition != null) {
val conditionIdentifier = if (antiCondition is RegistryLikeIdentifierCondition) antiCondition.identifier else (antiCondition as RegistryLikeTagCondition).tag.location
antiConditionConverted = ExtraCodecs.TagOrElementLocation(conditionIdentifier, antiCondition is RegistryLikeTagCondition)
}
return CobblemonBiomeDexEvolutionRequirementDisplay(icon, tooltip, conditionConverted, antiConditionConverted)
}
fun buildBlocksTraveledRequirement(blocksTraveledRequirement: BlocksTraveledRequirement): DexEvolutionRequirementDisplay {
val icon = Items.LEATHER_BOOTS
val blocksComponent = Component.literal(blocksTraveledRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.blocks_traveled.tooltip", blocksComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildDamageTakenRequirement(damageTakenRequirement: DamageTakenRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.PUNCHING_GLOVE
val blocksComponent = Component.literal(damageTakenRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.damage_taken.tooltip", blocksComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildDefeatRequirement(defeatRequirement: DefeatRequirement): DexEvolutionRequirementDisplay? {
val description = describePokemonProperties(defeatRequirement.target) ?: return null
val icon = CobblemonItems.PUNCHING_GLOVE
val defeatedAmount = defeatRequirement.amount
val descriptionComponent = Component.literal(defeatedAmount.toString()).green().add(" ").add(description)
val tooltip = lang("ui.pokedex.requirement.defeat.tooltip", descriptionComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildFriendshipRequirement(friendshipRequirement: FriendshipRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.SOOTHE_BELL
val friendshipAmountComponent = Component.literal(friendshipRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.friendship.tooltip", friendshipAmountComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildHeldItemRequirement(heldItemRequirement: HeldItemRequirement): DexEvolutionRequirementDisplay? {
val items = heldItemRequirement.itemCondition.items.getOrNull() ?: return null
val item = items.firstOrNull()?.value() ?: return null
val itemComponent = items.unwrapKey().map { tag ->
val id = tag.location()
val fallback = id.path.substringAfterLast('/').split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) }
Component.translatableWithFallback("tag.item.${id.namespace}.${id.path.replace('/', '.')}", fallback)
}.orElseGet { item.description.plainCopy() }
val tooltip = lang("ui.pokedex.requirement.held_item.tooltip", itemComponent.green())
return CobblemonDexEvolutionRequirementDisplay(item, tooltip)
}
fun buildLevelRequirement(levelRequirement: LevelRequirement): DexEvolutionRequirementDisplay {
val minLevel = levelRequirement.minLevel
val maxLevel = levelRequirement.maxLevel
val icon = CobblemonItems.RARE_CANDY
val tooltip =
if (minLevel == maxLevel || maxLevel == Int.MAX_VALUE)
lang("ui.pokedex.requirement.level.tooltip.min", Component.literal(minLevel.toString()).green())
else
lang("ui.pokedex.requirement.level.tooltip.between", Component.literal(minLevel.toString()).green(), Component.literal(maxLevel.toString()).green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildMoonPhaseRequirement(moonPhaseRequirement: MoonPhaseRequirement): DexEvolutionRequirementDisplay {
val icon = Items.CLOCK
val moonPhaseComponent = moonPhaseRequirement.moonPhase.displayName().plainCopy().green()
val tooltip = lang("ui.pokedex.requirement.moon_phase.tooltip", moonPhaseComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildMoveSetRequirement(moveSetRequirement: MoveSetRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.TECHNICAL_MACHINE
val tooltip = lang("ui.pokedex.requirement.has_move.tooltip", moveSetRequirement.move.displayName.plainCopy().green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildMoveTypeRequirement(moveTypeRequirement: MoveTypeRequirement): DexEvolutionRequirementDisplay {
val type = ElementalTypes.get(moveTypeRequirement.type.name)
val icon = when (type) {
ElementalTypes.NORMAL -> CobblemonItems.NORMAL_GEM
ElementalTypes.FIRE -> CobblemonItems.FIRE_GEM
ElementalTypes.WATER -> CobblemonItems.WATER_GEM
ElementalTypes.GRASS -> CobblemonItems.GRASS_GEM
ElementalTypes.ELECTRIC -> CobblemonItems.ELECTRIC_GEM
ElementalTypes.ICE -> CobblemonItems.ICE_GEM
ElementalTypes.FIGHTING -> CobblemonItems.FIGHTING_GEM
ElementalTypes.POISON -> CobblemonItems.POISON_GEM
ElementalTypes.GROUND -> CobblemonItems.GROUND_GEM
ElementalTypes.FLYING -> CobblemonItems.FLYING_GEM
ElementalTypes.PSYCHIC -> CobblemonItems.PSYCHIC_GEM
ElementalTypes.BUG -> CobblemonItems.BUG_GEM
ElementalTypes.ROCK -> CobblemonItems.ROCK_GEM
ElementalTypes.GHOST -> CobblemonItems.GHOST_GEM
ElementalTypes.DRAGON -> CobblemonItems.DRAGON_GEM
ElementalTypes.DARK -> CobblemonItems.DARK_GEM
ElementalTypes.STEEL -> CobblemonItems.STEEL_GEM
ElementalTypes.FAIRY -> CobblemonItems.FAIRY_GEM
else -> CobblemonItems.NORMAL_GEM
}
val tooltip = lang("ui.pokedex.requirement.has_move_type.tooltip", moveTypeRequirement.type.displayName.plainCopy().green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildOwnerHoldsItemRequirement(ownerHoldsItemRequirement: OwnerHoldsItemRequirement): DexEvolutionRequirementDisplay? {
val item = ownerHoldsItemRequirement.itemCondition.items.orElse(null)?.firstOrNull()?.value() ?: return null
val tooltip = lang("ui.pokedex.requirement.owner_held_item.tooltip", item.description.plainCopy().green())
return CobblemonDexEvolutionRequirementDisplay(item, tooltip)
}
fun buildPartyMemberRequirement(partyMemberRequirement: PartyMemberRequirement): DexEvolutionRequirementDisplay? {
val description = describePokemonProperties(partyMemberRequirement.target) ?: return null
val icon = CobblemonItems.POKE_BALL
val tooltip = if (partyMemberRequirement.contains) {
lang("ui.pokedex.requirement.party_member.tooltip", description)
} else {
lang("ui.pokedex.requirement.party_member.anticondition.tooltip", description)
}
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildPokemonPropertiesRequirement(pokemonPropertiesRequirement: PokemonPropertiesRequirement): DexEvolutionRequirementDisplay? {
val description = describePokemonProperties(pokemonPropertiesRequirement.target) ?: return null
val icon = Items.NAME_TAG
val tooltip = lang("ui.pokedex.requirement.pokemon_properties.tooltip", description)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildPropertyRangeRequirement(propertyRangeRequirement: PropertyRangeRequirement): DexEvolutionRequirementDisplay? {
val provider = SpeciesFeatures.getFeature(propertyRangeRequirement.feature) as? IntSpeciesFeatureProvider ?: return null
val displayName = provider.display?.name?.takeIf { it.isNotBlank() }?.asTranslated()?.green() ?: return null
val icon = provider.itemPoints.keys.firstOrNull()?.let { BuiltInRegistries.ITEM.get(it) } ?: Items.NAME_TAG
val range = propertyRangeRequirement.range
val tooltip = lang(
"ui.pokedex.requirement.property_range.tooltip",
displayName,
Component.literal(range.first.toString()).green(),
Component.literal(range.last.toString()).green()
)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildRecoilRequirement(recoilRequirement: RecoilRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.LIFE_ORB
val recoilComponent = Component.literal(recoilRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.recoil.tooltip", recoilComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildStatCompareRequirement(statCompareRequirement: StatCompareRequirement): DexEvolutionRequirementDisplay {
val highStat = Stats.getStat(statCompareRequirement.highStat)
val lowStat = Stats.getStat(statCompareRequirement.lowStat)
val icon = when (highStat) {
Stats.HP, Stats.EVASION, Stats.ACCURACY -> CobblemonItems.HEALTH_MOCHI
Stats.ATTACK -> CobblemonItems.MUSCLE_MOCHI
Stats.DEFENCE -> CobblemonItems.RESIST_MOCHI
Stats.SPECIAL_ATTACK -> CobblemonItems.GENIUS_MOCHI
Stats.SPECIAL_DEFENCE -> CobblemonItems.CLEVER_MOCHI
Stats.SPEED -> CobblemonItems.SWIFT_MOCHI
}
val highStatComponent = highStat.displayName.plainCopy().green()
val lowStatComponent = lowStat.displayName.plainCopy().green()
val tooltip = lang("ui.pokedex.requirement.stat_compare.tooltip", highStatComponent, lowStatComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildStatEqualRequirement(statEqualRequirement: StatEqualRequirement): DexEvolutionRequirementDisplay {
val statOne = Stats.getStat(statEqualRequirement.statOne)
val statTwo = Stats.getStat(statEqualRequirement.statTwo)
val statOneComponent = statOne.displayName.plainCopy().green()
val statTwoComponent = statTwo.displayName.plainCopy().green()
val icon = CobblemonItems.FRESH_START_MOCHI
val tooltip = lang("ui.pokedex.requirement.stat_equal.tooltip", statOneComponent, statTwoComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildStructureRequirement(structureRequirement: StructureRequirement, negated: Boolean = false): DexEvolutionRequirementDisplay? {
if (negated && structureRequirement.structureCondition != null && structureRequirement.structureAnticondition != null) return null
val condition = if (negated) structureRequirement.structureAnticondition else structureRequirement.structureCondition
val anticondition = if (negated) structureRequirement.structureCondition else structureRequirement.structureAnticondition
if (condition == null && anticondition == null) {
return null
}
val icon = Items.FILLED_MAP
val displayName: (RegistryLikeCondition<Structure>) -> MutableComponent = {
when (it) {
is RegistryLikeTagCondition -> tagDisplayName("worldgen.structure", it.tag.location())
is RegistryLikeIdentifierCondition -> Component.literal(prettifyPath(it.identifier.path))
else -> Component.literal(it.toString())
}
}
val tooltip = if (condition != null && anticondition != null) {
lang("ui.pokedex.requirement.structure.both.tooltip", displayName(condition).green(), displayName(anticondition).green())
} else if (condition != null) {
lang("ui.pokedex.requirement.structure.tooltip", displayName(condition).green())
} else {
lang("ui.pokedex.requirement.structure.anticondition.tooltip", displayName(anticondition!!).green())
}
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildTimeRangeRequirement(timeRangeRequirement: TimeRangeRequirement): DexEvolutionRequirementDisplay? {
val icon = Items.CLOCK
val timeRangeComponent = timeRangeRequirement.range.displayName()?.plainCopy() ?: return null
val tooltip = lang("ui.pokedex.requirement.time_range.tooltip", timeRangeComponent.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildUseMoveRequirement(useMoveRequirement: UseMoveRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.CHOICE_BAND
val tooltip = lang("ui.pokedex.requirement.use_move.tooltip", useMoveRequirement.move.displayName.plainCopy().green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildWeatherRequirement(weatherRequirement: WeatherRequirement): DexEvolutionRequirementDisplay {
val icon: Item
val tooltip: MutableComponent
if (weatherRequirement.isThundering == true) {
icon = Items.LIGHTNING_ROD
tooltip = lang("ui.pokedex.requirement.weather.tooltip.thundering")
} else {
icon = Items.WATER_BUCKET
tooltip = lang("ui.pokedex.requirement.weather.tooltip.raining")
}
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
@@ -1,142 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokedex.entry
import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.StringSpeciesFeature
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.text.add
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.lang
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.locale.Language
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.item.Items
fun describePokemonProperties(properties: PokemonProperties): MutableComponent? {
if (hasUndescribedProperty(properties)) {
return null
}
val descriptors = mutableListOf<MutableComponent>()
properties.species?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }?.let {
descriptors.add(it.translatedName.plainCopy().green())
}
properties.gender?.let {
descriptors.add(lang("gender.${it.name.lowercase()}").green())
}
properties.nickname?.let {
descriptors.add(lang("ui.pokedex.requirement.properties.nickname", it.plainCopy().green()))
}
properties.type?.let {
val typeName = ElementalTypes.get(it)?.displayName?.plainCopy() ?: Component.literal(it)
descriptors.add(lang("ui.pokedex.requirement.properties.type", typeName.green()))
}
properties.nature?.let {
val natureName = Natures.getNature(it.asIdentifierDefaultingNamespace())?.displayName?.asTranslated() ?: Component.literal(it)
descriptors.add(lang("ui.pokedex.requirement.properties.nature", natureName.green()))
}
properties.customProperties.forEach { customProperty ->
describeCustomProperty(customProperty)?.let { descriptors.add(it) }
}
val heldItemDescriptor = properties.heldItem?.let { heldItem ->
val itemId = heldItem.substringBefore('[').trim().asIdentifierDefaultingNamespace()
val item = BuiltInRegistries.ITEM.get(itemId)
if (item == Items.AIR) null else lang("ui.pokedex.requirement.properties.held_item", item.description.plainCopy().green())
}
if (descriptors.isEmpty() && heldItemDescriptor == null) {
return null
}
val mainDescriptor = when {
descriptors.isEmpty() -> null
descriptors.size == 1 -> descriptors[0]
else -> {
val allButLast = descriptors.dropLast(1).reduce { acc, next -> acc.add(", ").add(next) }
lang("ui.pokedex.requirement.properties.and", allButLast, descriptors.last())
}
}
return when {
mainDescriptor != null && heldItemDescriptor != null -> mainDescriptor.add(" ").add(heldItemDescriptor)
mainDescriptor != null -> mainDescriptor
else -> heldItemDescriptor
}
}
private fun hasUndescribedProperty(properties: PokemonProperties): Boolean {
return properties.form != null ||
properties.shiny != null ||
properties.level != null ||
properties.friendship != null ||
properties.fullness != null ||
properties.pokeball != null ||
properties.ability != null ||
properties.status != null ||
properties.teraType != null ||
properties.dmaxLevel != null ||
properties.gmaxFactor != null ||
properties.tradeable != null ||
properties.originalTrainerType != null ||
properties.originalTrainer != null ||
properties.moves != null ||
properties.movesetBuilders != null ||
properties.cosmeticItem != null ||
properties.scaleModifier != null ||
properties.isAlpha != null ||
properties.minPerfectIVs != null ||
properties.customProperties.any { describeCustomProperty(it) == null }
}
private fun describeCustomProperty(property: CustomPokemonProperty): MutableComponent? {
return when (property) {
is IntSpeciesFeature -> describeIntSpeciesFeature(property)
is StringSpeciesFeature -> describeStringSpeciesFeature(property)
else -> null
}
}
private fun describeIntSpeciesFeature(property: IntSpeciesFeature): MutableComponent? {
val value = Component.literal(property.value.toString())
val overrideKey = "ui.pokedex.requirement.properties.custom.${property.name}"
if (Language.getInstance().has("cobblemon.$overrideKey")) {
return lang(overrideKey, value.green())
}
val displayName = (SpeciesFeatures.getFeature(property.name) as? IntSpeciesFeatureProvider)
?.display?.name?.takeIf { it.isNotBlank() }
?: return null
return lang("ui.pokedex.requirement.properties.int_feature", value.green(), displayName.asTranslated().green())
}
private fun describeStringSpeciesFeature(property: StringSpeciesFeature): MutableComponent? {
val key = "ui.pokedex.requirement.properties.custom.${property.name}"
if (!Language.getInstance().has("cobblemon.$key")) {
return null
}
val value = PokemonSpecies.getByIdentifier(property.value.asIdentifierDefaultingNamespace())?.translatedName?.plainCopy()
?: Component.literal(property.value)
return lang(key, value.green())
}
@@ -26,6 +26,7 @@ import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.MovesetBuilder
import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies.getByIdentifier
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffect
import com.cobblemon.mod.common.api.pokemon.effect.adapter.ShoulderEffectAdapter
import com.cobblemon.mod.common.api.pokemon.egg.EggGroup
@@ -127,7 +128,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
.registerTypeAdapter(EggGroup::class.java, EggGroupAdapter)
.registerTypeAdapter(MobEffect::class.java, RegistryElementAdapter<MobEffect>(BuiltInRegistries::MOB_EFFECT))
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter())
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(RidingBehaviourSettings::class.java, RidingBehaviourSettingsAdapter)
.registerTypeAdapter(RideSoundSettingsList::class.java, RideSoundSettingsListAdapter)
.registerTypeAdapter(ObtainableItemCondition::class.java, ObtainableItemConditionAdapter)
@@ -15,8 +15,6 @@ import com.cobblemon.mod.common.block.entity.PokeSnackBlockEntity
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
import net.minecraft.client.ParticleStatus
import net.minecraft.core.particles.BlockParticleOption
import net.minecraft.core.particles.ParticleOptions
import net.minecraft.core.particles.ParticleTypes
@@ -42,25 +40,13 @@ sealed class ParticleData {
val chance: Double,
val amount: Int,
val locatorResolver: (Map<String, MatrixWrapper>) -> List<String>
): ParticleData() {
fun amount(): Int = when(Minecraft.getInstance().options.particles().get()) {
ParticleStatus.ALL -> amount
ParticleStatus.DECREASED -> amount / 2
ParticleStatus.MINIMAL -> amount / 4
}
}
): ParticleData()
data class MinecraftParticle(
val particle: ParticleOptions,
val chance: Double,
val amount: Int
): ParticleData() {
fun amount(): Int = when(Minecraft.getInstance().options.particles().get()) {
ParticleStatus.ALL -> amount
ParticleStatus.DECREASED -> amount / 2
ParticleStatus.MINIMAL -> amount / 4
}
}
): ParticleData()
}
/**
@@ -109,10 +109,8 @@ open class Learnset {
evolutionMoves
}
// Syncs level up, TM, and egg moves.
// We only sync level up moves atm
fun shouldSynchronize(other: Learnset) = other.levelUpMoves != this.levelUpMoves
|| other.tmMoves != this.tmMoves
|| other.eggMoves != this.eggMoves
fun decode(buffer: RegistryFriendlyByteBuf) {
this.levelUpMoves.clear()
@@ -124,8 +122,6 @@ open class Learnset {
}
levelUpMoves[level] = moves
}
decodeMoveList(buffer, this.tmMoves)
decodeMoveList(buffer, this.eggMoves)
}
fun encode(buffer: RegistryFriendlyByteBuf) {
@@ -137,21 +133,5 @@ open class Learnset {
buffer.writeInt(move.num)
}
}
encodeMoveList(buffer, this.tmMoves)
encodeMoveList(buffer, this.eggMoves)
}
private fun decodeMoveList(buffer: RegistryFriendlyByteBuf, list: MutableList<MoveTemplate>) {
list.clear()
repeat(times = buffer.readSizedInt(IntSize.U_SHORT)) {
Moves.getByNumericalId(buffer.readInt())?.let(list::add)
}
}
private fun encodeMoveList(buffer: RegistryFriendlyByteBuf, list: List<MoveTemplate>) {
buffer.writeSizedInt(IntSize.U_SHORT, list.size)
for (move in list) {
buffer.writeInt(move.num)
}
}
}
@@ -11,8 +11,6 @@ package com.cobblemon.mod.common.api.pokemon.requirement
import com.cobblemon.mod.common.api.pokemon.evolution.ContextEvolution
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.resources.ResourceLocation
import java.util.WeakHashMap
/**
* Represents a universal requirement suitable for various checks on pokemon..
@@ -34,11 +32,4 @@ fun interface Requirement {
*/
fun check(pokemon: Pokemon): Boolean
val pokedexIcon: ResourceLocation? get() = pokedexIconOverrides[this]
val pokedexTooltip: String? get() = pokedexTooltipOverrides[this]
companion object {
val pokedexIconOverrides = WeakHashMap<Requirement, ResourceLocation>()
val pokedexTooltipOverrides = WeakHashMap<Requirement, String>()
}
}
@@ -54,8 +54,8 @@ enum class Stats(override val identifier: ResourceLocation, override val display
"hp" -> HP
"atk", "attack" -> ATTACK // Hyper Cutter states the full stat name "Attack"
"def", "defense", "defence" -> DEFENCE // Big Pecks states the full stat name "Defense"
"spa", "special_attack" -> SPECIAL_ATTACK
"spd", "special_defence", "special_defense" -> SPECIAL_DEFENCE
"spa" -> SPECIAL_ATTACK
"spd" -> SPECIAL_DEFENCE
"spe", "speed" -> SPEED
"evasion" -> EVASION
else -> ACCURACY
@@ -1,53 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.api.orientation.OrientationController
import net.minecraft.util.SmoothDouble
import net.minecraft.world.phys.Vec3
/**
* One frame of steering for a rolling ride on the client that's driving it.
* The mouse values are the share the ride took in consumeLook, already through the riding
* sensitivity, invert and swap options and still in mouse units.
*/
class RideSteerInput(
/** The ride's own orientation controller */
val controller: OrientationController,
val mouseYaw: Double,
val mousePitch: Double,
val mouseXSmoother: SmoothDouble,
val mouseYSmoother: SmoothDouble,
/** Vanilla's mouse sensitivity curve, cubed */
val sensitivity: Double,
/** Seconds since the last frame */
val deltaTime: Double,
private val yawSmoother: SmoothDouble,
private val pitchSmoother: SmoothDouble,
private val rollSmoother: SmoothDouble
) {
companion object {
// Share of each push the smoothers get. Lower makes smoothed turning softer
const val SMOOTHED_SHARE = 0.5
}
/** Turns the ride by local yaw, pitch and roll degrees right away. */
fun rotate(rotation: Vec3) {
controller.rotate(rotation.x.toFloat(), rotation.y.toFloat(), rotation.z.toFloat())
}
/** Turns the ride by local yaw, pitch and roll degrees eased through vanilla's mouse smoother so it spreads over a few frames. */
fun rotateSmoothed(rotation: Vec3) {
controller.rotate(
yawSmoother.getNewDeltaValue(rotation.x * SMOOTHED_SHARE, deltaTime).toFloat(),
pitchSmoother.getNewDeltaValue(rotation.y * SMOOTHED_SHARE, deltaTime).toFloat(),
rollSmoother.getNewDeltaValue(rotation.z * SMOOTHED_SHARE, deltaTime).toFloat()
)
}
}
@@ -12,20 +12,15 @@ import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.camera.RideCameraContext
import com.cobblemon.mod.common.api.riding.camera.RideLookInput
import com.cobblemon.mod.common.api.riding.camera.RideLookTake
import com.cobblemon.mod.common.api.riding.camera.RideLookSettings
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import org.joml.Quaternionf
/**
* Represents the behaviour of a Pokemon when being ridden.
@@ -46,39 +41,30 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
fun pose(settings: Settings, state: State, vehicle: PokemonEntity): PoseType
/** Ride speed for vanilla's setSpeed. Only reads state, anything that changes state goes in [driverTick]. */
fun speed(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float
/**
* Runs once a tick only on the client that's steering, after [tick] and the ride's rotation.
* Whatever it works out reaches the server and other players through the riding state sync.
*/
fun driverTick(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player) {}
fun clampPassengerRotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity) {}
fun updatePassengerRotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity) {}
fun rotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity): Vec2
/**
* Ride velocity for this tick in world space blocks per tick.
* [RideFrame.yawToWorld] turns a velocity worked out along the ride's yaw into world space.
*/
fun velocity(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, input: Vec3): Vec3
/**
* True to have the driver's client call [velocity] a second time each tick inside travel.
* Vanilla already asks for it once in getRiddenInput and travel always asked again, so every ride's
* numbers are tuned around running twice a tick. Return false once a ride is retuned for a single run.
*/
fun velocityRunsTwicePerTick(settings: Settings, state: State, vehicle: PokemonEntity): Boolean = true
fun angRollVel(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, deltaTime: Double): Vec3
/**
* Turns the ride for one frame while it rolls. Runs every frame on the client that's driving with the mouse
* share the ride took in [consumeLook]. Rotate through [RideSteerInput.controller] or its helpers.
*/
fun steer(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, input: RideSteerInput) {}
fun rotationOnMouseXY(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3
fun canJump(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Boolean
@@ -90,6 +76,8 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
fun rideFovMultiplier(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float
fun useAngVelSmoothing(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun inertia(settings: Settings, state: State, vehicle: PokemonEntity): Double
fun shouldRoll(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
@@ -123,22 +111,10 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
fun applyRenderRotation(settings: Settings, state: State, vehicle: PokemonEntity, partialTicks: Float) {}
/**
* How much of this frame's mouse look the ride takes to steer with. What it takes goes through
* [steer] and the rest moves the rider's look. Only asked for the driver and never while freelooking.
* Determines if the pair of mouse inputs Pair(mouseX, mouseY) get used to modify the driverRotations or the vehicle rotations themselves.
* Driver rotations are the rotations used by passengers or a driver that is freelooking. This does not override the freelook button.
*/
fun consumeLook(settings: Settings, state: State, vehicle: PokemonEntity, input: RideLookInput): RideLookTake = RideLookTake.all(input)
/**
* Rotation the rider's camera hangs off while the ride rolls. Called every frame on the client
* and the rider's look sits on top of it. The player's disableRoll setting still applies after this.
*/
fun cameraBase(settings: Settings, state: State, vehicle: PokemonEntity, context: RideCameraContext): Quaternionf = context.defaultBase()
/** Degrees to roll the first person hands and held items */
fun firstPersonHandRoll(settings: Settings, state: State, vehicle: PokemonEntity, context: RideCameraContext): Float = 0f
/** Look limits and carry for riders while the ride rolls. */
fun lookSettings(settings: Settings, state: State, vehicle: PokemonEntity): RideLookSettings = RideLookSettings.DEFAULT
fun mouseModifiesDriverRotation(settings: Settings, state: State, vehicle: PokemonEntity): Pair<Boolean, Boolean> = Pair(false, false)
/**
* Used in MoLang functions for querying states for animations or other magical MoLang effects.
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.air.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.ClimbBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartBehaviour
@@ -36,9 +35,7 @@ object RidingBehaviours {
register(MinekartBehaviour.KEY, MinekartBehaviour())
register(HoverBehaviour.KEY, HoverBehaviour())
register(RocketBehaviour.KEY, RocketBehaviour())
register(BoardBehaviour.KEY, BoardBehaviour())
register(SubmarineBehaviour.KEY, SubmarineBehaviour())
register(ClimbBehaviour.KEY, ClimbBehaviour())
register(CompositeBehaviour.KEY, CompositeBehaviour())
}
@@ -15,13 +15,8 @@ import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.orientation.OrientationController
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.FORWARDS
import com.cobblemon.mod.common.api.orientation.OrientationController.Companion.UP
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.camera.RideCameraContext
import com.cobblemon.mod.common.api.riding.camera.RideLookInput
import com.cobblemon.mod.common.api.riding.camera.RideLookTake
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
@@ -29,9 +24,7 @@ import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.FloatSpring
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.math.simpleSpringDamper
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
@@ -41,40 +34,12 @@ import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.*
class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
companion object {
val KEY = cobblemonResource("air/bird")
private const val FLY_POSE_SPEED = 0.1
// At or below this the bird hovers instead of flying
private const val HOVER_SPEED = 0.1
// Within this many degrees of straight up or down wings level means nothing so the camera holds its roll
private const val VERTICAL_DEADZONE_DEGREES = 15f
private val MIN_LEVEL_UP_LENGTH_SQ = sin(VERTICAL_DEADZONE_DEGREES.toRadians()).pow(2)
// Below this speed the bird is too slow to bank so its wings always level out
private const val NO_BANK_SPEED = 0.2
// Degrees before the max roll where rolling further starts easing off. Raise for a softer stop
private const val ROLL_CAP_BAND_DEGREES = 20f
// Degrees past the turn roll limit over which mouse pitch hands over from world up and down to the bird's own pitch
private const val PITCH_BLEND_BAND_DEGREES = 15f
// Pitch where mouse pitch starts handing back to the bird's own pitch. By straight up or down it's all the bird's
// own so a loop carries on over the top instead of world up pulling it back
private const val WORLD_PITCH_FADE_START = 60f
// Fastest the wings level out by themselves in degrees per second
private const val MAX_ROLL_SETTLE_RATE = 45f
// Within this many degrees of level the settle rate eases off so it lands on level instead of overshooting
private const val ROLL_SETTLE_ARRIVAL_DEGREES = 35f
// How fast the settle rate can change in degrees per second squared. Same as the old per frame cap at 60 fps
private const val ROLL_SETTLE_ACCEL = 67.5f
// Keeps the turn roll limit from dividing by zero
private const val MIN_TURN_ROLL_LIMIT = 1f
private const val GLIDE_ENTRY_SPEED = 0.19
private const val GLIDE_EXIT_SPEED = 0.13
}
override val key: ResourceLocation = KEY
@@ -84,7 +49,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
val poseProvider = PoseProvider<BirdSettings, BirdState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > FLY_POSE_SPEED })
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.2 })
val globalBird: BirdSettings
get() = CobblemonRideSettings.bird
@@ -115,17 +80,25 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
) {
if(vehicle.level().isClientSide) {
tickStamina(settings, state, vehicle, driver)
checkStaminaDive(state)
checkUpsideDown(state, vehicle, driver)
}
}
// Out of stamina the bird can't hold itself up so it builds up a dive
private fun checkStaminaDive(state: BirdState) {
if (state.stamina.get() == 0.0f) {
val diveRate = 1.0 / (2.0 * 20.0) // 2 seconds to max out
state.staminaDiveForce.set(min(1.0, state.staminaDiveForce.get() + diveRate))
} else {
state.staminaDiveForce.set(0.0)
private fun checkUpsideDown(
state: BirdState,
vehicle: PokemonEntity,
driver: Player,
) {
// Also checks stamina
val controller = (vehicle as OrientationControllable).orientationController
if (cos(controller.roll.toRadians()) < 0.0) {
val upsideDownRate = 1.0 / (2.0 * 20.0) // 2 seconds to max out upsideDown force
state.currUpsideDownForce.set( min(1.0, state.currUpsideDownForce.get() + upsideDownRate * abs(cos(controller.roll.toRadians()))))
} else if (state.stamina.get() == 0.0f) { // checks stamina depletion and forces dive if depleted
val upsideDownRate = 1.0 / (2.0 * 20.0) // 1 seconds to max out
state.currUpsideDownForce.set( min(1.0, state.currUpsideDownForce.get() + upsideDownRate))
} else { // Its right side up or has stamina so reset it
state.currUpsideDownForce.set(0.0)
}
}
@@ -184,8 +157,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
//Perform ride velocity update
calculateRideSpaceVel(settings, state, vehicle, driver)
// Pulls the bird down once it's out of stamina
val extraDownwardForce = state.staminaDiveForce.get().pow(2) * -0.3 // 6 blocks a second downward
// The downward force used to encourage players to stop flying upside down.
val extraDownwardForce = state.currUpsideDownForce.get().pow(2) * -0.3 // 6 blocks a second downward
// Convert the local velocity vector into a world vector
val localVelVec = Vector3f(
@@ -193,7 +166,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
(state.rideVelocity.get().y).toFloat(),
state.rideVelocity.get().z.toFloat() * -1.0f // Flip the z axis to make this left handed
)
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d()
// Unrotate preemptively as this vector gets rotatee later down the line in MC logic.
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d().yRot(vehicle.yRot.toRadians())
worldVelVec = worldVelVec.add(0.0, extraDownwardForce, 0.0) // Add the stamina depletion force to bring the ride down.
return worldVelVec
@@ -218,23 +192,21 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
// speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0) {
activeInput = true
if (newVelocity.length() < glideTopSpeed) {
//make sure it can't exceed top speed
val forwardInput = when {
state.stamina.get() == 0.0f -> 0.0
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z < (-topSpeed / 3.0) -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
if (driver.zza != 0.0f && state.stamina.get() > 0.0 && newVelocity.length() < glideTopSpeed) {
//make sure it can't exceed top speed
val forwardInput = when {
state.stamina.get() == 0.0f -> 0.0
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z < (-topSpeed / 3.0) -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
activeInput = true
}
val controller = (vehicle as? OrientationControllable)?.orientationController
@@ -292,9 +264,11 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
//Check if the ride should be gliding
if (driver.isLocalPlayer) {
val powered = activeInput && state.stamina.get() > 0.0
val requiredSpeed = if (state.gliding.get()) GLIDE_EXIT_SPEED else GLIDE_ENTRY_SPEED
state.gliding.set(!powered && newVelocity.z >= requiredSpeed)
if ((activeInput && state.stamina.get() > 0.0) || state.rideVelocity.get().z < 0.2) {
state.gliding.set(false)
} else {
state.gliding.set(true)
}
}
//air resistance
@@ -304,23 +278,19 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
state.rideVelocity.set(newVelocity)
}
private fun rotateOverTime(
override fun angRollVel(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
) {
if (vehicle !is OrientationControllable) return
): Vec3 {
if (vehicle !is OrientationControllable) return Vec3.ZERO
val controller = (vehicle as OrientationControllable).orientationController
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalBird.handlingExpr!!)
// Turn rate tops out at the turn roll limit. Banking further is for show and doesn't turn any harder
val turnRollLimit = frameTuning(settings, state, vehicle).turnRollLimit.coerceIn(MIN_TURN_ROLL_LIMIT, 90f)
val bankFromLevel = if (abs(controller.roll) > 90f) 180f - abs(controller.roll) else abs(controller.roll)
val turnShare = sin(min(bankFromLevel, turnRollLimit).toRadians()) / sin(turnRollLimit.toRadians()) * controller.roll.sign
val yawDeltaDeg = deltaTime * handling * turnShare
val yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble()))
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble()))).coerceIn(-1.0, 1.0)
// Dampen yaw when upside down
@@ -335,6 +305,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
controller.applyGlobalPitch(pCorr)
controller.rotateRoll(rCorr)
//yaw, pitch, roll
return Vec3(0.0, 0.0, 0.0)
}
private fun correctOrientation(
@@ -344,28 +316,45 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
controller: OrientationController,
deltaTime: Double
) {
// Wings only level out by themselves when the rider has let go and they're nearly level already,
// or when the bird's too slow to bank at all. A bank the rider holds stays where they put it
val tuning = frameTuning(settings, state, vehicle)
val idle = state.noInputTimeRoll.get() >= tuning.rollSettleDelay
val settling = state.rideVelocity.get().length() < NO_BANK_SPEED || (idle && abs(controller.roll) < tuning.rollSettleAngle)
val rollError = Mth.wrapDegrees(0.0f.angleDifference(controller.roll))
val desiredRollRate = if (settling) {
MAX_ROLL_SETTLE_RATE * (min(ROLL_SETTLE_ARRIVAL_DEGREES, abs(rollError)) / ROLL_SETTLE_ARRIVAL_DEGREES) * -rollError.sign
} else {
0f
// Calculate correcting roll force.
val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect ?: globalBird.timeToRollCorrect!!)
// If the ride is moving then have the correction rate be an equal proportion of:
// - how rolled are you
// - how long has it been since last mouse input (higher is stronger correction)
// Also the correction is proportional as a whole to how NOT pitched are you so you don't spin endlessly when looking up or down
val howRolledAmI = sqrt(abs((cos(controller.roll.toRadians()) - 1.0f) * 0.5f)) // sqrt so it trends towards one pretty quickly
val maxRollCorrectionRate =
if (state.rideVelocity.get().length() < 0.2) 45.0f
else 45.0f * cos(controller.pitch.toRadians()).pow(2) * (
howRolledAmI*1.0f +
(state.noInputTimeRoll.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
)
val maxRollForce = maxRollCorrectionRate / 40.0f
val rollArrivalDeg = 35.0f
val desiredRoll = 0.0f
// Calculate the signed error (how far and in what direction)
val rollError = Mth.wrapDegrees(desiredRoll.angleDifference(controller.roll))
// Use the absolute error to calculate the magnitude of the influence
val influenceMagnitude = (min(rollArrivalDeg, abs(rollError)) / rollArrivalDeg)
val desiredRollForce = maxRollCorrectionRate * influenceMagnitude * -rollError.sign
var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
// Ensure the 'steering' correction force doesn't exceed our maxForce
if (abs(steeredRollForce) > maxRollForce) {
steeredRollForce = steeredRollForce.sign * maxRollForce
}
val maxRateChange = ROLL_SETTLE_ACCEL * deltaTime
val currentRollRate = state.currRollCorrectionForce.get()
state.currRollCorrectionForce.set(currentRollRate + (desiredRollRate - currentRollRate).coerceIn(-maxRateChange, maxRateChange))
// Apply the 'steered' force (its really not a steering force I need a better name)
state.currRollCorrectionForce.set(
state.currRollCorrectionForce.get() + steeredRollForce
)
//Calculate correcting pitch force.
val maxPitchCorrectionRate = if (state.staminaDiveForce.get() != 0.0) 15.0f * state.staminaDiveForce.get().toFloat() else 30.0f
val maxPitchCorrectionRate = if (state.currUpsideDownForce.get() != 0.0) 15.0f * state.currUpsideDownForce.get().toFloat() else 30.0f
val maxPitchForce = maxPitchCorrectionRate / 40.0f
val pitchArrivalDeg = 35.0f
val desiredPitch = if (state.staminaDiveForce.get() != 0.0) 90.0f else 0.0f
val desiredPitch = if (state.currUpsideDownForce.get() != 0.0) 90.0f else 0.0f
if((state.rideVelocity.get().length() < 0.1) || state.staminaDiveForce.get() != 0.0) {
if((state.rideVelocity.get().length() < 0.1) || state.currUpsideDownForce.get() != 0.0) {
val pitchErrror = Mth.wrapDegrees(desiredPitch.angleDifference(controller.pitch))
val arrivalInfluence = (min(pitchArrivalDeg,abs(pitchErrror)) / pitchArrivalDeg)
val desiredPitchForce = maxPitchCorrectionRate * arrivalInfluence * -pitchErrror.sign
@@ -388,18 +377,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
}
override fun steer(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
driver: Player,
input: RideSteerInput
) {
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
rotateOverTime(settings, state, vehicle, driver, input.deltaTime)
}
private fun mouseRotation(
override fun rotationOnMouseXY(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
@@ -412,10 +390,15 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
// Time since the rider last moved the mouse, wings only settle once it passes the settle delay
if (abs(mouseX) < 1 && abs(mouseY) < 1) {
val controller = (driver as OrientationControllable).orientationController
// Begin the roll correction time counter if not enough mouse input
// is detected
if (mouseX < 1 && abs(mouseY) < 1) {
state.noInputTimeRoll.set(state.noInputTimeRoll.get() + deltaTime)
} else {
} else if (cos(controller.roll.toRadians()) < 0.1) {
state.noInputTimeRoll.set(state.noInputTimeRoll.get() + deltaTime * 0.5)
}
else {
state.noInputTimeRoll.set(0.0)
}
@@ -456,42 +439,46 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBird.speedExpr!!) / 20.0
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult ?: globalBird.mouseInputMult!!)
val tuning = frameTuning(settings, state, vehicle)
// Applied to mouseX to prevent turning upside down when trying to roll hard.
val rollDegreeDebuff = if(mouseX.sign.toFloat() == controller.roll.sign) 1 - 0.9f*sqrt(abs(sin(controller.roll.toRadians())))*sqrt(abs(cos(controller.pitch))) else 1.0f
//Smooth out mouse input.
val smoothingSpeed = 2.0
val mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult
val mouseYc = (mouseY).coerceIn(-45.0, 45.0) * mouseInputMult
val mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult * rollDegreeDebuff
// When upside down apply a pitching force to nudge the player towards a dive. This reduces the 'weirdness'
// of being upside down
val rolledOverPitchCorrection = -8*sqrt(abs(min(cos(controller.roll.toRadians()),0.0f))) * (cos(controller.pitch.toRadians())).pow(2)
val mouseYc = ((mouseY).coerceIn(-45.0, 45.0) * mouseInputMult) + rolledOverPitchCorrection
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed)
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed)
val currSpeed = state.rideVelocity.get().length()
val currHorzSpeed = state.rideVelocity.get().horizontalDistance()
val hoverSpeed = HOVER_SPEED
val hoverSpeed = 0.1
val pitchDamping = abs(cos(controller.pitch.toRadians())).pow(2) // Helper value used when wanting to discard the influence of another value at steep pitches
// Yaw locally when at or below hoverSpeed but not when pitched up or rolled over
val globalYaw = xInput * (1 - sqrt(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed))).coerceIn(0.0, 1.0)
controller.applyGlobalYaw(globalYaw.toFloat())
// No pitching at hover speed and faster pitching the faster the bird goes
val pitchInput = yInput * if (currSpeed > hoverSpeed || yInput > 0.0) RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed).coerceIn(0.2, 1.0)
// Pitch locally up or down depending upon a number of factors:
// - Reduce pitch substantially when rolled and in a steep yaw. This prevents the player from ignoring a slow handling stat
// - Do not pitch at all when at or below hover speed
// - pitch faster at higher speeds
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr ?: globalBird.horizontalPitchExpr!!)
var localPitch = yInput * (1 - abs(sin(controller.roll.toRadians())).pow(2) * p * hypot(controller.forwardVector.x.toDouble(), controller.forwardVector.z.toDouble()).toFloat())
localPitch *= if (currSpeed > hoverSpeed || yInput > 0.0) RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed).coerceIn(0.2, 1.0)
else 0.0
// Inside a normal bank mouse pitch is world up and down so pulling up climbs instead of tightening the turn.
// Past the turn roll limit, or pointing near straight up or down, it hands over to the bird's own pitch
val pastTurnRoll = smoothstep01((abs(controller.roll) - tuning.turnRollLimit) / PITCH_BLEND_BAND_DEGREES)
val nearVertical = smoothstep01((abs(controller.pitch) - WORLD_PITCH_FADE_START) / (90f - WORLD_PITCH_FADE_START))
val localShare = 1f - (1f - pastTurnRoll) * (1f - nearVertical)
controller.applyGlobalPitch((pitchInput * (1f - localShare)).toFloat())
// Pulling with the bird's own pitch while rolled turns hard so it's held back by handling there
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr ?: globalBird.horizontalPitchExpr!!)
val localPitch = pitchInput * localShare * (1 - abs(sin(controller.roll.toRadians())).pow(2) * p * hypot(controller.forwardVector.x.toDouble(), controller.forwardVector.z.toDouble()).toFloat())
// Roll less when slow and not at all when at hoverSpeed or lower
// Roll less when slow and not at all when at hoverSpeed or lower.
// Apply a roll righting force when trying to pitch hard while rolled sideways. This nudges the player towards
// pitching globally
val pitchInfluencedRollCorrection = 0.0
val howRolledAmI = (( cos(controller.roll.toRadians()) - 1).coerceIn(-1.0f, 0.0f) * -2) //Coerce into how much roll you want to let in in the deadzone
val isInputtingTowardsRoll = if ( xInput.sign == controller.roll.sign.toDouble()) 1.0 else 0.0
val rollInput = xInput * sqrt(abs(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed)).coerceIn(0.0,1.0))
val rollForce = if (currHorzSpeed > hoverSpeed) capRoll(rollInput, controller.roll, tuning.maxRoll) else 0.0
val inputInRolledDir = if (isInputtingTowardsRoll == 1.0) 1.0 else -cos(controller.roll.toRadians()).coerceIn(-1.0f, 0.0f).toDouble()
val rollDampen = (1 - howRolledAmI.coerceIn(0.0f, 0.7f) * pitchDamping * inputInRolledDir)
val rollForce = if (currHorzSpeed > hoverSpeed) xInput * sqrt(abs(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed)).coerceIn(0.0,1.0)) * rollDampen + pitchInfluencedRollCorrection
else 0.0
// When already rolled significantly part of the roll force should turn to local yaw force
val localYawForce = rollForce * abs(sin(controller.roll.toRadians())).pow(2) * pitchDamping * isInputtingTowardsRoll
@@ -555,6 +542,14 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
return 1.0f + normalizedGlideSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun inertia(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): Double {
return 0.5
}
@@ -608,139 +603,6 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
override fun createDefaultState(settings: BirdSettings) = BirdState()
/**
* Keeps the camera level until the bird rolls past the engage angle, then eases it into the bird's roll.
* It levels out again once the bird comes back under the release angle. Yaw and pitch always follow the bird.
*/
override fun cameraBase(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
context: RideCameraContext
): Quaternionf {
val ride = context.defaultBase()
val tuning = frameTuning(settings, state, vehicle)
val roll = context.data as? BirdCameraRoll ?: BirdCameraRoll().also { context.data = it }
val forward = FORWARDS.rotate(ride, Vector3f())
val up = UP.rotate(ride, Vector3f())
// Up with wings level. Pointing near straight up or down it flips sides so it's only used away from vertical
val levelUp = Vector3f(0f, 1f, 0f).sub(Vector3f(forward).mul(forward.y))
val hasLevel = levelUp.lengthSquared() > MIN_LEVEL_UP_LENGTH_SQ
if (hasLevel) {
levelUp.normalize()
val rideRoll = signedAngle(levelUp, up, forward)
if (roll.engaged && abs(rideRoll) < tuning.rollRelease) {
roll.engaged = false
} else if (!roll.engaged && abs(rideRoll) > tuning.rollEngage) {
roll.engaged = true
}
}
// Camera up lives in world space so the level up flipping over the top of a loop can't drag the camera round
val cameraUp = roll.cameraUp?.let { flatten(it, forward) } ?: Vector3f(up)
val target = when {
roll.engaged -> up
hasLevel -> levelUp
else -> Vector3f(cameraUp)
}
if (roll.cameraUp == null) {
cameraUp.set(target)
} else if (context.frameSeconds > 0f) {
// Spring the leftover angle so it builds up speed and settles instead of lunging at the start
roll.spring.value = signedAngle(target, cameraUp, forward)
simpleSpringDamper(roll.spring, 0f, tuning.rollHalflife, context.frameSeconds)
cameraUp.set(target).rotateAxis(roll.spring.value * Mth.DEG_TO_RAD, forward.x, forward.y, forward.z)
}
roll.cameraUp = cameraUp
return levelFrame(forward, cameraUp)
}
override fun firstPersonHandRoll(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
context: RideCameraContext
): Float {
return context.rideRollAroundView() * frameTuning(settings, state, vehicle).handRollShare
}
// Hovering the rider can look up and down freely and around up to the hover limit. Only pushing past it turns the bird
override fun consumeLook(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
input: RideLookInput
): RideLookTake {
// Only forward speed counts so rising or sinking on the spot is still hovering
if (state.rideVelocity.get().horizontalDistance() > HOVER_SPEED) return RideLookTake.all(input)
val limit = frameTuning(settings, state, vehicle).hoverLookYawLimit
// Never snap a look that's already past the limit, it just can't be pushed further out
val bound = max(limit, abs(input.yaw))
val target = input.yaw + input.yawDelta
val pushed = target - target.coerceIn(-bound, bound)
return RideLookTake(pushed, 0f, recenterYaw = false, recenterPitch = false)
}
/** Knobs the bird reads every frame, resolved at most once a tick so MoLang doesn't run per frame. */
private fun frameTuning(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): BirdTuning {
val cached = state.frameTuning.get()
if (cached != null && cached.tick == vehicle.tickCount) return cached
val runtime = vehicle.runtime
return BirdTuning(
tick = vehicle.tickCount,
rollEngage = runtime.resolveFloat(settings.cameraRollEngage ?: globalBird.cameraRollEngage!!),
rollRelease = runtime.resolveFloat(settings.cameraRollRelease ?: globalBird.cameraRollRelease!!),
rollHalflife = runtime.resolveFloat(settings.cameraRollHalflife ?: globalBird.cameraRollHalflife!!),
hoverLookYawLimit = runtime.resolveFloat(settings.hoverLookYawLimit ?: globalBird.hoverLookYawLimit!!),
turnRollLimit = runtime.resolveFloat(settings.turnRollLimit ?: globalBird.turnRollLimit!!),
maxRoll = runtime.resolveFloat(settings.maxRoll ?: globalBird.maxRoll!!),
rollSettleAngle = runtime.resolveFloat(settings.rollSettleAngle ?: globalBird.rollSettleAngle!!),
rollSettleDelay = runtime.resolveFloat(settings.timeToRollCorrect ?: globalBird.timeToRollCorrect!!),
handRollShare = runtime.resolveFloat(settings.handRollShare ?: globalBird.handRollShare!!)
).also { state.frameTuning.set(it) }
}
/** Smoothstep of [t] clamped to 0 to 1. */
private fun smoothstep01(t: Float): Float {
val x = t.coerceIn(0f, 1f)
return x * x * (3f - 2f * x)
}
/**
* Eases roll that would take the bird further from level to a stop at [maxRoll] degrees.
* Rolling back toward level is never held back.
*/
private fun capRoll(rollDelta: Double, roll: Float, maxRoll: Float): Double {
if (rollDelta.sign != roll.sign.toDouble()) return rollDelta
val room = (maxRoll - abs(roll)).coerceAtLeast(0f)
val ease = (room / ROLL_CAP_BAND_DEGREES).coerceIn(0f, 1f)
val eased = rollDelta * ease * ease * (3f - 2f * ease)
// Never step past the cap in one frame
return eased.coerceIn(-room.toDouble(), room.toDouble())
}
/** Signed degrees from [from] to [to] turning around [axis]. Both have to be perpendicular to it. */
private fun signedAngle(from: Vector3f, to: Vector3f, axis: Vector3f): Float {
return atan2(Vector3f(from).cross(to).dot(axis), from.dot(to)) * Mth.RAD_TO_DEG
}
/** [vector] with the part along [axis] removed, or null if nothing is left. */
private fun flatten(vector: Vector3f, axis: Vector3f): Vector3f? {
val flat = Vector3f(vector).sub(Vector3f(axis).mul(vector.dot(axis)))
return if (flat.lengthSquared() > 0f) flat.normalize() else null
}
/** Rotation whose forward is [forward] and up is [up]. They have to be unit length and perpendicular. */
private fun levelFrame(forward: Vector3f, up: Vector3f): Quaternionf {
val right = Vector3f(forward).cross(up)
val back = Vector3f(forward).negate()
return Quaternionf().setFromNormalized(Matrix3f(right, up, back))
}
override fun asMoLangValue(
settings: BirdSettings,
state: BirdState,
@@ -793,39 +655,6 @@ class BirdSettings : RidingBehaviourSettings {
// This is coerced between 1 and 0 or things would get weird.
var stamDrainPerRateDive: Expression? = null
private set
// Degrees of roll before the camera starts rolling with the bird
var cameraRollEngage: Expression? = null
private set
// Degrees the bird has to roll back under before the camera levels out. Keep it below engage so it doesn't flicker
var cameraRollRelease: Expression? = null
private set
// Seconds the camera roll spring takes to settle. Raise for a lazier camera
var cameraRollHalflife: Expression? = null
private set
// Degrees either side the rider can look while hovering before pushing further turns the bird
var hoverLookYawLimit: Expression? = null
private set
// Roll in degrees where turning tops out and mouse pitch stops being world up and down
var turnRollLimit: Expression? = null
private set
// Furthest the bird can roll from level in degrees. Keep cameraRollEngage just above it so the camera
// stays level through normal flying and only rolls in loops and flips. 180 lets it roll all the way over
var maxRoll: Expression? = null
private set
// Within this many degrees of level the wings settle back to level once the rider lets go of the mouse
var rollSettleAngle: Expression? = null
private set
// Share of the bird's roll the first person hands lean with, 0 to 1
var handRollShare: Expression? = null
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
@@ -844,14 +673,6 @@ class BirdSettings : RidingBehaviourSettings {
buffer.writeNullableExpression(stamDrainPercRateGlide)
buffer.writeNullableExpression(stamDrainPerRateDive)
buffer.writeNullableExpression(timeToRollCorrect)
buffer.writeNullableExpression(cameraRollEngage)
buffer.writeNullableExpression(cameraRollRelease)
buffer.writeNullableExpression(cameraRollHalflife)
buffer.writeNullableExpression(hoverLookYawLimit)
buffer.writeNullableExpression(turnRollLimit)
buffer.writeNullableExpression(maxRoll)
buffer.writeNullableExpression(rollSettleAngle)
buffer.writeNullableExpression(handRollShare)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
@@ -870,14 +691,6 @@ class BirdSettings : RidingBehaviourSettings {
stamDrainPercRateGlide = buffer.readNullableExpression()
stamDrainPerRateDive = buffer.readNullableExpression()
timeToRollCorrect = buffer.readNullableExpression()
cameraRollEngage = buffer.readNullableExpression()
cameraRollRelease = buffer.readNullableExpression()
cameraRollHalflife = buffer.readNullableExpression()
hoverLookYawLimit = buffer.readNullableExpression()
turnRollLimit = buffer.readNullableExpression()
maxRoll = buffer.readNullableExpression()
rollSettleAngle = buffer.readNullableExpression()
handRollShare = buffer.readNullableExpression()
}
}
@@ -888,9 +701,7 @@ class BirdState : RidingBehaviourState() {
var currPitchCorrectionForce = ridingState(0.0, Side.CLIENT)
var noInputTimeRoll = ridingState(0.0, Side.CLIENT)
var noInputTimePitch = ridingState(0.0, Side.CLIENT)
var staminaDiveForce = ridingState(0.0, Side.CLIENT)
// Client only cache of the knobs read every frame, never synced
var frameTuning = ridingState<BirdTuning?>(null, Side.CLIENT)
var currUpsideDownForce = ridingState(0.0, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
@@ -910,7 +721,6 @@ class BirdState : RidingBehaviourState() {
currPitchCorrectionForce.set(0.0, forced = true)
noInputTimeRoll.set(0.0, forced = true)
noInputTimePitch.set(0.0, forced = true)
frameTuning.set(null, forced = true)
}
override fun toString(): String {
@@ -947,26 +757,3 @@ fun Float.angleDifference(other: Float): Float {
while (diff > 180F) diff -= 360F
return diff
}
/** Bird knobs resolved for one tick. */
data class BirdTuning(
val tick: Int,
val rollEngage: Float,
val rollRelease: Float,
val rollHalflife: Float,
val hoverLookYawLimit: Float,
val turnRollLimit: Float,
val maxRoll: Float,
val rollSettleAngle: Float,
val rollSettleDelay: Float,
val handRollShare: Float
)
/** Bird's camera roll smoothing, kept in the camera context between frames. */
class BirdCameraRoll {
var engaged = false
// World space up the camera had last frame, null until the first frame
var cameraUp: Vector3f? = null
// Degrees left between the camera and where it's heading, plus how fast that's closing
val spring = FloatSpring()
}
@@ -1,938 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.orientation.OrientationController
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RideSteerInput
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.util.DistanceFieldLayer
import com.cobblemon.mod.common.api.riding.util.DistanceFieldTuning
import com.cobblemon.mod.common.api.riding.util.PrimitiveShape
import com.cobblemon.mod.common.api.riding.util.RideFrame
import com.cobblemon.mod.common.api.riding.util.collectPrimitivesFromBlocks
import com.cobblemon.mod.common.api.riding.util.estimateGradient
import com.cobblemon.mod.common.api.riding.util.sampleField
import com.cobblemon.mod.common.api.riding.util.visualizeDistanceField
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import net.minecraft.client.Minecraft
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sign
import kotlin.math.sqrt
import net.minecraft.core.Direction
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
class BoardBehaviour : RidingBehaviour<BoardSettings, BoardState> {
companion object {
val KEY = cobblemonResource("air/board")
}
override val key = KEY
val globalBoard: BoardSettings
get() = CobblemonRideSettings.board
override fun getRidingStyle(settings: BoardSettings, state: BoardState): RidingStyle {
return RidingStyle.LAND
}
val poseProvider = PoseProvider<BoardSettings, BoardState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, vehicle ->
return@PoseOption state.walking.get()
})
override fun isActive(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
// Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any false
}
// This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !blockState.isAir && blockState.fluidState.isEmpty
}
true
}
}
override fun pose(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): PoseType {
return this.poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player
): Float {
return state.rideVelocity.get().length().toFloat()
}
override fun tick(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
if (vehicle.level().isClientSide) {
handleSprinting(state, driver)
inAirCheck(state, vehicle)
tickStamina(settings, state, vehicle)
state.walking.set(state.rideVelocity.get().horizontalDistance() > 0.1 || driver.zza != 0.0f)
//surfDistCheck(state, vehicle)
}
//TODO: should be a config somewhere
val debugVisualizeField = true;
if (false && debugVisualizeField && !vehicle.level().isClientSide && vehicle.tickCount % 5 == 0) {
visualizeDistanceField(
referenceEntity = vehicle,
center = vehicle.position().add(0.0, (vehicle.bbHeight * 0.5f).toDouble(), 0.0),
layers = listOf(
DistanceFieldLayer(isoValue = 2.5f, color = Vector3f(1f, 0.2f, 0.2f))
),
shape = PrimitiveShape.ELLIPSOID,
horizontalRadius = 4.0,
verticalRange = 4.0,
sampleSpacing = 0.3,
marchStep = 0.15,
tuning = DistanceFieldTuning(smoothingRange = 0.25f)
)
}
}
/**
* Check how far the vehicle is from the "surface"
*/
fun surfDistCheck(
state: BoardState,
vehicle: PokemonEntity,
) {
val center = vehicle.position()
val horizontalRadius = 4.0;
val verticalRange = 4.0;
val level = vehicle.level()
//if (level !is ServerLevel) return
// Take a cubic area around the vehicle and gather SDF primitives from it.
val searchBox = AABB(
center.x - horizontalRadius, center.y - verticalRange, center.z - horizontalRadius,
center.x + horizontalRadius, center.y + verticalRange, center.z + horizontalRadius
)
val primitives = collectPrimitivesFromBlocks(level,
searchBox,
PrimitiveShape.ELLIPSOID,
includeFluids = true
)
val sp = center.toVector3f() // Sample Point
val tuning = DistanceFieldTuning(smoothingRange = 0.25f)
val sd = sampleField(primitives, sp.x, sp.y, sp.z, tuning)
state.surfDist.set(sd)
}
fun inAirCheck(
state: BoardState,
vehicle: PokemonEntity,
) {
// Check both vertical movement and if there are blocks below.
val posBelow = vehicle.blockPosition().below()
val blockStateBelow = vehicle.level().getBlockState(posBelow)
val isAirOrLiquid = blockStateBelow.isAir || !blockStateBelow.fluidState.isEmpty
val canSupportEntity = blockStateBelow.isFaceSturdy(vehicle.level(), posBelow, Direction.UP)
val standingOnSolid = canSupportEntity && !isAirOrLiquid
// inAir if not on the ground
val inAir = !(vehicle.deltaMovement.y == 0.0 || standingOnSolid)
state.inAir.set(true)
}
fun handleSprinting(
state: BoardState,
driver: Player
) {
val sprintTimerTickLength = 7 // Emulate vanillas tick timer length
var hasDoubleTapped = false
// If you are not sprinting and you just pressed down and theres no
if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && !state.sprinting.get() && state.sprintTickTimer.get() == 0) {
state.sprintTickTimer.set(sprintTimerTickLength)
} else if (!state.forwardHeldLastTick.get() && Minecraft.getInstance().options.keyUp.isDown() && state.sprintTickTimer.get() != 0) { // If has double tapped
hasDoubleTapped = true
}else if(!state.sprinting.get() && state.sprintTickTimer.get() > 0) {
state.sprintTickTimer.set(state.sprintTickTimer.get() - 1)
} else {
state.sprintTickTimer.set(0)
}
val tryingToSprint = (Minecraft.getInstance().options.keySprint.isDown() && Minecraft.getInstance().options.keyUp.isDown()) || hasDoubleTapped
if (state.stamina.get() <= 0.0f || !Minecraft.getInstance().options.keyUp.isDown()) {
// If stamina runs out or the player is not holding forward then stop sprinting
state.sprinting.set(false)
if (state.stamina.get() <= 0.0f) {
state.sprintToggleable.set(false)
}
} else if (!state.sprinting.get() && !state.sprintToggleable.get() && state.stamina.get() > 0.33f) {
// If you are not sprinting and sprint is not toggleable and you're not trying to sprint and stamina is above 0.3 then enable the toggle
state.sprintToggleable.set(true)
} else if (tryingToSprint && state.sprintToggleable.get()) {
// If you are trying to sprint and the toggle allows it then start sprinting and disable toggle
state.sprinting.set(true)
}
// Take not of whether forward was held last tick
state.forwardHeldLastTick.set(Minecraft.getInstance().options.keyUp.isDown())
}
fun tickStamina(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
) {
val stam = state.stamina.get()
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalBoard.infiniteStamina!!)) {
return
}
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalBoard.staminaExpr!!)).toFloat() / 20.0f
val newStam = if (state.sprinting.get()) {
max(0.0f,stam - stamDrainRate)
} else {
min(1.0f,stam + stamDrainRate * 4)
}
state.stamina.set(newStam.coerceIn(0F, 1F))
}
override fun updatePassengerRotation(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: LivingEntity
) { return }
override fun clampPassengerRotation(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: LivingEntity
) { }
override fun rotation(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
val turnAmount = calcRotAmount(settings, state, vehicle, driver)
return Vec2(vehicle.xRot, vehicle.yRot + turnAmount )
}
/*
* Calculates the rotation amount given the difference between
* the current player y rot and entity y rot. This gives the
* affect of influencing a rides rotation through the mouse
* without it being instant.
*/
fun calcRotAmount(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: LivingEntity
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBoard.speedExpr!!)
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalBoard.handlingExpr!!)
val walkHandlingBoost = 5
val maxYawDiff = vehicle.runtime.resolveFloat(settings.lookYawLimit ?: globalBoard.lookYawLimit!!)
// Normalize the current rotation diff
val rotDiff = Mth.wrapDegrees(driver.yRot - vehicle.yRot).coerceIn(-maxYawDiff,maxYawDiff)
val rotDiffNorm = rotDiff / maxYawDiff//(rotDiff / maxYawDiff).coerceIn(-0.2f, 0.2f)
// Take the square root so that the ride levels out quicker when at lower differences between entity
// y and driver y
// This influences the speed of the turn based on how far in one direction you're looking
val minRotMod = 0.4f // Min speed that the rotation modulation converges towards
val rotDiffMod = ((sqrt(abs(rotDiffNorm)) * (1.0f - minRotMod)) + minRotMod) * rotDiffNorm.sign
// Turn rate should be quick when walking and slower when sprinting.
// Smoothly move between these based on speed
val walkSpeed = getWalkSpeed(vehicle)
val w = max(walkSpeed, vehicle.deltaMovement.horizontalDistance())
val invRelSpeed = (RidingBehaviour.scaleToRange(w, walkSpeed, topSpeed ) - 1.0f) * -1.0f
val turnRate = ((handling.toFloat() / 20.0f) * max(walkHandlingBoost * sqrt(abs(invRelSpeed)), 1.0)).toFloat()
// Ensure you only ever rotate as much difference as there is between the angles.
val turnSpeed = turnRate * rotDiffMod
val rotAmount = turnSpeed.coerceIn(-abs(rotDiff), abs(rotDiff))
return rotAmount
}
private fun spawnGradientDebugParticles(vehicle: PokemonEntity, normal: Vec3) {
val level = vehicle.level()
val headPos = vehicle.position().add(0.0, vehicle.bbHeight.toDouble(), 0.0)
val steps = 20
val length = 2.0
for (i in 0..steps) {
val t = i.toDouble() / steps
val point = headPos.add(normal.scale(length * t))
level.addParticle(ParticleTypes.END_ROD, point.x, point.y, point.z, 0.0, 0.0, 0.0)
}
}
// This ride works along its yaw so its velocity gets turned into world space here
override fun velocity(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return RideFrame.yawToWorld(localVelocity(settings, state, vehicle, driver, input), vehicle.yRot)
}
private fun localVelocity(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
state.rideVelocity.set(calculateRideSpaceVel(settings, state, vehicle, driver))
return state.rideVelocity.get()
}
private fun calculateRideSpaceVel(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val canSprint = vehicle.runtime.resolveBoolean(settings.canSprint ?: globalBoard.canSprint!!)
val canJump = vehicle.runtime.resolveBoolean(settings.canJump ?: globalBoard.canJump!!)
val jumpForce = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalBoard.jumpExpr!!) * 0.75
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBoard.speedExpr!!)
val walkSpeed = getWalkSpeed(vehicle)
val topSpeed = if(canSprint && state.sprinting.get()) rideTopSpeed else walkSpeed
val accelTime = if(state.sprinting.get()) (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalBoard.accelerationExpr!!) * 20.0) else 0.5 * 20.0
val accel = topSpeed / accelTime
var activeInput = false
/******************************************************
* Gather the previous velocity and check for horizontal
* collisions
*****************************************************/
var newVelocity = state.rideVelocity.get() //.normalize().scale(vehicle.deltaMovement.horizontalDistance())
/******************************************************
* Gather the previous velocity and check for horizontal
* collisions
*****************************************************/
if (vehicle.horizontalCollision) {
var horizontalVec = Vec3(newVelocity.x, 0.0, newVelocity.z)
horizontalVec = horizontalVec.normalize().scale(min(horizontalVec.length(), 0.3))
newVelocity = Vec3(horizontalVec.x, newVelocity.y, horizontalVec.z)
}
/******************************************************
* Speed up and slow down based on input
*****************************************************/
if (driver.zza != 0.0f) {
// If on a tight turn then do not speed up past half of top speed in order to turn quicker
// Also determine how fast to be slowing down based on how far turned you are
val lookYawLimit = vehicle.runtime.resolveFloat(settings.lookYawLimit ?: globalBoard.lookYawLimit!!)
val percOfMaxTurnSpeed = abs(Mth.wrapDegrees(driver.yRot - vehicle.yRot) / lookYawLimit) * 100.0f
val turnPercThresh = 0.0f
val s = min(((percOfMaxTurnSpeed - turnPercThresh) / (100.0f - turnPercThresh)).pow(1),1.0f)
val effectiveTopSpeed = if (percOfMaxTurnSpeed > turnPercThresh) topSpeed / max(2.0f*s,1.0f) else topSpeed
val turningSlowDown = s * 0.1
//make sure it can't exceed top speed
val forwardInput = when {
driver.zza > 0 && newVelocity.z > effectiveTopSpeed -> 0.0
driver.zza < 0 && newVelocity.z < (-effectiveTopSpeed / 3.0) -> 0.0
else -> driver.zza.sign
}
// Add extra friction if trying to slow down to turn faster
if (newVelocity.z > effectiveTopSpeed) {
newVelocity = newVelocity.subtract(0.0, 0.0, min(turningSlowDown * newVelocity.z.sign, newVelocity.z))
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
activeInput = true
}
/******************************************************
* Gravity logic
*****************************************************/
// if(state.surfDist.get() > 1.6f) {
// val gravity = (9.8 / ( 20.0)) * 0.2 * 0.6
// val terminalVel = 2.0
// newVelocity = Vec3(newVelocity.x, max(newVelocity.y - gravity, -terminalVel), newVelocity.z)
// } else if(state.surfDist.get() > 1.4f && state.surfDist.get() <= 1.6f) {
// newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
// } else if(state.surfDist.get() <= 1.4f) {
// val gravity = -(9.8 / ( 20.0)) * 0.2 * 0.6
// val terminalVel = -2.0
// newVelocity = Vec3(newVelocity.x, min(newVelocity.y - gravity, -terminalVel), newVelocity.z)
// }
/******************************************************
* Hover logic - non-predictive: correct against current position
*****************************************************/
val correctionGain = 0.5
val maxCorrectionSpeed = 0.3
val center = vehicle.position()
val horizontalRadius = 4.0;
val verticalRange = 4.0;
val level = vehicle.level()
// Take a cubic area around the vehicle and gather SDF primitives from it.
val searchBox = AABB(
center.x - horizontalRadius, center.y - verticalRange, center.z - horizontalRadius,
center.x + horizontalRadius, center.y + vehicle.bbHeight, center.z + horizontalRadius
)
val primitives = collectPrimitivesFromBlocks(level,
searchBox,
PrimitiveShape.ELLIPSOID,
includeFluids = true
)
val tuning = DistanceFieldTuning(smoothingRange = 0.25f)
val currentPos = vehicle.position()
val currentDist = sampleField(primitives, currentPos.x.toFloat(), currentPos.y.toFloat(), currentPos.z.toFloat(), tuning)
val normal = estimateGradient(primitives, currentPos, tuning)
state.surfaceNormal.set(normal)
//spawnGradientDebugParticles(vehicle, normal) // LETS SEE IT!
// Only correct when under the hover surface.
// Otherwise allow gravity to do its thing
// if (currentDist < targetHoverDist) {
// val error = currentDist - targetHoverDist
// val normalSpeed = newVelocity.dot(normal)
// val tangentialVelocity = newVelocity.subtract(normal.scale(normalSpeed))
//
// val correctionSpeed = (-error * correctionGain).coerceIn(-maxCorrectionSpeed, maxCorrectionSpeed)
// newVelocity = tangentialVelocity.add(normal.scale(correctionSpeed))
// } else {
// val hoverCoolnessLowGravFactor = 0.5
// val gravity = (9.8 / ( 20.0)) * 0.2 * 0.6 * hoverCoolnessLowGravFactor
// val terminalVel = 2.0 * hoverCoolnessLowGravFactor
// newVelocity = Vec3(newVelocity.x, max(newVelocity.y - gravity, -terminalVel), newVelocity.z)
// }
/******************************************************
* Hover logic
*****************************************************/
val hoverActivationDist = 3.0f
val targetHoverDist = 1.0f
val hoverCoolnessLowGravFactor = 0.70
val gravity = (9.8 / 20.0) * 0.2 * 0.6 * hoverCoolnessLowGravFactor
val terminalVel = 2.0 * hoverCoolnessLowGravFactor
// val repelStrength = 0.05 // tested and was alright
// val hoverDamping = 0.15
val repelStrength = 0.1
val hoverDamping = 0.2
val surfDist = currentDist
if (surfDist > hoverActivationDist) {
// Free fall - no surface influence yet, same as before.
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - gravity, -terminalVel), newVelocity.z)
} else {
// Getting close - push back toward targetHoverDist, harder the nearer we are.
val closeness = ((hoverActivationDist - surfDist) / (hoverActivationDist - targetHoverDist)).coerceIn(0f, 1.5f)
val repelForce = closeness * repelStrength
val dampingForce = -newVelocity.y * hoverDamping // resists overshoot/bounce
newVelocity = Vec3(newVelocity.x, newVelocity.y + repelForce + dampingForce - gravity * 0.3, newVelocity.z)
}
/******************************************************
* Ground Friction
// *****************************************************/
// if( (newVelocity.horizontalDistance() > 0 && vehicle.onGround() && !activeInput) || newVelocity.horizontalDistance() > topSpeed) {
// newVelocity = newVelocity.subtract(0.0, 0.0, min(0.03 * newVelocity.z.sign, newVelocity.z))
// }
/******************************************************
* Jump Logic
*****************************************************/
if (state.jumpTicks.get() > 0 || (state.jumpTicks.get() >= 0 && driver.jumping && vehicle.onGround() && canJump && driver.deltaMovement.y <= 0.1)) {
// Spread out jumpforce so that variable height jumps are possible
val jumpInputTicks = 6
if (driver.jumping && (state.jumpTicks.get() >= 0 && state.jumpTicks.get() < jumpInputTicks)) {
val appliedJumpForce = ((jumpForce*1.5) / jumpInputTicks) //* (1 - state.jumpTicks.get() / jumpInputTicks)
newVelocity = Vec3(newVelocity.x, newVelocity.y + appliedJumpForce, newVelocity.z)
state.jumpTicks.set(state.jumpTicks.get() + 1)
} else {
// Set delay before next jump is possible after hitting ground
val tickJumpDelay = 3
state.jumpTicks.set(-tickJumpDelay)
}
} else if (vehicle.onGround() && state.jumpTicks.get() < 0) {
// Tick off the delay once reaching the ground
state.jumpTicks.set(state.jumpTicks.get() + 1)
}
// Zero out lateral velocity possibly picked up from a controller transition
newVelocity = Vec3(0.0, newVelocity.y, newVelocity.z)
return newVelocity
}
private fun getWalkSpeed( vehicle: PokemonEntity ): Double {
val walkspeed = vehicle.runtime.resolveDouble(vehicle.behaviour.moving.walk.walkSpeed)
val movementSpeed = vehicle.attributes.getValue(Attributes.MOVEMENT_SPEED)
val speedModifier = 1.2 * 0.35
return walkspeed * movementSpeed * speedModifier
}
private fun rotateOverTime(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
) {
if (vehicle !is OrientationControllable) return
val controller = (vehicle as OrientationControllable).orientationController
// turning
val turnMaxRate = 5.5
val turnAccel = 1.0
val turnFriction = 0.75
val targetYawRate = -driver.xxa.toDouble() * turnMaxRate
val currentYawRate = state.yawAngularVelocity.get()
val newYawRate = if (abs(targetYawRate) > 0.001) {
moveToward(currentYawRate, targetYawRate, turnAccel * deltaTime * 20.0)
} else {
moveToward(currentYawRate, 0.0, turnFriction * deltaTime * 20.0)
}
state.yawAngularVelocity.set(newYawRate)
controller.applyGlobalYaw((newYawRate * deltaTime * 20.0).toFloat())
// up-vector alignment framerate-independent
val upAlignmentRatePerTick = 0.1 // fraction of the remaining gap closed per tick
val frameBlend = 1.0 - (1.0 - upAlignmentRatePerTick).pow(deltaTime * 20.0)
alignUpToSurfaceNormal(controller, state.surfaceNormal.get(), frameBlend.toFloat())
// lean into turns (cosmetic local roll)
val maxLeanDeg = 35.0
val leanPerYawRate = 4.0 * 3.0
val leanRatePerTick = 0.25 * 1.5
val desiredLean = (newYawRate * leanPerYawRate).coerceIn(-maxLeanDeg, maxLeanDeg)
val leanFrameBlend = 1.0 - (1.0 - leanRatePerTick).pow(deltaTime * 20.0)
val leanError = controller.roll.angleDifference(desiredLean.toFloat())
controller.rotateRoll((leanError * leanFrameBlend).toFloat())
}
private fun moveToward(current: Double, target: Double, maxDelta: Double): Double {
val diff = target - current
return if (abs(diff) <= maxDelta) target else current + maxDelta * diff.sign
}
private fun alignUpToSurfaceNormal(controller: OrientationController, targetUp: Vec3, alignmentBlend: Float) {
if (targetUp.lengthSqr() < 1e-6) return
val targetUpVec = Vector3f(targetUp.x.toFloat(), targetUp.y.toFloat(), targetUp.z.toFloat()).normalize()
controller.updateOrientation { current ->
val currentUp = current.transform(Vector3f(OrientationController.UP), Vector3f())
if (currentUp.lengthSquared() < 1e-6f) return@updateOrientation current
// Full rotation that would exactly align currentUp to targetUp...
val fullCorrection = Quaternionf().rotationTo(currentUp, targetUpVec)
// ...but only take a partial step toward it, so alignment is smooth, not instant.
val steppedCorrection = Quaternionf().slerp(fullCorrection, alignmentBlend)
// Pre-multiply (world-space correction), same pattern applyGlobalYaw uses.
Matrix3f().set(steppedCorrection).mul(current, Matrix3f())
}
}
override fun steer(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player,
input: RideSteerInput
) {
input.rotate(mouseRotation(settings, state, vehicle, driver, input.mousePitch, input.mouseYaw, input.mouseYSmoother, input.mouseXSmoother, input.sensitivity, input.deltaTime))
rotateOverTime(settings, state, vehicle, driver, input.deltaTime)
}
private fun mouseRotation(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
//Smooth out mouse input.
val smoothingSpeed = 4
val xInput = mouseXSmoother.getNewDeltaValue(mouseX * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseY * 0.1, deltaTime * smoothingSpeed);
//yaw, pitch, roll
return Vec3(0.0, yInput, xInput)
}
override fun canJump(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity,
driver: Player
): Float {
return if (state.sprinting.get()) 1.15f else 1.0f
}
override fun inertia(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): Double {
return 1.0
}
override fun shouldRoll(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): Boolean {
return true
}
override fun turnOffOnGround(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotateRiderHead(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): Boolean {
return true
}
override fun maxUpStep(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): Float? {
return if (state.inAir.get()) 0.0f else 1.2f
}
override fun getRideSounds(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: BoardSettings) = BoardState()
override fun asMoLangValue(
settings: BoardSettings,
state: BoardState,
vehicle: PokemonEntity
): ObjectValue<RidingBehaviour<BoardSettings, BoardState>> {
val canJump = vehicle.runtime.resolveBoolean(settings.canJump ?: globalBoard.canJump!!)
val value = super.asMoLangValue(settings, state, vehicle)
value.functions.put("sprinting") { DoubleValue(state.sprinting.get()) }
value.functions.put("walking") { DoubleValue(state.walking.get()) }
value.functions.put("in_air") { DoubleValue(state.inAir.get() || (state.rideVelocity.get().y >= 0.0 && (vehicle.controllingPassenger as? Player)?.jumping == true && canJump) || (state.rideVelocity.get().y > 0.0)) }
return value
}
}
class BoardSettings : RidingBehaviourSettings {
override val key = BoardBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteStamina: Expression? = null
private set
var canJump: Expression? = null
private set
var canSprint: Expression? = null
private set
var lookYawLimit: Expression? = null
private set
var speedExpr: Expression? = null
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelerationExpr: Expression? = null
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression? = null
private set
//Between a one block jump and a six block jump
var jumpExpr: Expression? = null
private set
var handlingExpr: Expression? = null
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(canSprint)
buffer.writeNullableExpression(lookYawLimit)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(canSprint)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
canSprint = buffer.readNullableExpression()
lookYawLimit = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
canSprint = buffer.readNullableExpression()
}
}
class BoardState : RidingBehaviourState() {
var sprinting = ridingState(false, Side.CLIENT)
var walking = ridingState(false, Side.BOTH)
var sprintToggleable = ridingState(false, Side.CLIENT)
var inAir = ridingState(false, Side.CLIENT)
var jumpTicks = ridingState(0, Side.CLIENT)
var sprintTickTimer = ridingState(0, Side.CLIENT)
var forwardHeldLastTick = ridingState(false, Side.CLIENT)
var surfDist = ridingState(Float.POSITIVE_INFINITY, Side.BOTH)
var surfaceNormal = ridingState(Vec3(0.0, 1.0, 0.0), Side.CLIENT)
var yawAngularVelocity = ridingState(0.0, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(sprinting.get())
buffer.writeBoolean(walking.get())
buffer.writeBoolean(sprintToggleable.get())
buffer.writeBoolean(inAir.get())
buffer.writeInt(jumpTicks.get())
buffer.writeInt(sprintTickTimer.get())
buffer.writeBoolean(forwardHeldLastTick.get())
buffer.writeFloat(surfDist.get())
buffer.writeVec3(surfaceNormal.get())
buffer.writeDouble(yawAngularVelocity.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
sprinting.set(buffer.readBoolean(), forced = true)
walking.set(buffer.readBoolean(), forced = true)
sprintToggleable.set(buffer.readBoolean(), forced = true)
inAir.set(buffer.readBoolean(), forced = true)
jumpTicks.set(buffer.readInt(), forced = true)
sprintTickTimer.set(buffer.readInt(), forced = true)
forwardHeldLastTick.set(buffer.readBoolean(), forced = true)
surfDist.set(buffer.readFloat(), forced = true)
surfaceNormal.set(buffer.readVec3(), forced = true)
yawAngularVelocity.set(buffer.readDouble(), forced = true)
}
override fun reset() {
super.reset()
sprinting.set(false, forced = true)
walking.set(false, forced = true)
sprintToggleable.set(false, forced = true)
inAir.set(false, forced = true)
jumpTicks.set(0, forced = true)
sprintTickTimer.set(0, forced = true)
forwardHeldLastTick.set(false, forced = true)
surfDist.set(Float.POSITIVE_INFINITY, forced = true)
surfaceNormal.set(Vec3(0.0, 1.0, 0.0), forced = true)
yawAngularVelocity.set(0.0, forced = true)
}
override fun copy() = BoardState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.sprinting.set(this.sprinting.get(), forced = true)
it.walking.set(this.sprinting.get(), forced = true)
it.sprintToggleable.set(this.sprintToggleable.get(), forced = true)
it.inAir.set(this.inAir.get(), forced = true)
it.jumpTicks.set(this.jumpTicks.get(), forced = true)
it.sprintTickTimer.set(this.sprintTickTimer.get(), forced = true)
it.forwardHeldLastTick.set(this.forwardHeldLastTick.get(), forced = true)
it.surfDist.set(this.surfDist.get(), forced = true)
it.surfaceNormal.set(this.surfaceNormal.get(), forced = true)
it.yawAngularVelocity.set(this.yawAngularVelocity.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is BoardState) return false
if (previous.sprinting.get() != sprinting.get()) return true
if (previous.walking.get() != walking.get()) return true
if (previous.sprintToggleable.get() != sprintToggleable.get()) return true
if (previous.inAir.get() != inAir.get()) return true
if (previous.surfDist.get() != surfDist.get()) return true
return super.shouldSync(previous)
}
}

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