forked from mirrors/cobblemon
Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c38630bd6f | ||
|
|
4dba646de9 | ||
|
|
201da371a5 | ||
|
|
d6828baeec | ||
|
|
34f1fd2809 | ||
|
|
09a5fe4c5d | ||
|
|
c42bd7a293 | ||
|
|
33a303a2f5 | ||
|
|
9f7dd84ace | ||
|
|
68e93a13d8 | ||
|
|
611bfd9792 | ||
|
|
0c428526e7 | ||
|
|
b28355b8f2 | ||
|
|
8e163d0e14 | ||
|
|
1660a53867 | ||
|
|
c50ae2d478 | ||
|
|
497de9a8f1 | ||
|
|
f57300dc18 | ||
|
|
0e3c197d9b | ||
|
|
4a12a39287 | ||
|
|
2da2222540 |
@@ -36,11 +36,9 @@ common/src/main/resources/assets/cobblemon/showdown
|
||||
# Generated files
|
||||
*/generated/resources/*
|
||||
/fabric/src/generated/resources/cobblemon-common.accesswidener
|
||||
/fabric/src/main/generated/*
|
||||
/.ignored/
|
||||
common/.fabric
|
||||
.kotlin/
|
||||
|
||||
# MacOS
|
||||
.DS_Store
|
||||
/common/config/*
|
||||
|
||||
+2
-2
@@ -71,8 +71,8 @@ setup_packsquash:
|
||||
- echo "Installing wget & unzip"
|
||||
- yum install -y wget unzip
|
||||
- echo "Downloading PackSquash"
|
||||
- wget https://github.com/ComunidadAylas/PackSquash/releases/download/v0.4.1/packsquash-x86_64-unknown-linux-musl.zip
|
||||
- unzip packsquash-x86_64-unknown-linux-musl.zip -d ./packsquash_bin
|
||||
- wget https://github.com/ComunidadAylas/PackSquash/releases/download/v0.4.0/PackSquash.CLI.executable.x86_64-unknown-linux-musl.zip
|
||||
- unzip PackSquash.CLI.executable.x86_64-unknown-linux-musl.zip -d ./packsquash_bin
|
||||
- echo "Creating PackSquash options.toml"
|
||||
- |
|
||||
cat <<EOF > ./options.toml
|
||||
|
||||
+21
-346
@@ -1,385 +1,60 @@
|
||||
# Changelog
|
||||
## [1.8.0 (MONTH Xth, 2026)](#1-8-0)
|
||||
## [1.7.4 (MONTH Xth, 2026)](#1-7-4)
|
||||
|
||||
### 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 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 mons (+-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`.
|
||||
- A new Biome tag called `#cobblemon:is_stony_beach` has been added.
|
||||
- Gimmighoul towers now generate at ground level rather than sinking one block into the ground.
|
||||
- 1 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
|
||||
|
||||
### New rideable Pokémon
|
||||
- Drampa
|
||||
- Archeops
|
||||
- Skarmory
|
||||
- Crobat
|
||||
- Goodra
|
||||
- Hisuian Goodra
|
||||
- Milotic
|
||||
|
||||
### Pokémon Added
|
||||
- Wurmple
|
||||
- Silcoon
|
||||
- Beautifly
|
||||
- Cascoon
|
||||
- Dustox
|
||||
- Skitty
|
||||
- Delcatty
|
||||
|
||||
- Seviper
|
||||
- Zangoose
|
||||
- Cherubi
|
||||
- Cherrim
|
||||
- Stunky
|
||||
- Skuntank
|
||||
|
||||
- Audino
|
||||
- Pawniard
|
||||
- Bisharp
|
||||
- Kingambit
|
||||
|
||||
- Pancham (+ Cosmetic with Black Glasses)
|
||||
- Pangoro (+ Cosmetic with Black Glasses)
|
||||
|
||||
- Rockruff
|
||||
- Lycanroc
|
||||
|
||||
- Rolycoly
|
||||
- Carkol
|
||||
- Coalossal
|
||||
|
||||
- Snom
|
||||
- Frosmoth
|
||||
|
||||
- Duraludon
|
||||
- Archaludon
|
||||
|
||||
- Applin
|
||||
- Flapple
|
||||
- Appletun
|
||||
- Dipplin
|
||||
- Hydrapple
|
||||
|
||||
- Nymble
|
||||
- Lokix
|
||||
|
||||
#### Regional Forms
|
||||
- Sandshrew (Alolan)
|
||||
- Sandslash (Alolan)
|
||||
- Avalugg (Hisuian)
|
||||
|
||||
### Model updates for the following Pokémon
|
||||
- Goomy
|
||||
- Sliggoo
|
||||
- Goodra
|
||||
- Porygon
|
||||
- Porygon2
|
||||
- Porygon-Z
|
||||
- Zubat
|
||||
- Golbat
|
||||
- Crobat
|
||||
|
||||
### Model updates for the following Pokémon
|
||||
|
||||
- Sandshrew
|
||||
- Sandslash
|
||||
- Nidoking
|
||||
- Zubat
|
||||
- Golbat
|
||||
- Crobat
|
||||
- Porygon
|
||||
- Porygon2
|
||||
- Porygon-Z
|
||||
|
||||
- Skarmory
|
||||
- Jynx
|
||||
- Popplio
|
||||
- Brionne
|
||||
- Primarina
|
||||
- Fletchling
|
||||
- Fletchinder
|
||||
- Talonflame
|
||||
|
||||
- Sableye
|
||||
- Mawile
|
||||
|
||||
- Goomy
|
||||
- Sliggoo
|
||||
- Goodra
|
||||
|
||||
- Nacli
|
||||
- Naclstack
|
||||
- Garganacl
|
||||
|
||||
#### 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
|
||||
- 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
|
||||
|
||||
#### 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
|
||||
- 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
|
||||
|
||||
### Fixes
|
||||
- Adjusted Croagunk's walkspeed
|
||||
- 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 Garchomp T-posing when sleeping while out of battle.
|
||||
- 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.
|
||||
- Decrease default pastured mon wander radius to 32.
|
||||
- Factor in the weight of pokemon 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 Armor 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 Pokedex Screens item tag to support tagged variants of Redstone and Glowstone Dust
|
||||
- Sounds for interacting with various Cobblemon blocks now play from the center of the block, rather than the corner
|
||||
- The `/pc` command no longer plays PC activation sounds
|
||||
- Lowered the volume of Blitzle and Zebstrika's cry
|
||||
- Fixed Raichu (male) and Alolan Raichu (both genders) not displaying the correct Partner Cap when Pewter Crunchies are in the cosmetic slot.
|
||||
- Regional Foods will now prioritize being eaten instead of used for healing Pokémon, shift clicking will use it as healing
|
||||
|
||||
### Fixes
|
||||
- Fixed Croagunk's walkspeed
|
||||
- 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 Pokédex layout for genderless variations
|
||||
- Fixed odd stacking behaviour with the poke_bait item.
|
||||
- Fixes https://gitlab.com/cable-mc/cobblemon/-/issues/1943
|
||||
- Fix occasional crash on retrieving revived fossils
|
||||
- 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 issue where particle like beam can disappear when you don't see the beginning of the beam or you look away
|
||||
- Fixes https://gitlab.com/cable-mc/cobblemon/-/issues/1943
|
||||
- Fix occasional crash on retrieving revived fossils
|
||||
- Fix brushing not working on Neoforge
|
||||
- [Various Submarine Ride Fixes](https://gitlab.com/cable-mc/cobblemon/-/merge_requests/2157)
|
||||
- Fix Pastured mons despawning and running away.
|
||||
- Fix Apricorn trees crashing when other mods bonemeal it
|
||||
- [Various UFO Ride Improvements](https://gitlab.com/cable-mc/cobblemon/-/merge_requests/2156)
|
||||
- Fixed NeoForge brewing stand unexpectedly crashing users when shift clicking items within it's menu
|
||||
- Fixed NPCs switching pokemon 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 Ancient Pokeball's to their respective modifiers
|
||||
- Fixed a Server warning when recalling Pokemon
|
||||
- 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 Guilded 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
|
||||
|
||||
### 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.
|
||||
|
||||
### 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)`
|
||||
- q.item.is_enchanted()
|
||||
- q.item.has_enchantment(minecraft:sharpness, 3)
|
||||
- Added `has_chosen_starter` & `get_starter_uuid` as available Molang functions
|
||||
- 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.
|
||||
|
||||
### Particles
|
||||
- Added `marks`, `has_mark`, & `remove_marks` as available Molang functions for pokemon
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
- Japanese
|
||||
- German
|
||||
- Ecuadorian Spanish
|
||||
- Simplified Chinese
|
||||
- Traditional Chinese
|
||||
- Korean
|
||||
@@ -1,9 +0,0 @@
|
||||
- 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.
|
||||
@@ -0,0 +1,510 @@
|
||||
{
|
||||
"format_version": "1.21.11",
|
||||
"credit": "Made with Blockbench",
|
||||
"texture_size": [128, 128],
|
||||
"textures": {
|
||||
"2": "block/texture"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [9.999, 10.999, -0.001],
|
||||
"to": [15.001, 12.001, 0.001],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, -1, 1]},
|
||||
"faces": {
|
||||
"north": {"uv": [0.25, 1.125, 0.875, 1.25], "texture": "#2"},
|
||||
"east": {"uv": [0.25, 1.125, 0.25, 1.25], "texture": "#2"},
|
||||
"south": {"uv": [0.875, 1.125, 1.5, 1.25], "texture": "#2"},
|
||||
"west": {"uv": [0.875, 1.125, 0.875, 1.25], "texture": "#2"},
|
||||
"up": {"uv": [0.875, 1.125, 0.25, 1.125], "texture": "#2"},
|
||||
"down": {"uv": [1.5, 1.125, 0.875, 1.125], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [-0.0001, 8.9999, 2.9999],
|
||||
"to": [2.0001, 13.0001, 13.0001],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, -1, 2]},
|
||||
"faces": {
|
||||
"north": {"uv": [4.25, 6, 4.5, 6.5], "texture": "#2"},
|
||||
"east": {"uv": [3, 6, 4.25, 6.5], "texture": "#2"},
|
||||
"south": {"uv": [5.75, 6, 6, 6.5], "texture": "#2"},
|
||||
"west": {"uv": [4.5, 6, 5.75, 6.5], "texture": "#2"},
|
||||
"up": {"uv": [4.5, 6, 4.25, 4.75], "texture": "#2"},
|
||||
"down": {"uv": [4.75, 4.75, 4.5, 6], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [-0.0001, 8.9999, -0.0001],
|
||||
"to": [16.0001, 13.0001, 3.0001],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, -1, 1]},
|
||||
"faces": {
|
||||
"north": {"uv": [0.375, 3.375, 2.375, 3.875], "texture": "#2"},
|
||||
"east": {"uv": [0, 3.375, 0.375, 3.875], "texture": "#2"},
|
||||
"south": {"uv": [2.75, 3.375, 4.75, 3.875], "texture": "#2"},
|
||||
"west": {"uv": [2.375, 3.375, 2.75, 3.875], "texture": "#2"},
|
||||
"up": {"uv": [2.375, 3.375, 0.375, 3], "texture": "#2"},
|
||||
"down": {"uv": [4.375, 3, 2.375, 3.375], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [2.995, 10.995, -0.005],
|
||||
"to": [15.005, 13.005, 2.005],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, -1, 1]},
|
||||
"faces": {
|
||||
"north": {"uv": [0.25, 6.75, 1.75, 7], "texture": "#2"},
|
||||
"east": {"uv": [0, 6.75, 0.25, 7], "texture": "#2"},
|
||||
"south": {"uv": [2, 6.75, 3.5, 7], "texture": "#2"},
|
||||
"west": {"uv": [1.75, 6.75, 2, 7], "texture": "#2"},
|
||||
"up": {"uv": [1.75, 6.75, 0.25, 6.5], "texture": "#2"},
|
||||
"down": {"uv": [3.25, 6.5, 1.75, 6.75], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [9.999, 10.999, -0.001],
|
||||
"to": [15.001, 12.001, 0.001],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, -1, 1]},
|
||||
"faces": {
|
||||
"north": {"uv": [0.25, 1.375, 0.875, 1.5], "texture": "#2"},
|
||||
"east": {"uv": [0.25, 1.375, 0.25, 1.5], "texture": "#2"},
|
||||
"south": {"uv": [0.875, 1.375, 1.5, 1.5], "texture": "#2"},
|
||||
"west": {"uv": [0.875, 1.375, 0.875, 1.5], "texture": "#2"},
|
||||
"up": {"uv": [0.875, 1.375, 0.25, 1.375], "texture": "#2"},
|
||||
"down": {"uv": [1.5, 1.375, 0.875, 1.375], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [13.9999, 8.9999, 2.9999],
|
||||
"to": [16.0001, 13.0001, 13.0001],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, -1, 2]},
|
||||
"faces": {
|
||||
"north": {"uv": [1.25, 6, 1.5, 6.5], "texture": "#2"},
|
||||
"east": {"uv": [0, 6, 1.25, 6.5], "texture": "#2"},
|
||||
"south": {"uv": [2.75, 6, 3, 6.5], "texture": "#2"},
|
||||
"west": {"uv": [1.5, 6, 2.75, 6.5], "texture": "#2"},
|
||||
"up": {"uv": [1.5, 6, 1.25, 4.75], "texture": "#2"},
|
||||
"down": {"uv": [1.75, 4.75, 1.5, 6], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [-0.0001, 8.9999, 12.9999],
|
||||
"to": [16.0001, 13.0001, 16.0001],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, -1, 1]},
|
||||
"faces": {
|
||||
"north": {"uv": [0.375, 4.25, 2.375, 4.75], "texture": "#2"},
|
||||
"east": {"uv": [0, 4.25, 0.375, 4.75], "texture": "#2"},
|
||||
"south": {"uv": [2.75, 4.25, 4.75, 4.75], "texture": "#2"},
|
||||
"west": {"uv": [2.375, 4.25, 2.75, 4.75], "texture": "#2"},
|
||||
"up": {"uv": [2.375, 4.25, 0.375, 3.875], "texture": "#2"},
|
||||
"down": {"uv": [4.375, 3.875, 2.375, 4.25], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [1, 0, 1],
|
||||
"to": [15, 10, 15],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 1]},
|
||||
"faces": {
|
||||
"north": {"uv": [1.75, 1.75, 3.5, 3], "texture": "#2"},
|
||||
"east": {"uv": [0, 1.75, 1.75, 3], "texture": "#2"},
|
||||
"south": {"uv": [5.25, 1.75, 7, 3], "texture": "#2"},
|
||||
"west": {"uv": [3.5, 1.75, 5.25, 3], "texture": "#2"},
|
||||
"up": {"uv": [3.5, 1.75, 1.75, 0], "texture": "#2"},
|
||||
"down": {"uv": [5.25, 0, 3.5, 1.75], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [3.999, 1.999, 0.999],
|
||||
"to": [12.001, 10.001, 1.001],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 1]},
|
||||
"faces": {
|
||||
"north": {"uv": [0, 7.375, 1, 8.375], "texture": "#2"},
|
||||
"east": {"uv": [0, 7.375, 0, 8.375], "texture": "#2"},
|
||||
"south": {"uv": [1, 7.375, 2, 8.375], "texture": "#2"},
|
||||
"west": {"uv": [1, 7.375, 1, 8.375], "texture": "#2"},
|
||||
"up": {"uv": [1, 7.375, 0, 7.375], "texture": "#2"},
|
||||
"down": {"uv": [2, 7.375, 1, 7.375], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [2, 11, 5],
|
||||
"to": [14, 12, 6],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 11, 1]},
|
||||
"faces": {
|
||||
"north": {"uv": [5.125, 3.375, 6.625, 3.5], "texture": "#2"},
|
||||
"east": {"uv": [4.75, 3.375, 4.875, 3.5], "texture": "#2"},
|
||||
"south": {"uv": [7, 3.375, 8.5, 3.5], "texture": "#2"},
|
||||
"west": {"uv": [6.875, 3.375, 7, 3.5], "texture": "#2"},
|
||||
"up": {"uv": [6.625, 3.125, 5.125, 3], "texture": "#2"},
|
||||
"down": {"uv": [8.125, 3, 6.625, 3.125], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [2, 11, 4],
|
||||
"to": [14, 12, 5],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 11, 1]},
|
||||
"faces": {
|
||||
"north": {"uv": [5.125, 3.375, 6.625, 3.5], "texture": "#2"},
|
||||
"east": {"uv": [4.875, 3.375, 5, 3.5], "texture": "#2"},
|
||||
"south": {"uv": [7, 3.375, 8.5, 3.5], "texture": "#2"},
|
||||
"west": {"uv": [6.75, 3.375, 6.875, 3.5], "texture": "#2"},
|
||||
"up": {"uv": [6.625, 3.25, 5.125, 3.125], "texture": "#2"},
|
||||
"down": {"uv": [8.125, 3.125, 6.625, 3.25], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [2, 11, 3],
|
||||
"to": [14, 12, 4],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 11, 1]},
|
||||
"faces": {
|
||||
"north": {"uv": [5.125, 3.375, 6.625, 3.5], "texture": "#2"},
|
||||
"east": {"uv": [5, 3.375, 5.125, 3.5], "texture": "#2"},
|
||||
"south": {"uv": [7, 3.375, 8.5, 3.5], "texture": "#2"},
|
||||
"west": {"uv": [6.625, 3.375, 6.75, 3.5], "texture": "#2"},
|
||||
"up": {"uv": [6.625, 3.375, 5.125, 3.25], "texture": "#2"},
|
||||
"down": {"uv": [8.125, 3.25, 6.625, 3.375], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [10, 11, 6],
|
||||
"to": [14, 12, 8],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 11, 1]},
|
||||
"faces": {
|
||||
"north": {"uv": [5, 4.25, 5.5, 4.375], "texture": "#2"},
|
||||
"east": {"uv": [4.75, 4.25, 5, 4.375], "texture": "#2"},
|
||||
"south": {"uv": [5.75, 4.25, 6.25, 4.375], "texture": "#2"},
|
||||
"west": {"uv": [5.5, 4.25, 5.75, 4.375], "texture": "#2"},
|
||||
"up": {"uv": [5.5, 4.25, 5, 4], "texture": "#2"},
|
||||
"down": {"uv": [6, 4, 5.5, 4.25], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [2, 11, 6],
|
||||
"to": [6, 12, 8],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 11, 1]},
|
||||
"faces": {
|
||||
"north": {"uv": [5, 4.625, 5.5, 4.75], "texture": "#2"},
|
||||
"east": {"uv": [4.75, 4.625, 5, 4.75], "texture": "#2"},
|
||||
"south": {"uv": [5.75, 4.625, 6.25, 4.75], "texture": "#2"},
|
||||
"west": {"uv": [5.5, 4.625, 5.75, 4.75], "texture": "#2"},
|
||||
"up": {"uv": [5.5, 4.625, 5, 4.375], "texture": "#2"},
|
||||
"down": {"uv": [6, 4.375, 5.5, 4.625], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [2, 11, 10],
|
||||
"to": [14, 12, 11],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 11, 6]},
|
||||
"faces": {
|
||||
"north": {"uv": [5.125, 3.875, 6.625, 4], "texture": "#2"},
|
||||
"east": {"uv": [5, 3.875, 5.125, 4], "texture": "#2"},
|
||||
"south": {"uv": [7, 3.875, 8.5, 4], "texture": "#2"},
|
||||
"west": {"uv": [6.625, 3.875, 6.75, 4], "texture": "#2"},
|
||||
"up": {"uv": [6.625, 3.875, 5.125, 3.75], "texture": "#2"},
|
||||
"down": {"uv": [8.125, 3.75, 6.625, 3.875], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [2, 11, 11],
|
||||
"to": [14, 12, 12],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 11, 6]},
|
||||
"faces": {
|
||||
"north": {"uv": [5.125, 3.875, 6.625, 4], "texture": "#2"},
|
||||
"east": {"uv": [4.875, 3.875, 5, 4], "texture": "#2"},
|
||||
"south": {"uv": [7, 3.875, 8.5, 4], "texture": "#2"},
|
||||
"west": {"uv": [6.75, 3.875, 6.875, 4], "texture": "#2"},
|
||||
"up": {"uv": [6.625, 3.75, 5.125, 3.625], "texture": "#2"},
|
||||
"down": {"uv": [8.125, 3.625, 6.625, 3.75], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [2, 11, 12],
|
||||
"to": [14, 12, 13],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 11, 6]},
|
||||
"faces": {
|
||||
"north": {"uv": [5.125, 3.875, 6.625, 4], "texture": "#2"},
|
||||
"east": {"uv": [4.75, 3.875, 4.875, 4], "texture": "#2"},
|
||||
"south": {"uv": [7, 3.875, 8.5, 4], "texture": "#2"},
|
||||
"west": {"uv": [6.875, 3.875, 7, 4], "texture": "#2"},
|
||||
"up": {"uv": [6.625, 3.625, 5.125, 3.5], "texture": "#2"},
|
||||
"down": {"uv": [8.125, 3.5, 6.625, 3.625], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [10, 11, 8],
|
||||
"to": [14, 12, 10],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 11, 6]},
|
||||
"faces": {
|
||||
"north": {"uv": [6.25, 5, 6.75, 5.125], "texture": "#2"},
|
||||
"east": {"uv": [6, 5, 6.25, 5.125], "texture": "#2"},
|
||||
"south": {"uv": [7, 5, 7.5, 5.125], "texture": "#2"},
|
||||
"west": {"uv": [6.75, 5, 7, 5.125], "texture": "#2"},
|
||||
"up": {"uv": [6.75, 5, 6.25, 4.75], "texture": "#2"},
|
||||
"down": {"uv": [7.25, 4.75, 6.75, 5], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [6, 11, 9],
|
||||
"to": [10, 12, 10],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 11, 6]},
|
||||
"faces": {
|
||||
"north": {"uv": [6.125, 5.625, 6.625, 5.75], "texture": "#2"},
|
||||
"east": {"uv": [6, 5.625, 6.125, 5.75], "texture": "#2"},
|
||||
"south": {"uv": [6.75, 5.625, 7.25, 5.75], "texture": "#2"},
|
||||
"west": {"uv": [6.625, 5.625, 6.75, 5.75], "texture": "#2"},
|
||||
"up": {"uv": [6.625, 5.625, 6.125, 5.5], "texture": "#2"},
|
||||
"down": {"uv": [7.125, 5.5, 6.625, 5.625], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [2, 11, 8],
|
||||
"to": [6, 12, 10],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [-3, 11, 6]},
|
||||
"faces": {
|
||||
"north": {"uv": [6.25, 5.375, 6.75, 5.5], "texture": "#2"},
|
||||
"east": {"uv": [6, 5.375, 6.25, 5.5], "texture": "#2"},
|
||||
"south": {"uv": [7, 5.375, 7.5, 5.5], "texture": "#2"},
|
||||
"west": {"uv": [6.75, 5.375, 7, 5.5], "texture": "#2"},
|
||||
"up": {"uv": [6.75, 5.375, 6.25, 5.125], "texture": "#2"},
|
||||
"down": {"uv": [7.25, 5.125, 6.75, 5.375], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [9, 11, 7],
|
||||
"to": [10, 12, 9],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 11, 6]},
|
||||
"faces": {
|
||||
"north": {"uv": [6.5, 4.25, 6.625, 4.375], "texture": "#2"},
|
||||
"east": {"uv": [6.25, 4.25, 6.5, 4.375], "texture": "#2"},
|
||||
"south": {"uv": [6.875, 4.25, 7, 4.375], "texture": "#2"},
|
||||
"west": {"uv": [6.625, 4.25, 6.875, 4.375], "texture": "#2"},
|
||||
"up": {"uv": [6.625, 4.25, 6.5, 4], "texture": "#2"},
|
||||
"down": {"uv": [6.75, 4, 6.625, 4.25], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [6, 11, 7],
|
||||
"to": [7, 12, 9],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 11, 6]},
|
||||
"faces": {
|
||||
"north": {"uv": [6.5, 4.625, 6.625, 4.75], "texture": "#2"},
|
||||
"east": {"uv": [6.25, 4.625, 6.5, 4.75], "texture": "#2"},
|
||||
"south": {"uv": [6.875, 4.625, 7, 4.75], "texture": "#2"},
|
||||
"west": {"uv": [6.625, 4.625, 6.875, 4.75], "texture": "#2"},
|
||||
"up": {"uv": [6.625, 4.625, 6.5, 4.375], "texture": "#2"},
|
||||
"down": {"uv": [6.75, 4.375, 6.625, 4.625], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [7, 11, 7],
|
||||
"to": [9, 12, 9],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 11, 6]},
|
||||
"faces": {
|
||||
"north": {"uv": [6.25, 6.25, 6.5, 6.375], "texture": "#2"},
|
||||
"east": {"uv": [6, 6.25, 6.25, 6.375], "texture": "#2"},
|
||||
"south": {"uv": [6.75, 6.25, 7, 6.375], "texture": "#2"},
|
||||
"west": {"uv": [6.5, 6.25, 6.75, 6.375], "texture": "#2"},
|
||||
"up": {"uv": [6.5, 6.25, 6.25, 6], "texture": "#2"},
|
||||
"down": {"uv": [6.75, 6, 6.5, 6.25], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [6, 11, 6],
|
||||
"to": [10, 12, 7],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 11, 6]},
|
||||
"faces": {
|
||||
"north": {"uv": [6.125, 5.875, 6.625, 6], "texture": "#2"},
|
||||
"east": {"uv": [6, 5.875, 6.125, 6], "texture": "#2"},
|
||||
"south": {"uv": [6.75, 5.875, 7.25, 6], "texture": "#2"},
|
||||
"west": {"uv": [6.625, 5.875, 6.75, 6], "texture": "#2"},
|
||||
"up": {"uv": [6.625, 5.875, 6.125, 5.75], "texture": "#2"},
|
||||
"down": {"uv": [7.125, 5.75, 6.625, 5.875], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 13, 11],
|
||||
"to": [0, 15, 12],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 0, -2]},
|
||||
"faces": {
|
||||
"north": {"uv": [6.375, 6.5, 6.375, 6.75], "texture": "#2"},
|
||||
"east": {"uv": [6.25, 6.5, 6.375, 6.75], "texture": "#2"},
|
||||
"south": {"uv": [6.5, 6.5, 6.5, 6.75], "texture": "#2"},
|
||||
"west": {"uv": [6.375, 6.5, 6.5, 6.75], "texture": "#2"},
|
||||
"up": {"uv": [6.375, 6.5, 6.375, 6.375], "texture": "#2"},
|
||||
"down": {"uv": [6.375, 6.375, 6.375, 6.5], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 13, 14],
|
||||
"to": [0, 20, 15],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 1]},
|
||||
"faces": {
|
||||
"north": {"uv": [6.125, 6.5, 6.125, 7.375], "texture": "#2"},
|
||||
"east": {"uv": [6, 6.5, 6.125, 7.375], "texture": "#2"},
|
||||
"south": {"uv": [6.25, 6.5, 6.25, 7.375], "texture": "#2"},
|
||||
"west": {"uv": [6.125, 6.5, 6.25, 7.375], "texture": "#2"},
|
||||
"up": {"uv": [6.125, 6.5, 6.125, 6.375], "texture": "#2"},
|
||||
"down": {"uv": [6.125, 6.375, 6.125, 6.5], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 13, 14],
|
||||
"to": [0, 20, 15],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 1]},
|
||||
"faces": {
|
||||
"north": {"uv": [6.625, 6.5, 6.625, 7.375], "texture": "#2"},
|
||||
"east": {"uv": [6.5, 6.5, 6.625, 7.375], "texture": "#2"},
|
||||
"south": {"uv": [6.75, 6.5, 6.75, 7.375], "texture": "#2"},
|
||||
"west": {"uv": [6.625, 6.5, 6.75, 7.375], "texture": "#2"},
|
||||
"up": {"uv": [6.625, 6.5, 6.625, 6.375], "texture": "#2"},
|
||||
"down": {"uv": [6.625, 6.375, 6.625, 6.5], "texture": "#2"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [0, 13, 14],
|
||||
"to": [0, 20, 15],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [1, 0, 1]},
|
||||
"faces": {
|
||||
"north": {"uv": [6.875, 6.5, 6.875, 7.375], "texture": "#2"},
|
||||
"east": {"uv": [6.75, 6.5, 6.875, 7.375], "texture": "#2"},
|
||||
"south": {"uv": [7, 6.5, 7, 7.375], "texture": "#2"},
|
||||
"west": {"uv": [6.875, 6.5, 7, 7.375], "texture": "#2"},
|
||||
"up": {"uv": [6.875, 6.5, 6.875, 6.375], "texture": "#2"},
|
||||
"down": {"uv": [6.875, 6.375, 6.875, 6.5], "texture": "#2"}
|
||||
}
|
||||
}
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"name": "bone",
|
||||
"origin": [1, 0, 1],
|
||||
"color": 0,
|
||||
"children": [
|
||||
{
|
||||
"name": "bone9",
|
||||
"origin": [12.5, 11.5, 0],
|
||||
"color": 0,
|
||||
"children": [0]
|
||||
},
|
||||
1,
|
||||
2,
|
||||
{
|
||||
"name": "bone5",
|
||||
"origin": [9, 12, 1],
|
||||
"color": 0,
|
||||
"children": [3]
|
||||
},
|
||||
{
|
||||
"name": "bone4",
|
||||
"origin": [12.5, 11.5, 0],
|
||||
"color": 0,
|
||||
"children": [4]
|
||||
},
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
{
|
||||
"name": "bone8",
|
||||
"origin": [8, 6, 1],
|
||||
"color": 0,
|
||||
"children": [8]
|
||||
},
|
||||
{
|
||||
"name": "opening",
|
||||
"origin": [8, 11.5, 6],
|
||||
"color": 0,
|
||||
"children": [
|
||||
{
|
||||
"name": "bone3",
|
||||
"origin": [8, 11.5, 3],
|
||||
"color": 0,
|
||||
"children": [
|
||||
{
|
||||
"name": "bone10",
|
||||
"origin": [8, 11.5, 5.5],
|
||||
"color": 0,
|
||||
"children": [
|
||||
9,
|
||||
{
|
||||
"name": "bone11",
|
||||
"origin": [8, 11.5, 4.5],
|
||||
"color": 0,
|
||||
"children": [
|
||||
10,
|
||||
{
|
||||
"name": "bone12",
|
||||
"origin": [8, 11.5, 3.5],
|
||||
"color": 0,
|
||||
"children": [11]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
12,
|
||||
13
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "bone2",
|
||||
"origin": [8, 11.5, 13.0375],
|
||||
"color": 0,
|
||||
"children": [
|
||||
{
|
||||
"name": "bone13",
|
||||
"origin": [8, 11.5, 10.5],
|
||||
"color": 0,
|
||||
"children": [
|
||||
14,
|
||||
{
|
||||
"name": "bone14",
|
||||
"origin": [8, 11.5, 11.5],
|
||||
"color": 0,
|
||||
"children": [15]
|
||||
},
|
||||
{
|
||||
"name": "bone15",
|
||||
"origin": [8, 11.5, 12.5],
|
||||
"color": 0,
|
||||
"children": [16]
|
||||
}
|
||||
]
|
||||
},
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
24,
|
||||
{
|
||||
"name": "bone7",
|
||||
"origin": [1, 0, 1],
|
||||
"color": 0,
|
||||
"children": [
|
||||
{
|
||||
"name": "bone6",
|
||||
"origin": [0, 16.5, 14.5],
|
||||
"color": 0,
|
||||
"children": [25]
|
||||
},
|
||||
{
|
||||
"name": "bone5red",
|
||||
"origin": [0, 16.5, 14.5],
|
||||
"color": 0,
|
||||
"children": [26]
|
||||
},
|
||||
{
|
||||
"name": "bone8green",
|
||||
"origin": [0, 16.5, 14.5],
|
||||
"color": 0,
|
||||
"children": [27]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
import utilities.isSnapshot
|
||||
import utilities.version
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import utilities.isSnapshot
|
||||
import utilities.version
|
||||
|
||||
/*
|
||||
*
|
||||
@@ -72,10 +72,6 @@ tasks.withType<Test> {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
exclude("data/**/README.md")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
blossom {
|
||||
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.camera;
|
||||
|
||||
import com.cobblemon.mod.common.client.util.compat.SodiumCompat;
|
||||
import com.cobblemon.mod.common.platform.events.ClientChunkEvent;
|
||||
import com.cobblemon.mod.common.platform.events.PlatformEvents;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import kotlin.Unit;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class CameraClientChunkCacheExtension {
|
||||
private static final int MIN_RENDER_DISTANCE = 2;
|
||||
private static final int MAX_RENDER_DISTANCE = 64;
|
||||
private static final int RENDER_DISTANCE_MARGIN = 2;
|
||||
|
||||
private static final Long2ObjectOpenHashMap<LevelChunk> CHUNK_MAP = new Long2ObjectOpenHashMap<>();
|
||||
private static final Long2ObjectOpenHashMap<LevelChunk> CHUNK_MAP_OTHER_THREADS = new Long2ObjectOpenHashMap<>();
|
||||
private static volatile ChunkPos CAMERA_CENTER;
|
||||
|
||||
private CameraClientChunkCacheExtension() {}
|
||||
|
||||
public static void drop(ClientLevel level, ChunkPos chunkPos) {
|
||||
if (Minecraft.getInstance().isSameThread()) {
|
||||
long chunkPosLong = chunkPos.toLong();
|
||||
LevelChunk chunk = CHUNK_MAP.get(chunkPosLong);
|
||||
|
||||
if (chunk != null) {
|
||||
modifyChunkMaps(map -> map.remove(chunkPosLong));
|
||||
SodiumCompat.getInstance().notifyChunkRemoved(level, chunkPos.x, chunkPos.z);
|
||||
PlatformEvents.CLIENT_CHUNK_UNLOAD.postEvents(new ClientChunkEvent.Unload(chunk, level));
|
||||
level.unload(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static LevelChunk getChunk(int x, int z) {
|
||||
long chunkPos = ChunkPos.asLong(x, z);
|
||||
|
||||
if (Minecraft.getInstance().isSameThread())
|
||||
return CHUNK_MAP.get(chunkPos);
|
||||
else {
|
||||
synchronized (CHUNK_MAP_OTHER_THREADS) {
|
||||
return CHUNK_MAP_OTHER_THREADS.get(chunkPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static LevelChunk replaceWithPacketData(ClientLevel level, int x, int z, FriendlyByteBuf packetData, CompoundTag chunkTag, Consumer<ClientboundLevelChunkPacketData.BlockEntityTagOutput> tagOutput) {
|
||||
if (!Minecraft.getInstance().isSameThread())
|
||||
throw new UnsupportedOperationException("replaceWithPacketData called off-thread, this shouldn't happen!");
|
||||
|
||||
ChunkPos chunkPos = new ChunkPos(x, z);
|
||||
long longChunkPos = chunkPos.toLong();
|
||||
LevelChunk chunk = CHUNK_MAP.get(longChunkPos);
|
||||
|
||||
if (chunk == null) {
|
||||
LevelChunk newChunk = new LevelChunk(level, chunkPos);
|
||||
|
||||
chunk = newChunk;
|
||||
chunk.replaceWithPacketData(packetData, chunkTag, tagOutput);
|
||||
modifyChunkMaps(map -> map.put(longChunkPos, newChunk));
|
||||
}
|
||||
|
||||
level.onChunkLoaded(chunkPos);
|
||||
SodiumCompat.getInstance().notifyChunkAdded(level, x, z);
|
||||
PlatformEvents.CLIENT_CHUNK_LOAD.postEvents(new ClientChunkEvent.Load(chunk, level));
|
||||
return chunk;
|
||||
}
|
||||
|
||||
private static void modifyChunkMaps(Consumer<Long2ObjectOpenHashMap<LevelChunk>> operation) {
|
||||
operation.accept(CHUNK_MAP);
|
||||
|
||||
synchronized (CHUNK_MAP_OTHER_THREADS) {
|
||||
operation.accept(CHUNK_MAP_OTHER_THREADS);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
CHUNK_MAP.clear();
|
||||
CHUNK_MAP_OTHER_THREADS.clear();
|
||||
CAMERA_CENTER = null;
|
||||
}
|
||||
|
||||
public static boolean hasCameraChunks() {
|
||||
if (Minecraft.getInstance().isSameThread()) {
|
||||
return !CHUNK_MAP.isEmpty();
|
||||
} else {
|
||||
synchronized (CHUNK_MAP_OTHER_THREADS) {
|
||||
return !CHUNK_MAP_OTHER_THREADS.isEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes render distance using Chebyshev distance with a safety margin.
|
||||
*/
|
||||
public static int getSuggestedRenderDistance(ChunkPos center) {
|
||||
int maxDistance = 0;
|
||||
|
||||
if (Minecraft.getInstance().isSameThread()) {
|
||||
for (long key : CHUNK_MAP.keySet()) {
|
||||
int x = ChunkPos.getX(key);
|
||||
int z = ChunkPos.getZ(key);
|
||||
int dx = Math.abs(x - center.x);
|
||||
int dz = Math.abs(z - center.z);
|
||||
maxDistance = Math.max(maxDistance, Math.max(dx, dz));
|
||||
}
|
||||
} else {
|
||||
synchronized (CHUNK_MAP_OTHER_THREADS) {
|
||||
for (long key : CHUNK_MAP_OTHER_THREADS.keySet()) {
|
||||
int x = ChunkPos.getX(key);
|
||||
int z = ChunkPos.getZ(key);
|
||||
int dx = Math.abs(x - center.x);
|
||||
int dz = Math.abs(z - center.z);
|
||||
maxDistance = Math.max(maxDistance, Math.max(dx, dz));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Math.max(MIN_RENDER_DISTANCE, Math.min(MAX_RENDER_DISTANCE, maxDistance + RENDER_DISTANCE_MARGIN));
|
||||
}
|
||||
|
||||
public static void setCameraCenter(ChunkPos center) {
|
||||
CAMERA_CENTER = center;
|
||||
}
|
||||
|
||||
public static ChunkPos getCameraCenter() {
|
||||
return CAMERA_CENTER;
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.render.camera;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import net.minecraft.client.renderer.chunk.SectionRenderDispatcher;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
public final class CameraViewAreaExtension {
|
||||
private static final Long2ObjectOpenHashMap<SectionRenderDispatcher.RenderSection> SECTIONS = new Long2ObjectOpenHashMap<>();
|
||||
private static SectionRenderDispatcher sectionRenderDispatcher;
|
||||
private static int minSectionY;
|
||||
private static int maxSectionY;
|
||||
|
||||
private CameraViewAreaExtension() {
|
||||
}
|
||||
|
||||
public static void allChanged(SectionRenderDispatcher dispatcher, Level level) {
|
||||
clear();
|
||||
sectionRenderDispatcher = dispatcher;
|
||||
minSectionY = level.getMinSection();
|
||||
maxSectionY = level.getMaxSection();
|
||||
}
|
||||
|
||||
public static SectionRenderDispatcher.RenderSection rawFetch(int cx, int cy, int cz, boolean generateNew) {
|
||||
if (cy < minSectionY || cy >= maxSectionY) {
|
||||
return null;
|
||||
}
|
||||
|
||||
long sectionPos = SectionPos.asLong(cx, cy, cz);
|
||||
return generateNew ? provideSection(sectionPos) : SECTIONS.get(sectionPos);
|
||||
}
|
||||
|
||||
public static void setDirty(int cx, int cy, int cz, boolean playerChanged) {
|
||||
SectionRenderDispatcher.RenderSection section = rawFetch(cx, cy, cz, false);
|
||||
if (section != null) {
|
||||
section.setDirty(playerChanged);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onChunkUnload(int sectionX, int sectionZ) {
|
||||
for (int sectionY = minSectionY; sectionY < maxSectionY; sectionY++) {
|
||||
long sectionPos = SectionPos.asLong(sectionX, sectionY, sectionZ);
|
||||
SectionRenderDispatcher.RenderSection section = SECTIONS.remove(sectionPos);
|
||||
|
||||
if (section != null) {
|
||||
section.releaseBuffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
for (SectionRenderDispatcher.RenderSection section : SECTIONS.values()) {
|
||||
section.releaseBuffers();
|
||||
}
|
||||
SECTIONS.clear();
|
||||
}
|
||||
|
||||
public static int minSectionY() {
|
||||
return minSectionY;
|
||||
}
|
||||
|
||||
public static int maxSectionY() {
|
||||
return maxSectionY;
|
||||
}
|
||||
|
||||
private static SectionRenderDispatcher.RenderSection provideSection(long sectionPos) {
|
||||
return SECTIONS.computeIfAbsent(sectionPos, CameraViewAreaExtension::createSection);
|
||||
}
|
||||
|
||||
private static SectionRenderDispatcher.RenderSection createSection(long sectionPos) {
|
||||
if (sectionRenderDispatcher == null) {
|
||||
throw new IllegalStateException("SectionRenderDispatcher not initialised");
|
||||
}
|
||||
BlockPos origin = SectionPos.of(sectionPos).origin();
|
||||
return sectionRenderDispatcher.new RenderSection(0, origin.getX(), origin.getY(), origin.getZ());
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.client.render.camera.accessor;
|
||||
|
||||
import net.minecraft.client.renderer.culling.Frustum;
|
||||
|
||||
public interface CameraLevelRendererAccessor {
|
||||
Frustum cobblemonGetFrustum();
|
||||
}
|
||||
@@ -13,6 +13,5 @@ public interface ChannelDuck {
|
||||
void cobblemon$inverseAttenuation(float rolloffFactor);
|
||||
void cobblemon$clearFilters();
|
||||
|
||||
void cobblemon$applyReverb(float decayTime, float gain, float diffusion, float density);
|
||||
void cobblemon$clearReverb();
|
||||
// void cobblemon$exponentialAttenuation(float rolloff, float refDistance, float maxDistance);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.ChannelDuck;
|
||||
import com.mojang.blaze3d.audio.Channel;
|
||||
import org.lwjgl.openal.AL10;
|
||||
import org.lwjgl.openal.EXTEfx;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(Channel.class)
|
||||
public abstract class ChannelMixin implements ChannelDuck {
|
||||
|
||||
@Shadow
|
||||
private int source;
|
||||
|
||||
// Id of filter applied when sound is obstructed
|
||||
private int cobblemon$lowPassFilterId = 0;
|
||||
|
||||
@Override
|
||||
public void cobblemon$applyLowPassFilter(float gain, float hfGain) {
|
||||
// Delete old filter if it exists
|
||||
if (cobblemon$lowPassFilterId != 0) {
|
||||
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, 0); // Detach filter from source
|
||||
EXTEfx.alDeleteFilters(cobblemon$lowPassFilterId); // Delete old filter
|
||||
cobblemon$lowPassFilterId = 0;
|
||||
}
|
||||
|
||||
// Generate a fresh filter
|
||||
int filterId = EXTEfx.alGenFilters();
|
||||
if (!EXTEfx.alIsFilter(filterId)) {
|
||||
System.err.println("Failed to create OpenAL low-pass filter");
|
||||
return;
|
||||
}
|
||||
|
||||
EXTEfx.alFilteri(filterId, EXTEfx.AL_FILTER_TYPE, EXTEfx.AL_FILTER_LOWPASS);
|
||||
EXTEfx.alFilterf(filterId, EXTEfx.AL_LOWPASS_GAIN, gain);
|
||||
EXTEfx.alFilterf(filterId, EXTEfx.AL_LOWPASS_GAINHF, hfGain);
|
||||
|
||||
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, filterId);
|
||||
|
||||
// Save the new filter ID
|
||||
cobblemon$lowPassFilterId = filterId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cobblemon$inverseAttenuation(float rolloffFactor) {
|
||||
AL10.alSourcei(source, AL10.AL_DISTANCE_MODEL, AL10.AL_INVERSE_DISTANCE);
|
||||
AL10.alSourcef(source, AL10.AL_ROLLOFF_FACTOR, rolloffFactor);
|
||||
AL10.alSourcef(source, AL10.AL_REFERENCE_DISTANCE, 1.0f);
|
||||
AL10.alSourcef(source, AL10.AL_MAX_DISTANCE, 0.0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cobblemon$clearFilters() {
|
||||
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, 0);
|
||||
|
||||
if (EXTEfx.alIsFilter(cobblemon$lowPassFilterId)) {
|
||||
EXTEfx.alDeleteFilters(cobblemon$lowPassFilterId);
|
||||
cobblemon$lowPassFilterId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ 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)
|
||||
@@ -35,15 +36,15 @@ public class ClientPacketListenerMixin {
|
||||
for (int passenger : packet.getPassengers()) {
|
||||
passengers.add(this.level.getEntity(passenger));
|
||||
}
|
||||
pokemon.getOccupiedSeats().values().stream()
|
||||
Arrays
|
||||
.stream(pokemon.getOccupiedSeats())
|
||||
.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());
|
||||
var occupiedSeats = Arrays.asList(pokemon.getOccupiedSeats());
|
||||
passengers.stream()
|
||||
.filter(x -> !currentPassengers.contains(x))
|
||||
.filter(x -> !occupiedSeats.contains(x))
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(x -> x.startRiding(pokemon, true));
|
||||
ci.cancel();
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin;
|
||||
|
||||
import com.cobblemon.mod.common.block.entity.DiscShelfBlockEntity;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.NoteBlock;
|
||||
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(NoteBlock.class)
|
||||
public abstract class NoteBlockMixin {
|
||||
@Inject(method = "triggerEvent", at = @At("HEAD"), cancellable = true)
|
||||
private void cobblemon$triggerTMShelfSequencer(BlockState state, Level level, BlockPos pos, int type, int data, CallbackInfoReturnable<Boolean> cir) {
|
||||
final BlockPos shelfPos = pos.below();
|
||||
final BlockEntity blockEntity = level.getBlockEntity(shelfPos);
|
||||
if (!(blockEntity instanceof DiscShelfBlockEntity discShelfBlockEntity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!level.isClientSide) {
|
||||
final BlockPos instrumentPos = shelfPos.below();
|
||||
final SoundEvent soundEvent;
|
||||
if (level.getBlockState(instrumentPos).isAir()) {
|
||||
soundEvent = SoundEvents.NOTE_BLOCK_BIT.value();
|
||||
} else {
|
||||
final NoteBlockInstrument instrument = level.getBlockState(instrumentPos).instrument();
|
||||
final Holder<SoundEvent> holder = instrument.getSoundEvent();
|
||||
soundEvent = holder.value();
|
||||
}
|
||||
discShelfBlockEntity.onNoteBlockPulse(level, pos, soundEvent);
|
||||
}
|
||||
|
||||
// Suppress vanilla note-block playback while it is on top of a TM Shelf.
|
||||
cir.setReturnValue(false);
|
||||
}
|
||||
}
|
||||
@@ -19,10 +19,8 @@ import com.cobblemon.mod.common.mixin.accessor.TrackedEntityAccessor;
|
||||
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket;
|
||||
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket;
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.ClientboundUpdateRidingStatePacket;
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.ClientboundSeatAssignmentPacket;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.ServerEntity;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
@@ -39,7 +37,6 @@ import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -56,15 +53,12 @@ public abstract class ServerEntityMixin {
|
||||
private Matrix3f cobblemon$lastSentOrientation;
|
||||
@Unique
|
||||
private boolean cobblemon$lastSentActive;
|
||||
@Unique
|
||||
private List<Entity> cobblemon$lastSeatPassengers = List.of();
|
||||
|
||||
@Inject(method = "sendChanges", at = @At("TAIL"))
|
||||
private void cobblemon$sendChanges(CallbackInfo ci) {
|
||||
cobblemon$sendOrientationChanges();
|
||||
cobblemon$sendRidingStateChanges();
|
||||
cobblemon$sendDriverInput();
|
||||
cobblemon$sendOccupiedSeatsChange();
|
||||
}
|
||||
|
||||
private void cobblemon$sendRidingStateChanges() {
|
||||
@@ -107,24 +101,6 @@ public abstract class ServerEntityMixin {
|
||||
cobblemon$broadcast(new ClientboundUpdateDriverInputPacket(driverInput, entity.getId()));
|
||||
}
|
||||
|
||||
private void cobblemon$sendOccupiedSeatsChange() {
|
||||
if (!(this.entity instanceof PokemonEntity pokemon)) return;
|
||||
|
||||
List<Entity> currentPassengers = pokemon.getPassengers();
|
||||
if (currentPassengers.equals(cobblemon$lastSeatPassengers)) return;
|
||||
cobblemon$lastSeatPassengers = List.copyOf(currentPassengers);
|
||||
|
||||
pokemon.getOccupiedSeats().forEach((seat, passenger) -> {
|
||||
if (seat.getLocator() == null) return;
|
||||
if (passenger == null) return;
|
||||
cobblemon$broadcast(new ClientboundSeatAssignmentPacket(
|
||||
passenger.getId(),
|
||||
pokemon.getId(),
|
||||
seat.getLocator()
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
private void cobblemon$broadcast(NetworkPacket<?> packet) {
|
||||
if (!(entity.level() instanceof ServerLevel level)) return;
|
||||
ChunkMap chunkMap = level.getChunkSource().chunkMap;
|
||||
@@ -139,4 +115,5 @@ public abstract class ServerEntityMixin {
|
||||
CobblemonNetwork.INSTANCE.sendPacketToPlayer(player, packet);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import net.minecraft.client.sounds.ChannelAccess;
|
||||
import net.minecraft.client.sounds.SoundEngine;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
@@ -33,7 +32,6 @@ import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
import java.util.Iterator;
|
||||
@@ -110,17 +108,6 @@ public abstract class SoundEngineMixin implements SoundEngineDuck {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TM Shelf can intentionally push note-block pitches above vanilla's 2.0f ceiling for octave-up playback.
|
||||
* Keep vanilla clamping for all other sounds.
|
||||
*/
|
||||
@Inject(method = "calculatePitch", at = @At("HEAD"), cancellable = true)
|
||||
private void cobblemon$allowExtendedNoteblockPitch(SoundInstance sound, CallbackInfoReturnable<Float> cir) {
|
||||
if (sound.getLocation().getPath().startsWith("block.note_block.")) {
|
||||
cir.setReturnValue(Mth.clamp(sound.getPitch(), 0.5F, 4.0F));
|
||||
}
|
||||
}
|
||||
|
||||
/** Different behavior for resuming SoundInstances while a BattleMusicInstance is being played. We do not want to resume filtered sounds. */
|
||||
@Inject(method = "resume()V", at = @At("HEAD"), cancellable = true)
|
||||
public void resume(CallbackInfo cb) {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.world.CobblemonStructureIDs;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
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.Structure;
|
||||
import net.minecraft.world.level.levelgen.structure.StructurePiece;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureStart;
|
||||
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.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Mixin(Structure.class)
|
||||
public abstract class StructureMixin {
|
||||
// Define a set of structures that should not spawn below Sea Level
|
||||
@Unique
|
||||
private static final Set<ResourceLocation> RESTRICTED_STRUCTURES = new HashSet<>(Arrays.asList(
|
||||
CobblemonStructureIDs.STONJOURNER_HENGE,
|
||||
CobblemonStructureIDs.LUNA_HENGE,
|
||||
CobblemonStructureIDs.SOL_HENGE
|
||||
));
|
||||
|
||||
@Inject(method = "generate", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/levelgen/structure/StructureStart;isValid()Z"),
|
||||
cancellable = true)
|
||||
public void cobblemon$isValid(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) {
|
||||
ResourceLocation structureKey = registryAccess.registryOrThrow(Registries.STRUCTURE).getKey((Structure) (Object) this);
|
||||
if(!RESTRICTED_STRUCTURES.contains(structureKey)) {
|
||||
return;
|
||||
}
|
||||
for (StructurePiece piece : structureStart.getPieces()) {
|
||||
if (piece.getBoundingBox().minY() < chunkGenerator.getSeaLevel()) {
|
||||
cir.setReturnValue(StructureStart.INVALID_START);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin.accessor;
|
||||
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(Camera.class)
|
||||
public interface CameraAccessor {
|
||||
@Accessor("level")
|
||||
BlockGetter getLevel();
|
||||
|
||||
@Accessor("detached")
|
||||
void setDetached(boolean detached);
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin.accessor;
|
||||
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.renderer.GameRenderer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(GameRenderer.class)
|
||||
public interface GameRendererAccessor {
|
||||
@Accessor("mainCamera")
|
||||
Camera cobblemon$getMainCamera();
|
||||
|
||||
@Accessor("mainCamera")
|
||||
void cobblemon$setMainCamera(Camera camera);
|
||||
}
|
||||
+2
@@ -10,7 +10,9 @@ package com.cobblemon.mod.common.mixin.accessor;
|
||||
|
||||
import net.minecraft.client.gui.components.StateSwitchingButton;
|
||||
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(RecipeBookComponent.class)
|
||||
|
||||
+2
-1
@@ -15,9 +15,11 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.world.Containers;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.alchemy.PotionBrewing;
|
||||
import net.minecraft.world.item.crafting.RecipeHolder;
|
||||
import net.minecraft.world.item.crafting.RecipeManager;
|
||||
@@ -32,7 +34,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Mixin(BrewingStandBlockEntity.class)
|
||||
public class BrewingStandBlockEntityMixin {
|
||||
|
||||
|
||||
@@ -9,11 +9,17 @@
|
||||
package com.cobblemon.mod.common.mixin.brewing;
|
||||
|
||||
import com.cobblemon.mod.common.duck.RecipeAwareSlot;
|
||||
import com.cobblemon.mod.common.item.crafting.brewingstand.BrewingStandRecipe;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.BrewingStandMenu;
|
||||
import net.minecraft.world.inventory.ContainerData;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
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;
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.item.crafting.brewingstand.BrewingStandRecipe;
|
||||
import net.minecraft.world.inventory.BrewingStandMenu;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.RecipeManager;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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.camera;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import com.cobblemon.mod.common.api.camera.CameraManager;
|
||||
import com.cobblemon.mod.common.api.camera.CameraManager.ChunkViewUpdate;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.ChunkTrackingView;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
@Mixin(value = ChunkMap.class, priority = 1100)
|
||||
public abstract class ChunkMapMixin {
|
||||
private static final Logger COBBLEMON$CAMERA_LOGGER = LogUtils.getLogger();
|
||||
|
||||
@Shadow
|
||||
protected abstract void markChunkPendingToSend(ServerPlayer player, ChunkPos pos);
|
||||
|
||||
@Shadow
|
||||
private static void markChunkPendingToSend(ServerPlayer player, LevelChunk chunk) {}
|
||||
|
||||
@Shadow
|
||||
private static void dropChunk(ServerPlayer player, ChunkPos chunkPos) {}
|
||||
|
||||
@Inject(method = "updateChunkTracking", at = @At("HEAD"))
|
||||
private void cobblemon$handleCameraChunkUpdates(ServerPlayer player, CallbackInfo ci) {
|
||||
ChunkViewUpdate update = CameraManager.consumeChunkViewUpdate(player);
|
||||
|
||||
if (update != null) {
|
||||
ChunkTrackingView playerView = player.getChunkTrackingView();
|
||||
ChunkTrackingView current = update.getCurrent();
|
||||
ChunkTrackingView previous = update.getPrevious();
|
||||
|
||||
if (current != null) {
|
||||
ChunkTrackingView.difference(playerView, current, chunkPos -> cobblemon$queueChunkForPlayer(player, chunkPos), chunkPos -> {});
|
||||
}
|
||||
|
||||
if (previous != null) {
|
||||
Set<ChunkPos> droppingChunks = new HashSet<>();
|
||||
|
||||
ChunkTrackingView targetComparison = current != null ? current : playerView;
|
||||
ChunkTrackingView.difference(previous, targetComparison, chunkPos -> {}, droppingChunks::add);
|
||||
|
||||
for (ChunkPos chunkPos : droppingChunks) {
|
||||
if (playerView == null || !playerView.contains(chunkPos)) {
|
||||
dropChunk(player, chunkPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "onChunkReadyToSend", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;getChunkTrackingView()Lnet/minecraft/server/level/ChunkTrackingView;"))
|
||||
private void cobblemon$queueCameraChunk(LevelChunk chunk, CallbackInfo ci, @Local ServerPlayer player) {
|
||||
ChunkTrackingView view = CameraManager.getPlayerChunkView(player);
|
||||
|
||||
if (view != null && view.contains(chunk.getPos())) {
|
||||
markChunkPendingToSend(player, chunk);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "isChunkTracked", at = @At("HEAD"), cancellable = true)
|
||||
private void cobblemon$isCameraChunkTracked(ServerPlayer player, int x, int z, CallbackInfoReturnable<Boolean> cir) {
|
||||
ChunkTrackingView view = CameraManager.getPlayerChunkView(player);
|
||||
|
||||
if (view != null && view.contains(x, z) && !player.connection.chunkSender.isPending(ChunkPos.asLong(x, z))) {
|
||||
cir.setReturnValue(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "dropChunk", at = @At("HEAD"), cancellable = true)
|
||||
private static void cobblemon$preventCameraChunkDrop(ServerPlayer player, ChunkPos pos, CallbackInfo ci) {
|
||||
ChunkTrackingView view = CameraManager.getPlayerChunkView(player);
|
||||
|
||||
if (view != null && view.contains(pos)) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void cobblemon$queueChunkForPlayer(ServerPlayer player, ChunkPos chunkPos) {
|
||||
markChunkPendingToSend(player, chunkPos);
|
||||
}
|
||||
}
|
||||
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.camera.CameraFeedHandler;
|
||||
import com.cobblemon.mod.common.client.render.camera.CameraViewAreaExtension;
|
||||
import com.cobblemon.mod.common.client.render.camera.accessor.CameraLevelRendererAccessor;
|
||||
import com.cobblemon.mod.common.client.util.compat.SodiumCompat;
|
||||
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.client.renderer.LevelRenderer;
|
||||
import net.minecraft.client.renderer.ViewArea;
|
||||
import net.minecraft.client.renderer.chunk.SectionRenderDispatcher;
|
||||
import net.minecraft.client.renderer.culling.Frustum;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.SectionPos;
|
||||
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.ModifyVariable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(value = LevelRenderer.class, priority = 1100)
|
||||
public abstract class CameraLevelRendererMixin implements CameraLevelRendererAccessor {
|
||||
private static final int CINEMATIC_RENDER_DISTANCE_CHUNKS = 32;
|
||||
private static final float CINEMATIC_FOG_DISTANCE = CINEMATIC_RENDER_DISTANCE_CHUNKS * 16;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private Minecraft minecraft;
|
||||
@Shadow
|
||||
private SectionRenderDispatcher sectionRenderDispatcher;
|
||||
@Shadow
|
||||
private ClientLevel level;
|
||||
|
||||
@Shadow private Frustum capturedFrustum;
|
||||
|
||||
@Shadow public Frustum cullingFrustum;
|
||||
|
||||
@WrapWithCondition(method = "setupRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ViewArea;repositionCamera(DD)V"))
|
||||
private boolean cobblemon$shouldRepositionCamera(ViewArea viewArea, double x, double z) {
|
||||
return !CameraFeedHandler.isCapturing();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel vanilla setupRender during feed capture to avoid overwriting
|
||||
* the camera feed's visible sections. When Sodium is active, let it
|
||||
* proceed since Sodium manages its own chunk rendering pipeline and
|
||||
* needs setupRender to run.
|
||||
*/
|
||||
@Inject(method = "setupRender", at = @At("HEAD"), cancellable = true)
|
||||
private void cobblemon$onSetupRender(Camera camera, Frustum frustum, boolean hasCapturedFrustum, boolean isSpectator, CallbackInfo ci) {
|
||||
if (CameraFeedHandler.isCapturing() && !SodiumCompat.getInstance().isActive())
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(method = "allChanged", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/SectionOcclusionGraph;waitAndReset(Lnet/minecraft/client/renderer/ViewArea;)V"))
|
||||
private void cobblemon$onAllChanged(CallbackInfo ci) {
|
||||
CameraViewAreaExtension.allChanged(sectionRenderDispatcher, level);
|
||||
}
|
||||
|
||||
@Inject(method = "isSectionCompiled", at = @At("HEAD"), cancellable = true)
|
||||
private void cobblemon$onIsSectionCompiled(BlockPos pos, CallbackInfoReturnable<Boolean> cir) {
|
||||
if (CameraFeedHandler.isCapturing()) {
|
||||
SectionPos sectionPos = SectionPos.of(pos);
|
||||
SectionRenderDispatcher.RenderSection section = CameraViewAreaExtension.rawFetch(sectionPos.x(), sectionPos.y(), sectionPos.z(), false);
|
||||
|
||||
if (section != null && section.getCompiled() != SectionRenderDispatcher.CompiledSection.UNCOMPILED) {
|
||||
cir.setReturnValue(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/FogRenderer;setupColor(Lnet/minecraft/client/Camera;FLnet/minecraft/client/multiplayer/ClientLevel;IF)V"), ordinal = 1)
|
||||
private float cobblemon$modifyFogRenderDistance(float original) {
|
||||
if (CameraFeedHandler.isCapturing())
|
||||
return CINEMATIC_FOG_DISTANCE;
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* During feed capture the camera entity is a Marker, which causes the vanilla condition
|
||||
* {@code !(entity instanceof LocalPlayer) || camera.getEntity() == entity} to fail for
|
||||
* the local player — preventing them from rendering.
|
||||
*
|
||||
* By returning the local player from {@code getEntity()} and {@code true} from
|
||||
* {@code isDetached()}, both condition2 and condition3 in the entity rendering check pass:
|
||||
* <ul>
|
||||
* <li>condition2: {@code entity != player || isDetached()==true} → passes</li>
|
||||
* <li>condition3: {@code player == player} → passes</li>
|
||||
* </ul>
|
||||
*/
|
||||
@WrapOperation(
|
||||
method = "renderLevel",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;getEntity()Lnet/minecraft/world/entity/Entity;")
|
||||
)
|
||||
private Entity cobblemon$getCameraEntityDuringCapture(Camera camera, Operation<Entity> original) {
|
||||
if (CameraFeedHandler.isCapturing()) {
|
||||
Entity player = Minecraft.getInstance().player;
|
||||
if (player != null) return player;
|
||||
}
|
||||
return original.call(camera);
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
method = "renderLevel",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;isDetached()Z")
|
||||
)
|
||||
private boolean cobblemon$isCameraDetachedDuringCapture(Camera camera, Operation<Boolean> original) {
|
||||
if (CameraFeedHandler.isCapturing()) {
|
||||
return true;
|
||||
}
|
||||
return original.call(camera);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Frustum cobblemonGetFrustum() {
|
||||
return this.capturedFrustum != null ? this.capturedFrustum : this.cullingFrustum;
|
||||
}
|
||||
}
|
||||
@@ -8,21 +8,32 @@
|
||||
|
||||
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.camera.CameraRotation;
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController;
|
||||
import com.cobblemon.mod.common.api.riding.Rideable;
|
||||
import com.cobblemon.mod.common.client.MountedPokemonAnimationRenderController;
|
||||
import com.cobblemon.mod.common.client.camera.ClientCameraController;
|
||||
import com.cobblemon.mod.common.client.render.camera.CameraFeedHandler;
|
||||
import com.cobblemon.mod.common.client.render.camera.MountedCameraRenderer;
|
||||
import com.cobblemon.mod.common.duck.CameraDuck;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.LevelRenderer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(Camera.class)
|
||||
@@ -31,6 +42,7 @@ public abstract class CameraMixin implements CameraDuck {
|
||||
@Shadow private float eyeHeightOld;
|
||||
@Shadow private BlockGetter level;
|
||||
@Shadow private Entity entity;
|
||||
@Final @Shadow private Quaternionf rotation;
|
||||
@Final @Shadow private Vector3f forwards;
|
||||
@Final @Shadow private Vector3f up;
|
||||
@Final @Shadow private Vector3f left;
|
||||
@@ -42,6 +54,11 @@ public abstract class CameraMixin implements CameraDuck {
|
||||
@Override public BlockGetter cobblemon$getLevel() {
|
||||
return level;
|
||||
}
|
||||
@Shadow protected abstract void setRotation(float yRot, float xRot);
|
||||
|
||||
@Unique private float returnTimer = 0;
|
||||
@Unique private float rollAngleStart = 0;
|
||||
@Unique Minecraft minecraft = Minecraft.getInstance();
|
||||
|
||||
@Override public Entity cobblemon$getEntity() {
|
||||
return entity;
|
||||
@@ -65,6 +82,28 @@ public abstract class CameraMixin implements CameraDuck {
|
||||
Entity vehicle = entity.getVehicle();
|
||||
MountedPokemonAnimationRenderController.INSTANCE.reset();
|
||||
|
||||
// Check for cinematic camera override
|
||||
if (ClientCameraController.isActive()) {
|
||||
// When capturing a feed, use that specific camera's transform
|
||||
// instead of the primary camera to avoid both feeds sharing the same position.
|
||||
java.util.UUID capturedId = CameraFeedHandler.getCurrentlyCapturedCamera();
|
||||
ClientCameraController.ActiveTransform cameraTransform = capturedId != null
|
||||
? ClientCameraController.getActiveCameraTransform(capturedId)
|
||||
: ClientCameraController.getCameraTransform();
|
||||
if (cameraTransform != null) {
|
||||
Vec3 position = cameraTransform.getPosition();
|
||||
CameraRotation rotation = cameraTransform.getRotation();
|
||||
setPosition(new Vec3(position.x, position.y, position.z));
|
||||
setRotation(rotation.getYaw(), rotation.getPitch());
|
||||
|
||||
|
||||
OrientationController.Companion.getFORWARDS().rotate(this.rotation, this.forwards);
|
||||
OrientationController.Companion.getUP().rotate(this.rotation, this.up);
|
||||
OrientationController.Companion.getLEFT().rotate(this.rotation, this.left);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(vehicle instanceof Rideable){
|
||||
Vec3 position = MountedCameraRenderer.INSTANCE.getCameraPosition(
|
||||
instance,
|
||||
|
||||
@@ -1,123 +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.duck.ChannelDuck;
|
||||
import com.mojang.blaze3d.audio.Channel;
|
||||
import org.lwjgl.openal.AL10;
|
||||
import org.lwjgl.openal.AL11;
|
||||
import org.lwjgl.openal.EXTEfx;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(Channel.class)
|
||||
public abstract class ChannelMixin implements ChannelDuck {
|
||||
|
||||
@Shadow
|
||||
private int source;
|
||||
|
||||
// Ids of effects applied to the channel
|
||||
private int cobblemon$lowPassFilterId = 0;
|
||||
private int cobblemon$reverbEffectId = 0;
|
||||
private int cobblemon$reverbSlotId = 0;
|
||||
|
||||
@Override
|
||||
public void cobblemon$applyLowPassFilter(float gain, float hfGain) {
|
||||
// Delete old filter if it exists
|
||||
if (cobblemon$lowPassFilterId != 0) {
|
||||
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, 0); // Detach filter from source
|
||||
EXTEfx.alDeleteFilters(cobblemon$lowPassFilterId); // Delete old filter
|
||||
cobblemon$lowPassFilterId = 0;
|
||||
}
|
||||
|
||||
// Generate a fresh filter
|
||||
int filterId = EXTEfx.alGenFilters();
|
||||
if (!EXTEfx.alIsFilter(filterId)) {
|
||||
System.err.println("Failed to create OpenAL low-pass filter");
|
||||
return;
|
||||
}
|
||||
|
||||
EXTEfx.alFilteri(filterId, EXTEfx.AL_FILTER_TYPE, EXTEfx.AL_FILTER_LOWPASS);
|
||||
EXTEfx.alFilterf(filterId, EXTEfx.AL_LOWPASS_GAIN, gain);
|
||||
EXTEfx.alFilterf(filterId, EXTEfx.AL_LOWPASS_GAINHF, hfGain);
|
||||
|
||||
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, filterId);
|
||||
|
||||
// Save the new filter ID
|
||||
cobblemon$lowPassFilterId = filterId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cobblemon$inverseAttenuation(float rolloffFactor) {
|
||||
AL10.alSourcei(source, AL10.AL_DISTANCE_MODEL, AL10.AL_INVERSE_DISTANCE);
|
||||
AL10.alSourcef(source, AL10.AL_ROLLOFF_FACTOR, rolloffFactor);
|
||||
AL10.alSourcef(source, AL10.AL_REFERENCE_DISTANCE, 1.0f);
|
||||
AL10.alSourcef(source, AL10.AL_MAX_DISTANCE, 0.0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cobblemon$clearFilters() {
|
||||
AL10.alSourcei(source, EXTEfx.AL_DIRECT_FILTER, 0);
|
||||
|
||||
if (EXTEfx.alIsFilter(cobblemon$lowPassFilterId)) {
|
||||
EXTEfx.alDeleteFilters(cobblemon$lowPassFilterId);
|
||||
cobblemon$lowPassFilterId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cobblemon$applyReverb(float decayTime, float gain, float diffusion, float density) {
|
||||
// Clean up existing reverb first if it exists
|
||||
cobblemon$clearReverb();
|
||||
|
||||
// Create the effect object
|
||||
int effectId = EXTEfx.alGenEffects();
|
||||
if (!EXTEfx.alIsEffect(effectId)) {
|
||||
System.err.println("Failed to create OpenAL reverb effect");
|
||||
return;
|
||||
}
|
||||
|
||||
EXTEfx.alEffecti(effectId, EXTEfx.AL_EFFECT_TYPE, EXTEfx.AL_EFFECT_REVERB);
|
||||
EXTEfx.alEffectf(effectId, EXTEfx.AL_REVERB_DECAY_TIME, decayTime);
|
||||
EXTEfx.alEffectf(effectId, EXTEfx.AL_REVERB_GAIN, gain);
|
||||
EXTEfx.alEffectf(effectId, EXTEfx.AL_REVERB_DIFFUSION, diffusion);
|
||||
EXTEfx.alEffectf(effectId, EXTEfx.AL_REVERB_DENSITY, density);
|
||||
|
||||
// Create auxiliary effect slot and load effect into it
|
||||
int slotId = EXTEfx.alGenAuxiliaryEffectSlots();
|
||||
if (!EXTEfx.alIsAuxiliaryEffectSlot(slotId)) {
|
||||
System.err.println("Failed to create OpenAL auxiliary effect slot");
|
||||
EXTEfx.alDeleteEffects(effectId);
|
||||
return;
|
||||
}
|
||||
|
||||
EXTEfx.alAuxiliaryEffectSloti(slotId, EXTEfx.AL_EFFECTSLOT_EFFECT, effectId);
|
||||
|
||||
// Route source through the slot — send 0, no direct filter
|
||||
AL11.alSource3i(source, EXTEfx.AL_AUXILIARY_SEND_FILTER, slotId, 0, EXTEfx.AL_FILTER_NULL);
|
||||
|
||||
cobblemon$reverbEffectId = effectId;
|
||||
cobblemon$reverbSlotId = slotId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cobblemon$clearReverb() {
|
||||
if (cobblemon$reverbSlotId != 0) {
|
||||
// Detach source from slot
|
||||
AL11.alSource3i(source, EXTEfx.AL_AUXILIARY_SEND_FILTER,
|
||||
EXTEfx.AL_EFFECTSLOT_NULL, 0, EXTEfx.AL_FILTER_NULL);
|
||||
EXTEfx.alDeleteAuxiliaryEffectSlots(cobblemon$reverbSlotId);
|
||||
cobblemon$reverbSlotId = 0;
|
||||
}
|
||||
if (cobblemon$reverbEffectId != 0) {
|
||||
EXTEfx.alDeleteEffects(cobblemon$reverbEffectId);
|
||||
cobblemon$reverbEffectId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.camera.CameraClientChunkCacheExtension;
|
||||
import com.cobblemon.mod.common.client.camera.CameraChunkVisibilityManager;
|
||||
import com.cobblemon.mod.common.client.render.camera.CameraFeedHandler;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientChunkCache;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
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;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Mixin(value = ClientChunkCache.class, priority = 1100)
|
||||
public abstract class ClientChunkCacheMixin {
|
||||
@Shadow
|
||||
volatile ClientChunkCache.Storage storage;
|
||||
@Shadow
|
||||
@Final
|
||||
ClientLevel level;
|
||||
/**
|
||||
* Intercept updateViewCenter to use camera position when active.
|
||||
*/
|
||||
@Inject(method = "drop", at = @At("HEAD"))
|
||||
private void cobblemon$onDrop(ChunkPos chunkPos, CallbackInfo ci) {
|
||||
int renderDistance = Minecraft.getInstance().options.renderDistance().get();
|
||||
|
||||
if (CameraChunkVisibilityManager.shouldKeepChunk(chunkPos, renderDistance)
|
||||
|| CameraFeedHandler.shouldAddChunk(chunkPos, renderDistance)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CameraClientChunkCacheExtension.drop(level, chunkPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override replaceWithPacketData to accept chunks based on camera position.
|
||||
*/
|
||||
@Inject(method = "replaceWithPacketData", at = @At("HEAD"), cancellable = true)
|
||||
public void cobblemon$onReplaceWithPacketData(int x, int z, FriendlyByteBuf buffer, CompoundTag chunkTag, Consumer<ClientboundLevelChunkPacketData.BlockEntityTagOutput> tagOutputConsumer, CallbackInfoReturnable<LevelChunk> cir) {
|
||||
int renderDistance = Minecraft.getInstance().options.renderDistance().get();
|
||||
ChunkPos chunkPos = new ChunkPos(x, z);
|
||||
boolean isInPlayerRange = storage.inRange(x, z);
|
||||
boolean shouldAddChunk = CameraChunkVisibilityManager.shouldKeepChunk(chunkPos, renderDistance)
|
||||
|| CameraFeedHandler.shouldAddChunk(chunkPos, renderDistance);
|
||||
|
||||
if (shouldAddChunk) {
|
||||
LevelChunk newChunk = CameraClientChunkCacheExtension.replaceWithPacketData(level, x, z, new FriendlyByteBuf(buffer.copy()), chunkTag, tagOutputConsumer);
|
||||
if (!isInPlayerRange)
|
||||
cir.setReturnValue(newChunk);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "getChunk(IILnet/minecraft/world/level/chunk/status/ChunkStatus;Z)Lnet/minecraft/world/level/chunk/LevelChunk;", at = @At("TAIL"), cancellable = true)
|
||||
private void cobblemon$onGetChunk(int x, int z, ChunkStatus chunkStatus, boolean requireChunk, CallbackInfoReturnable<LevelChunk> cir) {
|
||||
if (!storage.inRange(x, z)) {
|
||||
LevelChunk chunk = CameraClientChunkCacheExtension.getChunk(x, z);
|
||||
|
||||
if (chunk != null)
|
||||
cir.setReturnValue(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonRecipeCategories;
|
||||
import com.cobblemon.mod.common.item.crafting.CookingPotBookCategory;
|
||||
import com.cobblemon.mod.common.item.crafting.CookingPotRecipe;
|
||||
import com.cobblemon.mod.common.item.crafting.CookingPotRecipeBase;
|
||||
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;
|
||||
|
||||
+102
-15
@@ -9,43 +9,122 @@
|
||||
package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker;
|
||||
import com.cobblemon.mod.common.api.tween.TweenManager;
|
||||
import com.cobblemon.mod.common.client.camera.GameCamera;
|
||||
import com.cobblemon.mod.common.client.camera.ClientCameraController;
|
||||
import com.cobblemon.mod.common.client.render.camera.CameraFeedHandler;
|
||||
import com.cobblemon.mod.common.client.render.camera.compositor.CompositorManager;
|
||||
import net.minecraft.client.Camera;
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.DeltaTracker;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.GameRenderer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
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.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Mixin(GameRenderer.class)
|
||||
public class GameRendererMixin {
|
||||
@Unique
|
||||
long lastTime = -1;
|
||||
public abstract class GameRendererMixin {
|
||||
|
||||
@Inject(
|
||||
method = "render",
|
||||
at = @At(value = "TAIL")
|
||||
)
|
||||
public void render(DeltaTracker counter, boolean tick, CallbackInfo ci) {
|
||||
@Shadow @Final @Mutable
|
||||
private Camera mainCamera;
|
||||
|
||||
@Unique
|
||||
private Camera cobblemon$playerCamera;
|
||||
|
||||
@Unique
|
||||
private final GameCamera cobblemon$gameCamera = new GameCamera();
|
||||
|
||||
@Unique
|
||||
private boolean cobblemon$usingCinematic;
|
||||
|
||||
@Unique
|
||||
private long cobblemon$lastTime = -1;
|
||||
|
||||
@Inject(method = "render", at = @At("HEAD"))
|
||||
private void cobblemon$swapCamera(DeltaTracker counter, boolean tick, CallbackInfo ci) {
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
long newTime = System.currentTimeMillis();
|
||||
// Don't play scheduled animations when the game is paused
|
||||
if (client.isPaused()) {
|
||||
lastTime = newTime;
|
||||
if (client.level == null) {
|
||||
cobblemon$restoreCamera();
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastTime != -1) {
|
||||
ClientTaskTracker.INSTANCE.update((newTime - lastTime) / 1000F);
|
||||
if (ClientCameraController.shouldUseCinematicCamera()) {
|
||||
if (!cobblemon$usingCinematic) {
|
||||
cobblemon$playerCamera = mainCamera;
|
||||
mainCamera = cobblemon$gameCamera;
|
||||
cobblemon$usingCinematic = true;
|
||||
}
|
||||
ClientCameraController.prepareCinematicCamera(client, counter.getGameTimeDeltaPartialTick(tick));
|
||||
} else if (cobblemon$usingCinematic) {
|
||||
cobblemon$restoreCamera();
|
||||
}
|
||||
lastTime = newTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip the vanilla renderLevel() call when a fullscreen compositor is active since
|
||||
* the compositor covers the entire screen with camera feeds.
|
||||
* This avoids a full wasted scene render every frame.
|
||||
*/
|
||||
@Redirect(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;renderLevel(Lnet/minecraft/client/DeltaTracker;)V"))
|
||||
private void cobblemon$skipVanillaRenderLevel(GameRenderer instance, DeltaTracker deltaTracker) {
|
||||
if (!CompositorManager.isFullscreenActive()) {
|
||||
instance.renderLevel(deltaTracker);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;tryTakeScreenshotIfNeeded()V"))
|
||||
private void cobblemon$afterLevelRendering(DeltaTracker deltaTracker, boolean renderLevel, CallbackInfo ci) {
|
||||
// Only capture feeds when a compositor is actively consuming them.
|
||||
// This avoids expensive full-resolution renders (especially with Iris)
|
||||
// when feeds aren't being displayed.
|
||||
boolean compositorNeedsFeeds = CompositorManager.isFullscreenActive()
|
||||
|| !CompositorManager.INSTANCE.getRequiredFeeds().isEmpty();
|
||||
if (compositorNeedsFeeds && CameraFeedHandler.hasFeeds()) {
|
||||
CameraFeedHandler.captureFeeds(deltaTracker);
|
||||
}
|
||||
if (CompositorManager.isFullscreenActive()) {
|
||||
CompositorManager.INSTANCE.compositeAll();
|
||||
CompositorManager.renderFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "pick", at = @At("HEAD"), cancellable = true)
|
||||
private void cobblemon$preventFramePick(float partialTicks, CallbackInfo ci) {
|
||||
if (CameraFeedHandler.isCapturing())
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At(value = "TAIL"))
|
||||
private void cobblemon$afterRender(DeltaTracker counter, boolean tick, CallbackInfo ci) {
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
|
||||
long newTime = System.currentTimeMillis();
|
||||
|
||||
if (client.isPaused()) {
|
||||
cobblemon$lastTime = newTime;
|
||||
return;
|
||||
}
|
||||
|
||||
if (cobblemon$lastTime != -1) {
|
||||
float delta = (newTime - cobblemon$lastTime) / 1000F;
|
||||
ClientTaskTracker.INSTANCE.update(delta);
|
||||
TweenManager.Companion.getClient().update(delta);
|
||||
}
|
||||
cobblemon$lastTime = newTime;
|
||||
}
|
||||
|
||||
@ModifyArg(
|
||||
@@ -60,4 +139,12 @@ public class GameRendererMixin {
|
||||
return (entityX) -> !(entity.getVehicle() == entityX && entityX instanceof PokemonEntity) && original.test(entityX);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void cobblemon$restoreCamera() {
|
||||
if (cobblemon$usingCinematic) {
|
||||
mainCamera = cobblemon$playerCamera;
|
||||
cobblemon$playerCamera = null;
|
||||
cobblemon$usingCinematic = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.OrientationControllable;
|
||||
import com.cobblemon.mod.common.client.camera.ClientCameraController;
|
||||
import com.cobblemon.mod.common.item.PokedexItem;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -28,6 +29,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
public class ItemInHandRendererMixin {
|
||||
@Inject(method = "renderArmWithItem", at = @At(value = "HEAD"), cancellable = true)
|
||||
private void cobblemon$renderArmWithItem(AbstractClientPlayer abstractClientPlayer, float f, float g, InteractionHand interactionHand, float h, ItemStack itemStack, float i, PoseStack poseStack, MultiBufferSource multiBufferSource, int j, CallbackInfo ci) {
|
||||
// Hide arms when cinematic camera is active
|
||||
if (ClientCameraController.isActive()) {
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (abstractClientPlayer.isUsingItem() && abstractClientPlayer.getUseItem().getItem() instanceof PokedexItem) {
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@@ -9,13 +9,10 @@
|
||||
package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon;
|
||||
import com.cobblemon.mod.common.CobblemonItemComponents;
|
||||
import com.cobblemon.mod.common.CobblemonItems;
|
||||
import com.cobblemon.mod.common.ModAPI;
|
||||
import com.cobblemon.mod.common.item.PokeBallItem;
|
||||
import com.cobblemon.mod.common.item.PokedexItem;
|
||||
import com.cobblemon.mod.common.item.WearableItem;
|
||||
import com.cobblemon.mod.common.item.components.TMMoveComponent;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.ItemModelShaper;
|
||||
@@ -57,17 +54,11 @@ public abstract class ItemRendererMixin {
|
||||
cancellable = true
|
||||
)
|
||||
private void cobblemon$overrideItemModel(ItemStack stack, ItemDisplayContext renderMode, boolean leftHanded, PoseStack matrices, MultiBufferSource multiBufferSource, int light, int overlay, BakedModel model, CallbackInfo ci) {
|
||||
boolean shouldBe2d = renderMode == ItemDisplayContext.GUI || renderMode == ItemDisplayContext.FIXED || stack.getItem() == CobblemonItems.TECHNICAL_MACHINE;
|
||||
boolean shouldBe2d = renderMode == ItemDisplayContext.GUI || renderMode == ItemDisplayContext.FIXED;
|
||||
ResourceLocation resourceLocation = null;
|
||||
if (shouldBe2d) {
|
||||
if (stack.getItem() instanceof PokeBallItem pokeBallItem) resourceLocation = pokeBallItem.getPokeBall().getModel2d();
|
||||
else if (stack.getItem() instanceof PokedexItem pokedexItem) resourceLocation = pokedexItem.getType().getItemSpritePath();
|
||||
else if (stack.getItem() == CobblemonItems.TECHNICAL_MACHINE) {
|
||||
TMMoveComponent component = stack.get(CobblemonItemComponents.TM_MOVE);
|
||||
if (component != null) {
|
||||
resourceLocation = new ResourceLocation(Cobblemon.MODID, "tm/" + component.getMove().getElementalType().showdownId() + "_tm");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (renderMode != ItemDisplayContext.HEAD && stack.getItem() instanceof WearableItem wearableItem) resourceLocation = wearableItem.getModel2d();
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import com.cobblemon.mod.common.duck.RidePassenger;
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
|
||||
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket;
|
||||
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket;
|
||||
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdatePassengerPositionPacket;
|
||||
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateRiderRotationPacket;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import net.minecraft.client.CameraType;
|
||||
@@ -149,22 +148,10 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
|
||||
cobblemon$lastRideYRot = rideYRot;
|
||||
cobblemon$lastRideEyePos = rideEyePos;
|
||||
|
||||
CobblemonNetwork.INSTANCE.sendToServer(
|
||||
new ServerboundUpdateRiderRotationPacket(rideXRot, rideYRot, rideEyePos)
|
||||
);
|
||||
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateRiderRotationPacket(rideXRot, rideYRot, rideEyePos));
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "rideTick", at = @At("TAIL"))
|
||||
private void cobblemon$updatePassengerPosition(CallbackInfo ci) {
|
||||
if (Minecraft.getInstance().player != (Object)this) return;
|
||||
if (!(this.getVehicle() instanceof PokemonEntity pokemonEntity)) return;
|
||||
if (pokemonEntity.getSeatForPassenger(this) == null) return;
|
||||
|
||||
var offset = this.position().subtract(pokemonEntity.position());
|
||||
CobblemonNetwork.INSTANCE.sendToServer(
|
||||
new ServerboundUpdatePassengerPositionPacket(this.getId(), offset)
|
||||
);
|
||||
}
|
||||
|
||||
@Unique
|
||||
@@ -230,10 +217,9 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
|
||||
private void cobblemon$isHandsBusy(CallbackInfoReturnable<Boolean> cir) {
|
||||
Entity vehicle = this.getVehicle();
|
||||
if (vehicle instanceof PokemonEntity pokemonEntity) {
|
||||
Seat seat = pokemonEntity.getSeatForPassenger(this);;
|
||||
if (seat != null) {
|
||||
cir.setReturnValue(seat.getHandsBusy());
|
||||
}
|
||||
int seatIndex = pokemonEntity.getPassengers().indexOf(this);
|
||||
Seat seat = pokemonEntity.getSeats().get(seatIndex);
|
||||
cir.setReturnValue(seat.getHandsBusy());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.client.CobblemonBakingOverrides;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.minecraft.client.color.block.BlockColors;
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.BlockStateModelLoader;
|
||||
@@ -17,76 +18,29 @@ import net.minecraft.client.resources.model.ModelBakery;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(ModelBakery.class)
|
||||
public abstract class ModelLoaderMixin {
|
||||
|
||||
@Shadow abstract UnbakedModel getModel(ResourceLocation resourceLocation);
|
||||
|
||||
@Shadow protected abstract void registerModel(ModelResourceLocation modelId, UnbakedModel unbakedModel);
|
||||
|
||||
@Shadow protected abstract void loadItemModelAndDependencies(ResourceLocation resourceLocation);
|
||||
|
||||
@Unique
|
||||
List<String> dynamicFolders = Arrays.asList("egg", "tm");
|
||||
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/util/profiling/ProfilerFiller;popPush(Ljava/lang/String;)V",
|
||||
ordinal = 1
|
||||
)
|
||||
)
|
||||
public void loadCustomItemModels(BlockColors blockColors,
|
||||
ProfilerFiller profiler,
|
||||
Map<ResourceLocation, BlockModel> jsonUnbakedModels,
|
||||
Map<ResourceLocation, List<BlockStateModelLoader.LoadedJson>> blockStates,
|
||||
CallbackInfo ci
|
||||
) {
|
||||
/*
|
||||
* The models/item/*.json's only get loaded if there's an item in the registry that actually asks for it. It
|
||||
* naively looks for things in the root with an item ID and calls upon it, and some of those JSONs reference
|
||||
* other model JSONs and so it cascades a little bit. Used for block models but more importantly here, sprites.
|
||||
*
|
||||
* The thing about the eggs (and TMs) is, we really want the eggs to be addon-compatible in a way that doesn't restrict
|
||||
* the species that can have eggs, so we can't pre-plan all of them. This mixin looks for the things inside the
|
||||
* egg subfolder (which won't get loaded automagically) and manually inserts them into the unbaked model list so
|
||||
* Minecraft loads them for us. We'll then dynamically call these models from the ItemRendererMixin; so it's
|
||||
* hacky at the init stage and hacky at the render stage. Pretty cool in the gameplay stage though.
|
||||
*
|
||||
* - Hiro
|
||||
*/
|
||||
|
||||
dynamicFolders.forEach(folder -> {
|
||||
Map<ResourceLocation, Resource> map = Minecraft.getInstance().getResourceManager().listResources("models/item/" + folder, (t) -> true);
|
||||
map.forEach((key, value) -> {
|
||||
ResourceLocation resourceLocation = new ResourceLocation(key.getNamespace(), key.getPath().replace("models/item/", "").replace(".json", ""));
|
||||
this.loadItemModelAndDependencies(resourceLocation);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At("TAIL")
|
||||
method = "<init>",
|
||||
at = @At("TAIL")
|
||||
)
|
||||
public void init(BlockColors blockColors,
|
||||
ProfilerFiller profiler,
|
||||
Map<ResourceLocation, BlockModel> jsonUnbakedModels,
|
||||
Map<ResourceLocation, List<BlockStateModelLoader.LoadedJson>> blockStates,
|
||||
CallbackInfo ci) {
|
||||
ProfilerFiller profiler,
|
||||
Map<ResourceLocation, BlockModel> jsonUnbakedModels,
|
||||
Map<ResourceLocation, List<BlockStateModelLoader.LoadedJson>> blockStates,
|
||||
CallbackInfo ci) {
|
||||
CobblemonBakingOverrides.INSTANCE.getModels().forEach(bakingOverride -> {
|
||||
var unbakedModel = this.getModel(bakingOverride.getModelLocation());
|
||||
this.registerModel(bakingOverride.getModelIdentifier(), unbakedModel);
|
||||
|
||||
@@ -8,11 +8,14 @@
|
||||
|
||||
package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.client.camera.ClientCameraController;
|
||||
import com.cobblemon.mod.common.client.render.ClientPlayerIcon;
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.CameraType;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.model.PlayerModel;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
@@ -22,6 +25,7 @@ import net.minecraft.client.renderer.entity.player.PlayerRenderer;
|
||||
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.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(PlayerRenderer.class)
|
||||
@@ -31,6 +35,27 @@ public abstract class PlayerRendererMixin extends LivingEntityRenderer<AbstractC
|
||||
super(context, model, shadowRadius);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the camera type check to force player body rendering when cinematic camera is active.
|
||||
* This makes Minecraft think we're not in first person mode (for rendering purposes only),
|
||||
* so the local player's body is rendered even though the camera entity is still the player.
|
||||
*/
|
||||
@Redirect(
|
||||
method = "render(Lnet/minecraft/client/player/AbstractClientPlayer;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/CameraType;isFirstPerson()Z"
|
||||
)
|
||||
)
|
||||
private boolean cobblemon$allowBodyRenderInCinematic(CameraType instance) {
|
||||
// When cinematic camera is active and configured to show player body,
|
||||
// pretend we're not in first person so the player body renders
|
||||
if (ClientCameraController.isActive() && ClientCameraController.shouldRenderPlayerBody()) {
|
||||
return false;
|
||||
}
|
||||
return instance.isFirstPerson();
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At(value = "TAIL"))
|
||||
public void renderPlayer(AbstractClientPlayer player, float f, float g, PoseStack poseStack, MultiBufferSource multiBufferSource, int i, CallbackInfo ci) {
|
||||
ClientPlayerIcon.Companion.onRenderPlayer(player);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin.client;
|
||||
|
||||
import com.cobblemon.mod.common.client.render.camera.CameraViewAreaExtension;
|
||||
import net.minecraft.client.renderer.ViewArea;
|
||||
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(ViewArea.class)
|
||||
public class ViewAreaMixin {
|
||||
@Inject(method = "setDirty", at = @At("HEAD"))
|
||||
private void cobblemon$markCinematicDirty(int cx, int cy, int cz, boolean reRenderOnMainThread, CallbackInfo ci) {
|
||||
CameraViewAreaExtension.setDirty(cx, cy, cz, reRenderOnMainThread);
|
||||
}
|
||||
}
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.invoker;
|
||||
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(AbstractContainerScreen.class)
|
||||
public interface AbstractContainerScreenInvoker {
|
||||
@Invoker("findSlot")
|
||||
@Nullable Slot callFindSlot(double mouseX, double mouseY);
|
||||
}
|
||||
@@ -18,7 +18,6 @@ import com.cobblemon.mod.common.api.drop.EvolutionItemDropEntry
|
||||
import com.cobblemon.mod.common.api.drop.ItemDropEntry
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents.DATA_SYNCHRONIZED
|
||||
import com.cobblemon.mod.common.api.habitats.HabitatPools
|
||||
import com.cobblemon.mod.common.api.interaction.RequestManager
|
||||
import com.cobblemon.mod.common.api.molang.MoLangLoadedFilesCache
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
@@ -34,7 +33,6 @@ import com.cobblemon.mod.common.api.pokemon.feature.ChoiceSpeciesFeatureProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.FlagSpeciesFeatureProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeatureProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
|
||||
import com.cobblemon.mod.common.api.pokemon.feature.WeightedChoiceSpeciesFeatureProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.EvCalculator
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Generation8EvCalculator
|
||||
@@ -64,8 +62,6 @@ import com.cobblemon.mod.common.api.storage.player.adapter.DexDataMongoBackend
|
||||
import com.cobblemon.mod.common.api.storage.player.adapter.DexDataNbtBackend
|
||||
import com.cobblemon.mod.common.api.storage.player.adapter.PlayerDataJsonBackend
|
||||
import com.cobblemon.mod.common.api.storage.player.adapter.PlayerDataMongoBackend
|
||||
import com.cobblemon.mod.common.api.storage.player.adapter.TMMoveMongoBackend
|
||||
import com.cobblemon.mod.common.api.storage.player.adapter.TMMoveNbtBackend
|
||||
import com.cobblemon.mod.common.api.storage.player.factory.CachedPlayerDataStoreFactory
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonEntityTypeTags
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
|
||||
@@ -76,8 +72,11 @@ import com.cobblemon.mod.common.battles.BattleSide
|
||||
import com.cobblemon.mod.common.battles.ShowdownThread
|
||||
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.api.camera.track.CameraTrackDeserializers
|
||||
import com.cobblemon.mod.common.block.dispenser.DispenserBehaviorRegistry
|
||||
import com.cobblemon.mod.common.client.CobblemonPack
|
||||
import com.cobblemon.mod.common.api.tween.EasingRegistry
|
||||
import com.cobblemon.mod.common.api.tween.TweenManager
|
||||
import com.cobblemon.mod.common.command.argument.*
|
||||
import com.cobblemon.mod.common.config.CobblemonConfig
|
||||
import com.cobblemon.mod.common.config.LastChangedVersion
|
||||
@@ -94,13 +93,11 @@ import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacke
|
||||
import com.cobblemon.mod.common.permission.LaxPermissionValidator
|
||||
import com.cobblemon.mod.common.platform.events.PlatformEvents
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.pokemon.aspects.ALPHA_ASPECT
|
||||
import com.cobblemon.mod.common.pokemon.aspects.CHARACTERISTIC_RAINBOW_ASPECT
|
||||
import com.cobblemon.mod.common.pokemon.aspects.COSMETIC_SLOT_ASPECT
|
||||
import com.cobblemon.mod.common.pokemon.aspects.FORM_ASPECTS
|
||||
import com.cobblemon.mod.common.pokemon.aspects.GENDER_ASPECT
|
||||
import com.cobblemon.mod.common.pokemon.aspects.MARK_ASPECT
|
||||
import com.cobblemon.mod.common.pokemon.aspects.SHINY_ASPECT
|
||||
import com.cobblemon.mod.common.pokemon.evolution.variants.BlockClickEvolution
|
||||
import com.cobblemon.mod.common.pokemon.feature.SlowpokeTailRegrowthSpeciesFeature
|
||||
import com.cobblemon.mod.common.pokemon.feature.SlowpokeTailRegrowthSpeciesFeatureProvider
|
||||
import com.cobblemon.mod.common.pokemon.feature.TagSeasonResolver
|
||||
@@ -114,9 +111,14 @@ import com.cobblemon.mod.common.pokemon.properties.NoAIProperty
|
||||
import com.cobblemon.mod.common.pokemon.properties.UnaspectPropertyType
|
||||
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
|
||||
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
|
||||
import com.cobblemon.mod.common.pokemon.transformation.triggers.BlockClickTrigger
|
||||
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import com.cobblemon.mod.common.util.DataKeys
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.ifDedicatedServer
|
||||
import com.cobblemon.mod.common.util.isLaterVersion
|
||||
import com.cobblemon.mod.common.util.party
|
||||
import com.cobblemon.mod.common.util.requestWallpapers
|
||||
import com.cobblemon.mod.common.util.server
|
||||
import com.cobblemon.mod.common.world.feature.CobblemonPlacedFeatures
|
||||
import com.cobblemon.mod.common.world.feature.ore.CobblemonOrePlacedFeatures
|
||||
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
|
||||
@@ -245,8 +247,6 @@ object Cobblemon {
|
||||
|
||||
DATA_SYNCHRONIZED.subscribe {
|
||||
storage.onPlayerDataSync(it)
|
||||
it.tmList().scheduleFullSyncFromStores(it.party(), it.pc())
|
||||
it.pokedex().scheduleFullSyncFromStores(it.party(), it.pc())
|
||||
playerDataManager.syncAllToPlayer(it)
|
||||
starterHandler.handleJoin(it)
|
||||
it.requestWallpapers()
|
||||
@@ -259,6 +259,7 @@ object Cobblemon {
|
||||
playerDataManager.onPlayerDisconnect(it.player)
|
||||
RequestManager.onLogoff(it.player)
|
||||
serverPlayerStructs.remove(it.player.uuid)
|
||||
com.cobblemon.mod.common.api.molangui.MolangUiSessionManager.closeAllForPlayer(it.player.uuid)
|
||||
}
|
||||
PlatformEvents.PLAYER_DEATH.subscribe {
|
||||
PCLinkManager.removeLink(it.player.uuid)
|
||||
@@ -273,8 +274,13 @@ object Cobblemon {
|
||||
PlatformEvents.RIGHT_CLICK_BLOCK.subscribe { event ->
|
||||
val player = event.player
|
||||
val block = player.level().getBlockState(event.pos).block
|
||||
val context = BlockClickTrigger.Context(block, player.level())
|
||||
player.party().forEach { pokemon -> pokemon.triggerTransformations<BlockClickTrigger>(context) }
|
||||
player.party().forEach { pokemon ->
|
||||
pokemon.lockedEvolutions
|
||||
.filterIsInstance<BlockClickEvolution>()
|
||||
.forEach { evolution ->
|
||||
evolution.attemptEvolution(pokemon, BlockClickEvolution.BlockInteractionContext(block, player.level()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PlatformEvents.CHANGE_DIMENSION.subscribe {
|
||||
@@ -296,6 +302,7 @@ object Cobblemon {
|
||||
HeldItemProvider.register(CobblemonHeldItemManager, Priority.LOWEST)
|
||||
}
|
||||
|
||||
|
||||
fun sendServerSettingsPacketToPlayer(player: ServerPlayer) {
|
||||
ServerSettingsPacket(
|
||||
this.config.preventCompletePartyDeposit,
|
||||
@@ -304,7 +311,6 @@ object Cobblemon {
|
||||
this.config.maxPokemonLevel,
|
||||
this.config.maxPokemonFriendship,
|
||||
this.config.maxDynamaxLevel,
|
||||
this.config.unlockAllMoveDexMovesByDefault
|
||||
).sendToPlayer(player)
|
||||
}
|
||||
|
||||
@@ -316,14 +322,10 @@ object Cobblemon {
|
||||
|
||||
SHINY_ASPECT.register()
|
||||
GENDER_ASPECT.register()
|
||||
FORM_ASPECTS.register()
|
||||
COSMETIC_SLOT_ASPECT.register()
|
||||
CHARACTERISTIC_RAINBOW_ASPECT.register()
|
||||
ALPHA_ASPECT.register()
|
||||
MARK_ASPECT.register()
|
||||
|
||||
SpeciesFeatures.types["choice"] = ChoiceSpeciesFeatureProvider::class.java
|
||||
SpeciesFeatures.types["weighted_choice"] = WeightedChoiceSpeciesFeatureProvider::class.java
|
||||
SpeciesFeatures.types["flag"] = FlagSpeciesFeatureProvider::class.java
|
||||
SpeciesFeatures.types["integer"] = IntSpeciesFeatureProvider::class.java
|
||||
|
||||
@@ -346,6 +348,10 @@ object Cobblemon {
|
||||
CustomPokemonProperty.register(FreezeFrameProperty)
|
||||
CustomPokemonProperty.register(NoAIProperty)
|
||||
|
||||
// Tween & camera system initialization
|
||||
EasingRegistry.registerBuiltinEasings()
|
||||
CameraTrackDeserializers.registerBuiltinTracks()
|
||||
|
||||
CobblemonEvents.POKEMON_PROPERTY_INITIALISED.emit(Unit)
|
||||
|
||||
CallbackHandler.setup()
|
||||
@@ -358,6 +364,7 @@ object Cobblemon {
|
||||
PlatformEvents.SERVER_TICK_POST.subscribe {
|
||||
ServerTaskTracker.update(1/20F)
|
||||
ServerRealTimeTaskTracker.update()
|
||||
TweenManager.server.update(1/20F)
|
||||
}
|
||||
PlatformEvents.SERVER_TICK_PRE.subscribe {
|
||||
ServerRealTimeTaskTracker.update()
|
||||
@@ -366,7 +373,6 @@ object Cobblemon {
|
||||
val server = event.server
|
||||
MoLangLoadedFilesCache.initialize(server)
|
||||
playerDataManager = PlayerInstancedDataStoreManager().also { it.setup(server) }
|
||||
HabitatPools.onServerLoading(server)
|
||||
|
||||
val mongoClient: MongoClient?
|
||||
|
||||
@@ -379,12 +385,8 @@ object Cobblemon {
|
||||
val pokedexNbtFactory = CachedPlayerDataStoreFactory(DexDataNbtBackend())
|
||||
pokedexNbtFactory.setup(server)
|
||||
|
||||
val tmMoveFactory = CachedPlayerDataStoreFactory(TMMoveNbtBackend())
|
||||
tmMoveFactory.setup(server)
|
||||
|
||||
playerDataManager.setFactory(generalJsonFactory, PlayerInstancedDataStoreTypes.GENERAL)
|
||||
playerDataManager.setFactory(pokedexNbtFactory, PlayerInstancedDataStoreTypes.POKEDEX)
|
||||
playerDataManager.setFactory(tmMoveFactory, PlayerInstancedDataStoreTypes.TM_MOVES)
|
||||
|
||||
if (config.storageFormat == "nbt") {
|
||||
NBTStoreAdapter(pokemonStoreRoot.absolutePath, useNestedFolders = true, folderPerClass = true)
|
||||
@@ -411,14 +413,8 @@ object Cobblemon {
|
||||
val pokedexMongoFactory = CachedPlayerDataStoreFactory(DexDataMongoBackend(mongoClient, config.mongoDBDatabaseName, "PokeDexCollection"))
|
||||
pokedexMongoFactory.setup(server)
|
||||
|
||||
val tmMovesMongoFactory = CachedPlayerDataStoreFactory(
|
||||
TMMoveMongoBackend(mongoClient, config.mongoDBDatabaseName, "TMMovesCollection")
|
||||
)
|
||||
tmMovesMongoFactory.setup(server)
|
||||
|
||||
playerDataManager.setFactory(generalMongoFactory, PlayerInstancedDataStoreTypes.GENERAL)
|
||||
playerDataManager.setFactory(pokedexMongoFactory, PlayerInstancedDataStoreTypes.POKEDEX)
|
||||
playerDataManager.setFactory(tmMovesMongoFactory, PlayerInstancedDataStoreTypes.TM_MOVES)
|
||||
MongoDBStoreAdapter(mongoClient, config.mongoDBDatabaseName)
|
||||
} catch (e: ClassNotFoundException) {
|
||||
LOGGER.error("MongoDB driver not found.")
|
||||
@@ -602,7 +598,6 @@ object Cobblemon {
|
||||
this.implementation.registerCommandArgument(cobblemonResource("mark"), MarkArgumentType::class, SingletonArgumentInfo.contextFree(MarkArgumentType::mark))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("transform_type"), TransformTypeArgumentType::class, SingletonArgumentInfo.contextFree(TransformTypeArgumentType::transformType))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("model_part"), ModelPartArgumentType::class, SingletonArgumentInfo.contextFree(ModelPartArgumentType::modelPart))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("tm"), TmArgumentType::class, SingletonArgumentInfo.contextFree(TmArgumentType::tm))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.CobblemonBehaviours.autoPokemonBehaviours
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.ai.CobblemonBehaviour
|
||||
import com.cobblemon.mod.common.api.ai.config.BehaviourConfig
|
||||
@@ -20,7 +19,16 @@ import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.net.messages.client.data.BehaviourSyncPacket
|
||||
import com.cobblemon.mod.common.util.adapters.*
|
||||
import com.cobblemon.mod.common.util.adapters.ActivityAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.BehaviourConfigAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.ExpressionAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.ExpressionOrEntityVariableAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.MemoryModuleTypeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.SensorTypeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.TaskConfigAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.TranslatedTextAdapter
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.endsWith
|
||||
import com.google.gson.Gson
|
||||
|
||||
@@ -8,15 +8,22 @@
|
||||
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.block.entity.*
|
||||
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
|
||||
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
|
||||
import com.cobblemon.mod.common.block.entity.HealingMachineBlockEntity
|
||||
import com.cobblemon.mod.common.block.entity.PCBlockEntity
|
||||
import com.cobblemon.mod.common.block.entity.PokemonPastureBlockEntity
|
||||
import com.cobblemon.mod.common.block.entity.FossilAnalyzerBlockEntity
|
||||
import com.cobblemon.mod.common.block.entity.FossilMultiblockEntity
|
||||
import com.cobblemon.mod.common.block.entity.RestorationTankBlockEntity
|
||||
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockBuilder
|
||||
import com.cobblemon.mod.common.block.entity.*
|
||||
import com.cobblemon.mod.common.block.entity.DisplayCaseBlockEntity
|
||||
import com.cobblemon.mod.common.platform.PlatformRegistry
|
||||
import net.minecraft.core.Registry
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.core.registries.Registries
|
||||
import net.minecraft.resources.ResourceKey
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType
|
||||
import net.minecraft.resources.ResourceKey
|
||||
|
||||
object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, ResourceKey<Registry<BlockEntityType<*>>>, BlockEntityType<*>>() {
|
||||
|
||||
@@ -32,9 +39,6 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
|
||||
@JvmField
|
||||
val LECTERN: BlockEntityType<LecternBlockEntity> = this.create("lectern", BlockEntityType.Builder.of(::LecternBlockEntity, CobblemonBlocks.LECTERN).build(null))
|
||||
|
||||
@JvmField
|
||||
val DISC_SHELF: BlockEntityType<DiscShelfBlockEntity> = this.create("disc_shelf", BlockEntityType.Builder.of(::DiscShelfBlockEntity, CobblemonBlocks.DISC_SHELF).build(null))
|
||||
|
||||
@JvmField
|
||||
val BERRY = this.create("berry", BlockEntityType.Builder.of(::BerryBlockEntity, *CobblemonBlocks.berries().values.toTypedArray()).build(null))
|
||||
|
||||
@@ -44,13 +48,6 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
|
||||
val SIGN: BlockEntityType<CobblemonSignBlockEntity> = this.create("sign", BlockEntityType.Builder.of(::CobblemonSignBlockEntity, CobblemonBlocks.APRICORN_SIGN, CobblemonBlocks.APRICORN_WALL_SIGN, CobblemonBlocks.SACCHARINE_SIGN, CobblemonBlocks.SACCHARINE_WALL_SIGN).build(null))
|
||||
@JvmField
|
||||
val HANGING_SIGN: BlockEntityType<CobblemonHangingSignBlockEntity> = this.create("hanging_sign", BlockEntityType.Builder.of(::CobblemonHangingSignBlockEntity, CobblemonBlocks.APRICORN_HANGING_SIGN, CobblemonBlocks.APRICORN_WALL_HANGING_SIGN, CobblemonBlocks.SACCHARINE_HANGING_SIGN, CobblemonBlocks.SACCHARINE_WALL_HANGING_SIGN).build(null))
|
||||
@JvmField
|
||||
val TM_MACHINE: BlockEntityType<TMMachineBlockEntity> = this.create("tm_machine", BlockEntityType.Builder.of(::TMMachineBlockEntity, CobblemonBlocks.TM_MACHINE).build(null))
|
||||
|
||||
@JvmField
|
||||
val HABITAT_BLOCK: BlockEntityType<HabitatBlockEntity> = this.create("habitat_block",
|
||||
BlockEntityType.Builder.of(::HabitatBlockEntity, CobblemonBlocks.HABITAT_BLOCK).build(null)
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val GILDED_CHEST: BlockEntityType<GildedChestBlockEntity> = this.create("chest", BlockEntityType.Builder.of(::GildedChestBlockEntity,
|
||||
@@ -109,4 +106,10 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
|
||||
"poke_snack",
|
||||
BlockEntityType.Builder.of(::PokeSnackBlockEntity, CobblemonBlocks.POKE_SNACK, CobblemonBlocks.POKE_CAKE).build(null)
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val STADIUM_BLOCK: BlockEntityType<StadiumBlockEntity> = this.create(
|
||||
"stadium_block",
|
||||
BlockEntityType.Builder.of(::StadiumBlockEntity, CobblemonBlocks.STADIUM_BLOCK).build(null)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,27 +11,20 @@ package com.cobblemon.mod.common
|
||||
import com.cobblemon.mod.common.api.apricorn.Apricorn
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.block.*
|
||||
import com.cobblemon.mod.common.block.campfirepot.CampfireBlock
|
||||
import com.cobblemon.mod.common.block.LecternBlock
|
||||
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.general.BaleBlock
|
||||
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
|
||||
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
|
||||
import com.cobblemon.mod.common.block.grower.SaccharineTreeGrower
|
||||
import com.cobblemon.mod.common.block.habitat.HabitatBlock
|
||||
import com.cobblemon.mod.common.block.sign.CobblemonHangingSignBlock
|
||||
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.mixin.invoker.BlocksInvoker
|
||||
import com.cobblemon.mod.common.mixin.invoker.DoorBlockInvoker
|
||||
import com.cobblemon.mod.common.mixin.invoker.FireBlockInvoker
|
||||
import com.cobblemon.mod.common.mixin.invoker.PressurePlateBlockInvoker
|
||||
import com.cobblemon.mod.common.mixin.invoker.StairsBlockInvoker
|
||||
import com.cobblemon.mod.common.mixin.invoker.TrapdoorBlockInvoker
|
||||
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
|
||||
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
|
||||
import com.cobblemon.mod.common.mixin.invoker.*
|
||||
import com.cobblemon.mod.common.platform.PlatformRegistry
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.core.BlockPos
|
||||
@@ -53,6 +46,7 @@ import net.minecraft.world.level.block.state.properties.NoteBlockInstrument
|
||||
import net.minecraft.world.level.block.state.properties.WoodType
|
||||
import net.minecraft.world.level.material.MapColor
|
||||
import net.minecraft.world.level.material.PushReaction
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
|
||||
@Suppress("SameParameterValue", "HasPlatformType", "MemberVisibilityCanBePrivate", "unused")
|
||||
object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<Block>>, Block>() {
|
||||
@@ -127,19 +121,19 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val APRICORN_PLANKS = this.create("apricorn_planks",
|
||||
Block(
|
||||
BlockBehaviour.Properties.of().mapColor(MapColor.DIRT).instrument(NoteBlockInstrument.BASS)
|
||||
.strength(2.0F, 3.0F).sound(SoundType.WOOD)
|
||||
.strength(2.0f, 3.0f).sound(SoundType.WOOD)
|
||||
)
|
||||
)
|
||||
@JvmField
|
||||
val APRICORN_LEAVES = leaves("apricorn_leaves")
|
||||
@JvmField
|
||||
val APRICORN_FENCE = this.create("apricorn_fence", FenceBlock(BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD)))
|
||||
val APRICORN_FENCE = this.create("apricorn_fence", FenceBlock(BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
|
||||
@JvmField
|
||||
val APRICORN_FENCE_GATE = this.create("apricorn_fence_gate", FenceGateBlock(APRICORN_WOOD_TYPE, BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).strength(2.0F, 3.0F).sound(SoundType.WOOD).ignitedByLava().forceSolidOn()))
|
||||
val APRICORN_FENCE_GATE = this.create("apricorn_fence_gate", FenceGateBlock(APRICORN_WOOD_TYPE, BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).strength(2.0f, 3.0f).sound(SoundType.WOOD).ignitedByLava().forceSolidOn()))
|
||||
@JvmField
|
||||
val APRICORN_BUTTON = this.create("apricorn_button", BlocksInvoker.createWoodenButtonBlock(APRICORN_BLOCK_SET_TYPE))
|
||||
@JvmField
|
||||
val APRICORN_PRESSURE_PLATE = this.create("apricorn_pressure_plate", PressurePlateBlockInvoker.`cobblemon$create`(APRICORN_BLOCK_SET_TYPE, BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).noCollission().strength(0.5F).sound(SoundType.WOOD)))
|
||||
val APRICORN_PRESSURE_PLATE = this.create("apricorn_pressure_plate", PressurePlateBlockInvoker.`cobblemon$create`(APRICORN_BLOCK_SET_TYPE, BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).noCollission().strength(0.5f).sound(SoundType.WOOD)))
|
||||
@JvmField
|
||||
val APRICORN_SIGN = this.create("apricorn_sign", CobblemonSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_SIGN), APRICORN_WOOD_TYPE))
|
||||
|
||||
@@ -156,7 +150,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val APRICORN_WALL_HANGING_SIGN = this.create("apricorn_wall_hanging_sign", CobblemonWallHangingSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_HANGING_SIGN), APRICORN_WOOD_TYPE))
|
||||
@JvmField
|
||||
val APRICORN_SLAB = this.create("apricorn_slab", SlabBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD)))
|
||||
val APRICORN_SLAB = this.create("apricorn_slab", SlabBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
|
||||
@JvmField
|
||||
val APRICORN_STAIRS = this.create("apricorn_stairs", StairsBlockInvoker.`cobblemon$create`(APRICORN_PLANKS.defaultBlockState(), BlockBehaviour.Properties.ofFullCopy(APRICORN_PLANKS)))
|
||||
@JvmField
|
||||
@@ -193,12 +187,12 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val STRIPPED_SACCHARINE_WOOD = this.create("stripped_saccharine_wood", RotatedPillarBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_LIGHT_BLUE).instrument(NoteBlockInstrument.BASS).strength(2.0F).sound(SoundType.WOOD).ignitedByLava())) //log("stripped_saccharine_log")
|
||||
@JvmField
|
||||
val SACCHARINE_PLANKS = this.create("saccharine_planks", Block(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_LIGHT_BLUE).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD)))
|
||||
val SACCHARINE_PLANKS = this.create("saccharine_planks", Block(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_LIGHT_BLUE).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
|
||||
@JvmField
|
||||
val SACCHARINE_LEAVES = this.create("saccharine_leaves", SaccharineLeafBlock(
|
||||
BlockBehaviour.Properties.of().mapColor(
|
||||
MapColor.PLANT
|
||||
).strength(0.2F).randomTicks().sound(SoundType.GRASS).noOcclusion()
|
||||
).strength(0.2f).randomTicks().sound(SoundType.GRASS).noOcclusion()
|
||||
.isValidSpawn { _: BlockState?, _: BlockGetter?, _: BlockPos?, entity: EntityType<*>? ->
|
||||
entity === EntityType.OCELOT || entity === EntityType.PARROT
|
||||
}.isSuffocating { _: BlockState?, _: BlockGetter?, _: BlockPos? ->
|
||||
@@ -210,13 +204,13 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
false
|
||||
}))
|
||||
@JvmField
|
||||
val SACCHARINE_FENCE = this.create("saccharine_fence", FenceBlock(BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD)))
|
||||
val SACCHARINE_FENCE = this.create("saccharine_fence", FenceBlock(BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
|
||||
@JvmField
|
||||
val SACCHARINE_FENCE_GATE = this.create("saccharine_fence_gate", FenceGateBlock(SACCHARINE_WOOD_TYPE, BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD).ignitedByLava().forceSolidOn()))
|
||||
val SACCHARINE_FENCE_GATE = this.create("saccharine_fence_gate", FenceGateBlock(SACCHARINE_WOOD_TYPE, BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD).ignitedByLava().forceSolidOn()))
|
||||
@JvmField
|
||||
val SACCHARINE_BUTTON = this.create("saccharine_button", BlocksInvoker.createWoodenButtonBlock(BlockSetType.OAK))
|
||||
@JvmField
|
||||
val SACCHARINE_PRESSURE_PLATE = this.create("saccharine_pressure_plate", PressurePlateBlockInvoker.`cobblemon$create`(SACCHARINE_BLOCK_SET_TYPE, BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).noCollission().strength(0.5F).sound(SoundType.WOOD)))
|
||||
val SACCHARINE_PRESSURE_PLATE = this.create("saccharine_pressure_plate", PressurePlateBlockInvoker.`cobblemon$create`(SACCHARINE_BLOCK_SET_TYPE, BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).noCollission().strength(0.5f).sound(SoundType.WOOD)))
|
||||
@JvmField
|
||||
val SACCHARINE_SIGN = this.create("saccharine_sign", CobblemonSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_SIGN), SACCHARINE_WOOD_TYPE))
|
||||
@JvmField
|
||||
@@ -226,7 +220,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val SACCHARINE_WALL_HANGING_SIGN = this.create("saccharine_wall_hanging_sign", CobblemonWallHangingSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_HANGING_SIGN), SACCHARINE_WOOD_TYPE))
|
||||
@JvmField
|
||||
val SACCHARINE_SLAB = this.create("saccharine_slab", SlabBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD)))
|
||||
val SACCHARINE_SLAB = this.create("saccharine_slab", SlabBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
|
||||
@JvmField
|
||||
val SACCHARINE_STAIRS = this.create("saccharine_stairs", StairsBlockInvoker.`cobblemon$create`(SACCHARINE_PLANKS.defaultBlockState(), BlockBehaviour.Properties.ofFullCopy(SACCHARINE_PLANKS)))
|
||||
@JvmField
|
||||
@@ -255,31 +249,31 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val POKE_SNACK = this.create("poke_snack", PokeSnackBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY).lightLevel { if (it.getValue(BlockStateProperties.LIT)) 3 else 0 }, true))
|
||||
|
||||
@JvmField
|
||||
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null, MapColor.TERRACOTTA_ORANGE)
|
||||
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
|
||||
@JvmField
|
||||
val LARGE_BUDDING_TUMBLESTONE = tumblestoneBlock("large_budding_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, TUMBLESTONE_CLUSTER, MapColor.TERRACOTTA_ORANGE)
|
||||
val LARGE_BUDDING_TUMBLESTONE = tumblestoneBlock("large_budding_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, TUMBLESTONE_CLUSTER)
|
||||
@JvmField
|
||||
val MEDIUM_BUDDING_TUMBLESTONE = tumblestoneBlock("medium_budding_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_TUMBLESTONE, MapColor.TERRACOTTA_ORANGE)
|
||||
val MEDIUM_BUDDING_TUMBLESTONE = tumblestoneBlock("medium_budding_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_TUMBLESTONE)
|
||||
@JvmField
|
||||
val SMALL_BUDDING_TUMBLESTONE = tumblestoneBlock("small_budding_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_TUMBLESTONE, MapColor.TERRACOTTA_ORANGE)
|
||||
val SMALL_BUDDING_TUMBLESTONE = tumblestoneBlock("small_budding_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_TUMBLESTONE)
|
||||
|
||||
@JvmField
|
||||
val SKY_TUMBLESTONE_CLUSTER = tumblestoneBlock("sky_tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null, MapColor.COLOR_LIGHT_BLUE)
|
||||
val SKY_TUMBLESTONE_CLUSTER = tumblestoneBlock("sky_tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
|
||||
@JvmField
|
||||
val LARGE_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("large_budding_sky_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, SKY_TUMBLESTONE_CLUSTER, MapColor.COLOR_LIGHT_BLUE)
|
||||
val LARGE_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("large_budding_sky_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, SKY_TUMBLESTONE_CLUSTER)
|
||||
@JvmField
|
||||
val MEDIUM_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("medium_budding_sky_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_SKY_TUMBLESTONE, MapColor.COLOR_LIGHT_BLUE)
|
||||
val MEDIUM_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("medium_budding_sky_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_SKY_TUMBLESTONE)
|
||||
@JvmField
|
||||
val SMALL_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("small_budding_sky_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_SKY_TUMBLESTONE, MapColor.COLOR_LIGHT_BLUE)
|
||||
val SMALL_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("small_budding_sky_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_SKY_TUMBLESTONE)
|
||||
|
||||
@JvmField
|
||||
val BLACK_TUMBLESTONE_CLUSTER = tumblestoneBlock("black_tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null, MapColor.TERRACOTTA_BLACK)
|
||||
val BLACK_TUMBLESTONE_CLUSTER = tumblestoneBlock("black_tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
|
||||
@JvmField
|
||||
val LARGE_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("large_budding_black_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, BLACK_TUMBLESTONE_CLUSTER, MapColor.TERRACOTTA_BLACK)
|
||||
val LARGE_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("large_budding_black_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, BLACK_TUMBLESTONE_CLUSTER)
|
||||
@JvmField
|
||||
val MEDIUM_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("medium_budding_black_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_BLACK_TUMBLESTONE, MapColor.TERRACOTTA_BLACK)
|
||||
val MEDIUM_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("medium_budding_black_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_BLACK_TUMBLESTONE)
|
||||
@JvmField
|
||||
val SMALL_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("small_budding_black_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_BLACK_TUMBLESTONE, MapColor.TERRACOTTA_BLACK)
|
||||
val SMALL_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("small_budding_black_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_BLACK_TUMBLESTONE)
|
||||
|
||||
@JvmField
|
||||
val TUMBLESTONE_BLOCK = this.create("tumblestone_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_ORANGE).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
|
||||
@@ -288,87 +282,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val BLACK_TUMBLESTONE_BLOCK = this.create("black_tumblestone_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
|
||||
|
||||
@JvmField
|
||||
val TYPE_GEM_CORE = this.create("deepslate_crystal_core", TypeGemCoreBlock(BlockBehaviour.Properties.of().strength(4.0F, 10.0F).requiresCorrectToolForDrops().mapColor(MapColor.DEEPSLATE)))
|
||||
|
||||
private val typeGemBlocks = mutableMapOf<ResourceLocation, Block>()
|
||||
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_NORMAL = typeGemBlock("normal_gem_block", MapColor.QUARTZ)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_FIRE = typeGemBlock("fire_gem_block", MapColor.FIRE)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_WATER = typeGemBlock("water_gem_block", MapColor.WATER)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_GRASS = typeGemBlock("grass_gem_block", MapColor.COLOR_LIGHT_GREEN)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_ELECTRIC = typeGemBlock("electric_gem_block", MapColor.GOLD)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_ICE = typeGemBlock("ice_gem_block", MapColor.ICE)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_FIGHTING = typeGemBlock("fighting_gem_block", MapColor.COLOR_RED)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_POISON = typeGemBlock("poison_gem_block", MapColor.COLOR_PURPLE)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_GROUND = typeGemBlock("ground_gem_block", MapColor.PODZOL)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_FLYING = typeGemBlock("flying_gem_block", MapColor.COLOR_LIGHT_BLUE)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_PSYCHIC = typeGemBlock("psychic_gem_block", MapColor.COLOR_MAGENTA)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_BUG = typeGemBlock("bug_gem_block", MapColor.TERRACOTTA_LIGHT_GREEN)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_ROCK = typeGemBlock("rock_gem_block", MapColor.TERRACOTTA_LIGHT_GRAY)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_GHOST = typeGemBlock("ghost_gem_block", MapColor.TERRACOTTA_MAGENTA)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_DRAGON = typeGemBlock("dragon_gem_block", MapColor.COLOR_BLUE)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_DARK = typeGemBlock("dark_gem_block", MapColor.TERRACOTTA_BLACK)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_STEEL = typeGemBlock("steel_gem_block", MapColor.METAL)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_FAIRY = typeGemBlock("fairy_gem_block", MapColor.COLOR_PINK)
|
||||
|
||||
private val typeGemClusters = mutableMapOf<ResourceLocation, TypeGemClusterBlock>()
|
||||
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_NORMAL = typeGemCluster("normal_gem_cluster", TYPE_GEM_BLOCK_NORMAL, cobblemonResource("normal_gem"), MapColor.QUARTZ)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_FIRE = typeGemCluster("fire_gem_cluster", TYPE_GEM_BLOCK_FIRE, cobblemonResource("fire_gem"), MapColor.FIRE)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_WATER = typeGemCluster("water_gem_cluster", TYPE_GEM_BLOCK_WATER, cobblemonResource("water_gem"), MapColor.WATER)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_GRASS = typeGemCluster("grass_gem_cluster", TYPE_GEM_BLOCK_GRASS, cobblemonResource("grass_gem"), MapColor.COLOR_LIGHT_GREEN)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_ELECTRIC = typeGemCluster("electric_gem_cluster", TYPE_GEM_BLOCK_ELECTRIC, cobblemonResource("electric_gem"), MapColor.GOLD)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_ICE = typeGemCluster("ice_gem_cluster", TYPE_GEM_BLOCK_ICE, cobblemonResource("ice_gem"), MapColor.ICE)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_FIGHTING = typeGemCluster("fighting_gem_cluster", TYPE_GEM_BLOCK_FIGHTING, cobblemonResource("fighting_gem"), MapColor.COLOR_RED)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_POISON = typeGemCluster("poison_gem_cluster", TYPE_GEM_BLOCK_POISON, cobblemonResource("poison_gem"), MapColor.COLOR_PURPLE)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_GROUND = typeGemCluster("ground_gem_cluster", TYPE_GEM_BLOCK_GROUND, cobblemonResource("ground_gem"), MapColor.PODZOL)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_FLYING = typeGemCluster("flying_gem_cluster", TYPE_GEM_BLOCK_FLYING, cobblemonResource("flying_gem"), MapColor.COLOR_LIGHT_BLUE)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_PSYCHIC = typeGemCluster("psychic_gem_cluster", TYPE_GEM_BLOCK_PSYCHIC, cobblemonResource("psychic_gem"), MapColor.COLOR_MAGENTA)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_BUG = typeGemCluster("bug_gem_cluster", TYPE_GEM_BLOCK_BUG, cobblemonResource("bug_gem"), MapColor.TERRACOTTA_LIGHT_GREEN)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_ROCK = typeGemCluster("rock_gem_cluster", TYPE_GEM_BLOCK_ROCK, cobblemonResource("rock_gem"), MapColor.TERRACOTTA_LIGHT_GRAY)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_GHOST = typeGemCluster("ghost_gem_cluster", TYPE_GEM_BLOCK_GHOST, cobblemonResource("ghost_gem"), MapColor.TERRACOTTA_MAGENTA)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_DRAGON = typeGemCluster("dragon_gem_cluster", TYPE_GEM_BLOCK_DRAGON, cobblemonResource("dragon_gem"), MapColor.COLOR_BLUE)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_DARK = typeGemCluster("dark_gem_cluster", TYPE_GEM_BLOCK_DARK, cobblemonResource("dark_gem"), MapColor.TERRACOTTA_BLACK)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_STEEL = typeGemCluster("steel_gem_cluster", TYPE_GEM_BLOCK_STEEL, cobblemonResource("steel_gem"), MapColor.METAL)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_FAIRY = typeGemCluster("fairy_gem_cluster", TYPE_GEM_BLOCK_FAIRY, cobblemonResource("fairy_gem"), MapColor.COLOR_PINK)
|
||||
|
||||
@JvmField
|
||||
val POLISHED_TUMBLESTONE = this.create("polished_tumblestone", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_ORANGE).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
|
||||
@JvmField
|
||||
@@ -537,18 +450,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
)
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val HABITAT_BLOCK = this.create(
|
||||
name = "habitat_block",
|
||||
entry = HabitatBlock(
|
||||
properties = BlockBehaviour.Properties.of()
|
||||
.mapColor(MapColor.STONE)
|
||||
.strength(1.5F, Blocks.OBSIDIAN.explosionResistance)
|
||||
.pushReaction(PushReaction.BLOCK)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@JvmField
|
||||
val CAMPFIRE = create("campfire", CampfireBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.PODZOL).strength(2.0F).lightLevel{14}, false))
|
||||
|
||||
@@ -558,7 +459,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val RELIC_COIN_POUCH = create("relic_coin_pouch", CoinPouchBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.RELIC_COIN_POUCH_SOUNDS).pushReaction(PushReaction.DESTROY).noOcclusion(), true))
|
||||
@JvmField
|
||||
val RELIC_COIN_SACK = create("relic_coin_sack", CoinPouchBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.RELIC_COIN_SACK_SOUNDS).pushReaction(PushReaction.DESTROY).strength(0.4F).mapColor(MapColor.GOLD), false))
|
||||
val RELIC_COIN_SACK = create("relic_coin_sack", CoinPouchBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.RELIC_COIN_SACK_SOUNDS).pushReaction(PushReaction.DESTROY).strength(0.4f), false))
|
||||
|
||||
@JvmField
|
||||
val GILDED_CHEST = create("gilded_chest", GildedChestBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.CHEST).noOcclusion().sound(CobblemonSounds.GILDED_CHEST_SOUNDS), GildedChestBlock.Type.RED))
|
||||
@@ -611,14 +512,14 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val PINK_PLAQUE = create("pink_plaque", PlaqueBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.METAL).noOcclusion().pushReaction(PushReaction.DESTROY).strength(5.0F)))
|
||||
|
||||
@JvmField
|
||||
val CLEANSE_TAG = create("cleanse_tag", WallAttachedStackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_PAPER_SMALL_SOUNDS).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().noCollission(), WallAttachedStackableItemBlock.WallStackableItemBlockType.TAG))
|
||||
val CLEANSE_TAG = create("cleanse_tag", WallAttachedStackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().noCollission(), WallAttachedStackableItemBlock.WallStackableItemBlockType.TAG))
|
||||
@JvmField
|
||||
val SPELL_TAG = create("spell_tag", WallAttachedStackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_PAPER_SMALL_SOUNDS).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().noCollission(), WallAttachedStackableItemBlock.WallStackableItemBlockType.TAG))
|
||||
val SPELL_TAG = create("spell_tag", WallAttachedStackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().noCollission(), WallAttachedStackableItemBlock.WallStackableItemBlockType.TAG))
|
||||
|
||||
@JvmField
|
||||
val BLUNDER_POLICY = create("blunder_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_PAPER_LARGE_SOUNDS).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
|
||||
val BLUNDER_POLICY = create("blunder_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
|
||||
@JvmField
|
||||
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))
|
||||
val WEAKNESS_POLICY = create("weakness_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
|
||||
|
||||
@JvmField
|
||||
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
|
||||
@@ -673,43 +574,28 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@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
|
||||
val DAMAGED_MONITOR = create("damaged_monitor", DamagedMonitorBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).lightLevel { when (it.getValue(DamagedMonitorBlock.SCREEN)) { DamagedMonitorBlock.BrokenMonitorScreen.GLITCHING -> 10; else -> 0 } }))
|
||||
@JvmField
|
||||
val FOSSIL_ANALYZER = create("fossil_analyzer", FossilAnalyzerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).noOcclusion()))
|
||||
@JvmField
|
||||
val RESTORATION_TANK = create("restoration_tank", RestorationTankBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.GLASS).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).noOcclusion()))
|
||||
|
||||
@JvmField
|
||||
val HEALING_MACHINE = create("healing_machine", HealingMachineBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F).noOcclusion().lightLevel { if (it.getValue(HealingMachineBlock.CHARGE_LEVEL) >= HealingMachineBlock.MAX_CHARGE_LEVEL) 12 else 5 }))
|
||||
val HEALING_MACHINE = create("healing_machine", HealingMachineBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2f).noOcclusion().lightLevel { if (it.getValue(HealingMachineBlock.CHARGE_LEVEL) >= HealingMachineBlock.MAX_CHARGE_LEVEL) 12 else 5 }))
|
||||
|
||||
@JvmField
|
||||
val STADIUM_BLOCK = create("stadium_block", StadiumBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).strength(3.5F, 6.0F).noOcclusion().pushReaction(PushReaction.BLOCK)))
|
||||
|
||||
@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 }))
|
||||
|
||||
val TM_MACHINE = create(
|
||||
"tm_machine",
|
||||
TMMachineBlock(
|
||||
BlockBehaviour.Properties.of()
|
||||
.mapColor(MapColor.COLOR_GREEN)
|
||||
.sound(SoundType.METAL)
|
||||
.pushReaction(PushReaction.BLOCK)
|
||||
.strength(2F)
|
||||
.noOcclusion()
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@JvmField
|
||||
val LECTERN = create("lectern", LecternBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).sound(SoundType.WOOD).strength(2.5F).ignitedByLava().lightLevel { if (it.getValue(LecternBlock.EMIT_LIGHT) as Boolean) 13 else 0 }))
|
||||
|
||||
@JvmField
|
||||
val DISC_SHELF = create("disc_shelf", DiscShelfBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).sound(SoundType.METAL).strength(2.5F)))
|
||||
|
||||
@JvmField
|
||||
val DISPLAY_CASE = create("display_case", DisplayCaseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3F).requiresCorrectToolForDrops()))
|
||||
|
||||
// TODO after 1.7
|
||||
// @JvmField
|
||||
// val INCENSE_SWEET = create("incense_sweet", SweetIncenseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3F)))
|
||||
// val INCENSE_SWEET = create("incense_sweet", SweetIncenseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3f)))
|
||||
|
||||
@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 }))
|
||||
@@ -777,9 +663,8 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
|
||||
private fun apricornBlock(name: String, apricorn: Apricorn): ApricornBlock = this.create(name, ApricornBlock(BlockBehaviour.Properties.of().mapColor(apricorn.mapColor()).randomTicks().strength(Blocks.OAK_LOG.defaultDestroyTime(), Blocks.OAK_LOG.explosionResistance).sound(SoundType.WOOD).noOcclusion(), apricorn))
|
||||
|
||||
private fun tumblestoneBlock(name: String, stage: Int, height: Int, xzOffset: Int, nextStage: Block?, mapColor: MapColor) : Block {
|
||||
private fun tumblestoneBlock(name: String, stage: Int, height: Int, xzOffset: Int, nextStage: Block?) : Block {
|
||||
return this.create(name, TumblestoneBlock(BlockBehaviour.Properties.of()
|
||||
.mapColor(mapColor)
|
||||
.pushReaction(PushReaction.DESTROY)
|
||||
.noOcclusion()
|
||||
.strength(1.5F)
|
||||
@@ -1008,28 +893,4 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val block = BlocksInvoker.createLeavesBlock(SoundType.GRASS)
|
||||
return this.create(name, block)
|
||||
}
|
||||
|
||||
private fun typeGemBlock(name: String, mapColor: MapColor): Block {
|
||||
val typeGemBlock = this.create(name, Block(BlockBehaviour.Properties.of().mapColor(mapColor).strength(3.0F, 6.0F).sound(CobblemonSounds.TYPE_GEM_BLOCK_SOUNDS)))
|
||||
|
||||
val identifier = cobblemonResource(name)
|
||||
typeGemBlocks[identifier] = typeGemBlock
|
||||
|
||||
return typeGemBlock
|
||||
}
|
||||
|
||||
fun typeGemBlocks() = typeGemBlocks.toMap()
|
||||
|
||||
private fun typeGemCluster(name: String, gemBlock: Block, dropItemId: ResourceLocation, mapColor: MapColor): Block {
|
||||
val gemClusterBlock = this.create(name, TypeGemClusterBlock(BlockBehaviour.Properties.of().mapColor(mapColor).strength(2.0F, 3.0F).sound(CobblemonSounds.TYPE_GEM_CLUSTER_SOUNDS).pushReaction(PushReaction.DESTROY), gemBlock, dropItemId))
|
||||
|
||||
TypeGemClusterBlock.gemToClusterMap[gemBlock] = gemClusterBlock
|
||||
|
||||
val identifier = cobblemonResource(name)
|
||||
typeGemClusters[identifier] = gemClusterBlock
|
||||
|
||||
return gemClusterBlock
|
||||
}
|
||||
|
||||
fun typeGemClusters() = typeGemClusters.toMap()
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,11 @@ import net.minecraft.commands.CommandSourceStack
|
||||
import net.minecraft.commands.Commands
|
||||
|
||||
object CobblemonCommands {
|
||||
fun register(dispatcher: CommandDispatcher<CommandSourceStack>, registry: CommandBuildContext, selection: Commands.CommandSelection) {
|
||||
fun register(
|
||||
dispatcher: CommandDispatcher<CommandSourceStack>,
|
||||
registry: CommandBuildContext,
|
||||
selection: Commands.CommandSelection
|
||||
) {
|
||||
SpawnPokemon.register(dispatcher)
|
||||
SpawnNPCCommand.register(dispatcher)
|
||||
GivePokemon.register(dispatcher)
|
||||
@@ -71,11 +75,13 @@ object CobblemonCommands {
|
||||
MarkGiveAllCommand.register(dispatcher)
|
||||
PcSearchCommand.register(dispatcher)
|
||||
PcTakeCommand.register(dispatcher)
|
||||
GiveTmCommand.register(dispatcher)
|
||||
TmCommand.register(dispatcher)
|
||||
|
||||
// Possibly lock down registration if and only if under dev environment or running in an environment
|
||||
// with a certain system environment variable set
|
||||
CobblemonInfoCommand.register(dispatcher)
|
||||
CalculateSeatPositionCommand.register(dispatcher)
|
||||
CameraDebugCommand.register(dispatcher)
|
||||
StadiumBattleCommand.register(dispatcher)
|
||||
MolangUiCommand.register(dispatcher)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,12 +69,6 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
|
||||
.networkSynchronized(FoodColourComponent.PACKET_CODEC)
|
||||
.build())
|
||||
|
||||
@JvmField
|
||||
val TM_MOVE: DataComponentType<TMMoveComponent> = create("tm_move", DataComponentType.builder<TMMoveComponent>()
|
||||
.persistent(TMMoveComponent.CODEC)
|
||||
.networkSynchronized(TMMoveComponent.PACKET_CODEC)
|
||||
.build())
|
||||
|
||||
@JvmField
|
||||
val INGREDIENT: DataComponentType<IngredientComponent> = create("ingredient", DataComponentType.builder<IngredientComponent>()
|
||||
.persistent(IngredientComponent.CODEC)
|
||||
@@ -107,7 +101,6 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:flavour"), FLAVOUR)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ride_boosts"), RIDE_BOOST)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food_colour"), FOOD_COLOUR)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:tm_move"), TM_MOVE)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ingredient"), INGREDIENT)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food"), FOOD)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:mob_effects"), MOB_EFFECTS)
|
||||
|
||||
@@ -21,8 +21,8 @@ import com.cobblemon.mod.common.api.text.gray
|
||||
import com.cobblemon.mod.common.block.BerryBlock
|
||||
import com.cobblemon.mod.common.block.MintBlock
|
||||
import com.cobblemon.mod.common.block.MintBlock.MintType
|
||||
import com.cobblemon.mod.common.block.campfirepot.CampfirePotColor
|
||||
import com.cobblemon.mod.common.client.pokedex.PokedexType
|
||||
import com.cobblemon.mod.common.block.campfirepot.CampfirePotColor
|
||||
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
|
||||
import com.cobblemon.mod.common.item.*
|
||||
import com.cobblemon.mod.common.item.armor.CobblemonArmorTrims
|
||||
@@ -35,9 +35,11 @@ import com.cobblemon.mod.common.item.berry.HealingBerryItem
|
||||
import com.cobblemon.mod.common.item.berry.PPRestoringBerryItem
|
||||
import com.cobblemon.mod.common.item.berry.PortionHealingBerryItem
|
||||
import com.cobblemon.mod.common.item.berry.StatusCuringBerryItem
|
||||
import com.cobblemon.mod.common.item.interactive.PonigiriItem
|
||||
import com.cobblemon.mod.common.item.food.SinisterTeaItem
|
||||
import com.cobblemon.mod.common.item.interactive.*
|
||||
import com.cobblemon.mod.common.item.interactive.ability.AbilityChangeItem
|
||||
import com.cobblemon.mod.common.item.stadium.StadiumPlacementWandItem
|
||||
import com.cobblemon.mod.common.platform.PlatformRegistry
|
||||
import com.cobblemon.mod.common.pokeball.PokeBall
|
||||
import com.cobblemon.mod.common.pokemon.IVs
|
||||
@@ -74,6 +76,9 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val NPC_EDITOR = create("npc_editor", CobblemonItem(Item.Properties().stacksTo(1)))
|
||||
|
||||
@JvmField
|
||||
val STADIUM_PLACEMENT_WAND = create("stadium_placement_wand", StadiumPlacementWandItem())
|
||||
|
||||
@JvmField
|
||||
val pokeBalls = mutableListOf<PokeBallItem>()
|
||||
@JvmField
|
||||
@@ -452,19 +457,15 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val MONITOR = blockItem("monitor", CobblemonBlocks.MONITOR)
|
||||
@JvmField
|
||||
val DAMAGED_MONITOR = blockItem("damaged_monitor", CobblemonBlocks.DAMAGED_MONITOR)
|
||||
@JvmField
|
||||
val HEALING_MACHINE = blockItem("healing_machine", CobblemonBlocks.HEALING_MACHINE, Rarity.UNCOMMON)
|
||||
@JvmField
|
||||
val PC = blockItem("pc", CobblemonBlocks.PC)
|
||||
@JvmField
|
||||
val TM_MACHINE = blockItem("tm_machine", CobblemonBlocks.TM_MACHINE)
|
||||
@JvmField
|
||||
val DISC_SHELF = blockItem("disc_shelf", CobblemonBlocks.DISC_SHELF)
|
||||
@JvmField
|
||||
val PASTURE = blockItem("pasture", CobblemonBlocks.PASTURE)
|
||||
@JvmField
|
||||
val DISPLAY_CASE = blockItem("display_case", CobblemonBlocks.DISPLAY_CASE)
|
||||
@JvmField
|
||||
val STADIUM_BLOCK = blockItem("stadium_block", CobblemonBlocks.STADIUM_BLOCK)
|
||||
// @JvmField
|
||||
// val INCENSE_SWEET = blockItem("incense_sweet", CobblemonBlocks.INCENSE_SWEET) // TODO after 1.7
|
||||
|
||||
@@ -604,9 +605,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
|
||||
@JvmField val BERRY_JUICE = this.create("berry_juice", BerryJuiceItem())
|
||||
|
||||
@JvmField
|
||||
val HABITAT_BLOCK = blockItem("habitat_block", CobblemonBlocks.HABITAT_BLOCK)
|
||||
|
||||
@JvmField
|
||||
val GALARICA_NUTS = compostableItemNameBlockItem("galarica_nuts", CobblemonBlocks.GALARICA_NUT_BUSH)
|
||||
|
||||
@@ -1216,83 +1214,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val BLACK_TUMBLESTONE_BLOCK = blockItem("black_tumblestone_block", CobblemonBlocks.BLACK_TUMBLESTONE_BLOCK)
|
||||
|
||||
@JvmField
|
||||
val TYPE_GEM_CORE = blockItem("deepslate_crystal_core", CobblemonBlocks.TYPE_GEM_CORE)
|
||||
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_NORMAL = blockItem("normal_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_NORMAL)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_FIRE = blockItem("fire_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_FIRE)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_WATER = blockItem("water_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_WATER)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_ELECTRIC = blockItem("electric_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_ELECTRIC)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_GRASS = blockItem("grass_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_GRASS)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_ICE = blockItem("ice_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_ICE)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_FIGHTING = blockItem("fighting_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_FIGHTING)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_POISON = blockItem("poison_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_POISON)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_GROUND = blockItem("ground_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_GROUND)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_FLYING = blockItem("flying_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_FLYING)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_PSYCHIC = blockItem("psychic_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_PSYCHIC)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_BUG = blockItem("bug_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_BUG)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_ROCK = blockItem("rock_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_ROCK)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_GHOST = blockItem("ghost_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_GHOST)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_DRAGON = blockItem("dragon_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_DRAGON)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_DARK = blockItem("dark_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_DARK)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_STEEL = blockItem("steel_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_STEEL)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_FAIRY = blockItem("fairy_gem_cluster", CobblemonBlocks.TYPE_GEM_CLUSTER_FAIRY)
|
||||
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_NORMAL = blockItem("normal_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_NORMAL)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_FIRE = blockItem("fire_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_FIRE)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_WATER = blockItem("water_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_WATER)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_ELECTRIC = blockItem("electric_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_ELECTRIC)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_GRASS = blockItem("grass_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_GRASS)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_ICE = blockItem("ice_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_ICE)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_FIGHTING = blockItem("fighting_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_FIGHTING)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_POISON = blockItem("poison_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_POISON)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_GROUND = blockItem("ground_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_GROUND)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_FLYING = blockItem("flying_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_FLYING)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_PSYCHIC = blockItem("psychic_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_PSYCHIC)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_BUG = blockItem("bug_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_BUG)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_ROCK = blockItem("rock_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_ROCK)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_GHOST = blockItem("ghost_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_GHOST)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_DRAGON = blockItem("dragon_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_DRAGON)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_DARK = blockItem("dark_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_DARK)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_STEEL = blockItem("steel_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_STEEL)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_FAIRY = blockItem("fairy_gem_block", CobblemonBlocks.TYPE_GEM_BLOCK_FAIRY)
|
||||
|
||||
@JvmField
|
||||
val POKEROD_SMITHING_TEMPLATE = create("pokerod_smithing_template", SmithingTemplateItem(
|
||||
Component.translatable("item.minecraft.fishing_rod").blue(),
|
||||
@@ -1517,49 +1438,44 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
val RELIC_COIN_POUCH = blockItem("relic_coin_pouch", CobblemonBlocks.RELIC_COIN_POUCH)
|
||||
@JvmField
|
||||
val RELIC_COIN_SACK = blockItem("relic_coin_sack", CobblemonBlocks.RELIC_COIN_SACK)
|
||||
@JvmField
|
||||
val TECHNICAL_MACHINE = this.create("technical_machine", TechnicalMachineItem(Properties()))
|
||||
@JvmField
|
||||
val BLANK_TM = this.create("blank_tm", CobblemonItem(Item.Properties()))
|
||||
|
||||
// Type Gems
|
||||
@JvmField
|
||||
val NORMAL_GEM = this.create("normal_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_NORMAL))
|
||||
val NORMAL_GEM = noSettingsItem("normal_gem")
|
||||
@JvmField
|
||||
val FIRE_GEM = this.create("fire_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_FIRE))
|
||||
val FIRE_GEM = noSettingsItem("fire_gem")
|
||||
@JvmField
|
||||
val WATER_GEM = this.create("water_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_WATER))
|
||||
val WATER_GEM = noSettingsItem("water_gem")
|
||||
@JvmField
|
||||
val GRASS_GEM = this.create("grass_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_GRASS))
|
||||
val GRASS_GEM = noSettingsItem("grass_gem")
|
||||
@JvmField
|
||||
val ELECTRIC_GEM = this.create("electric_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_ELECTRIC))
|
||||
val ELECTRIC_GEM = noSettingsItem("electric_gem")
|
||||
@JvmField
|
||||
val ICE_GEM = this.create("ice_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_ICE))
|
||||
val ICE_GEM = noSettingsItem("ice_gem")
|
||||
@JvmField
|
||||
val FIGHTING_GEM = this.create("fighting_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_FIGHTING))
|
||||
val FIGHTING_GEM = noSettingsItem("fighting_gem")
|
||||
@JvmField
|
||||
val POISON_GEM = this.create("poison_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_POISON))
|
||||
val POISON_GEM = noSettingsItem("poison_gem")
|
||||
@JvmField
|
||||
val GROUND_GEM = this.create("ground_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_GROUND))
|
||||
val GROUND_GEM = noSettingsItem("ground_gem")
|
||||
@JvmField
|
||||
val FLYING_GEM = this.create("flying_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_FLYING))
|
||||
val FLYING_GEM = noSettingsItem("flying_gem")
|
||||
@JvmField
|
||||
val PSYCHIC_GEM = this.create("psychic_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_PSYCHIC))
|
||||
val PSYCHIC_GEM = noSettingsItem("psychic_gem")
|
||||
@JvmField
|
||||
val BUG_GEM = this.create("bug_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_BUG))
|
||||
val BUG_GEM = noSettingsItem("bug_gem")
|
||||
@JvmField
|
||||
val ROCK_GEM = this.create("rock_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_ROCK))
|
||||
val ROCK_GEM = noSettingsItem("rock_gem")
|
||||
@JvmField
|
||||
val GHOST_GEM = this.create("ghost_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_GHOST))
|
||||
val GHOST_GEM = noSettingsItem("ghost_gem")
|
||||
@JvmField
|
||||
val DRAGON_GEM = this.create("dragon_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_DRAGON))
|
||||
val DRAGON_GEM = noSettingsItem("dragon_gem")
|
||||
@JvmField
|
||||
val DARK_GEM = this.create("dark_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_DARK))
|
||||
val DARK_GEM = noSettingsItem("dark_gem")
|
||||
@JvmField
|
||||
val STEEL_GEM = this.create("steel_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_STEEL))
|
||||
val STEEL_GEM = noSettingsItem("steel_gem")
|
||||
@JvmField
|
||||
val FAIRY_GEM = this.create("fairy_gem", GemItem(Item.Properties(), CobblemonBlocks.TYPE_GEM_CLUSTER_FAIRY))
|
||||
|
||||
val FAIRY_GEM = noSettingsItem("fairy_gem")
|
||||
//@JvmField
|
||||
//val BINDING_SOIL = blockItem("binding_soil", CobblemonBlocks.BINDING_SOIL)
|
||||
|
||||
|
||||
@@ -15,12 +15,12 @@ import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.codecs.ListCodec
|
||||
import com.mojang.serialization.codecs.PrimitiveCodec
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.GlobalPos
|
||||
import net.minecraft.core.UUIDUtil
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import java.util.*
|
||||
|
||||
object CobblemonMemories {
|
||||
val memories = mutableMapOf<String, MemoryModuleType<*>>()
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
|
||||
import com.cobblemon.mod.common.api.molangui.container.MolangContainerMenu
|
||||
import com.cobblemon.mod.common.block.campfirepot.CookingPotMenu
|
||||
import com.cobblemon.mod.common.block.tmmachine.TMMachineMenu
|
||||
import com.cobblemon.mod.common.platform.PlatformRegistry
|
||||
import net.minecraft.core.Registry
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
@@ -20,8 +20,8 @@ import net.minecraft.world.inventory.MenuType
|
||||
|
||||
object CobblemonMenuType : PlatformRegistry<Registry<MenuType<*>>, ResourceKey<Registry<MenuType<*>>>, MenuType<*>>() {
|
||||
|
||||
val COOKING_POT = MenuType.register("cooking_pot_menu", ::CookingPotMenu)
|
||||
val TM_MACHINE = MenuType.register("tm_machine_menu", ::TMMachineMenu)
|
||||
val COOKING_POT = MenuType.register("cooking_pot", ::CookingPotMenu)
|
||||
val MOLANG_UI = MenuType.register("molang_ui", ::MolangContainerMenu)
|
||||
|
||||
override val registry: Registry<MenuType<*>>
|
||||
get() = BuiltInRegistries.MENU
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.moves.MoveSelector
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.MovesetBuilderAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.StringToObjectAdapter
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
|
||||
object CobblemonMovesetBuilders : JsonDataRegistry<MovesetBuilder> {
|
||||
override val gson: Gson = GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(MovesetBuilder::class.java, MovesetBuilderAdapter)
|
||||
.registerTypeAdapter(MoveSelector::class.java, StringToObjectAdapter(MoveSelector.selectors))
|
||||
.create()
|
||||
|
||||
override val typeToken = TypeToken.get(MovesetBuilder::class.java)
|
||||
override val resourcePath = "moveset_builders"
|
||||
override val id: ResourceLocation = cobblemonResource("moveset_builders")
|
||||
override val type = PackType.SERVER_DATA
|
||||
override val observable = SimpleObservable<CobblemonMovesetBuilders>()
|
||||
|
||||
val movesetBuilders = mutableMapOf<ResourceLocation, MovesetBuilder>()
|
||||
|
||||
fun getOrThrow(id: ResourceLocation): MovesetBuilder {
|
||||
return movesetBuilders[id]
|
||||
?: throw IllegalArgumentException("Unknown MovesetBuilder id: $id")
|
||||
}
|
||||
|
||||
override fun sync(player: ServerPlayer) { /* These don't sync. But what if they did? Nah probably shouldn't sync. */ }
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, MovesetBuilder>) {
|
||||
movesetBuilders.clear()
|
||||
data.forEach { (id, value) -> value.id = id }
|
||||
movesetBuilders.putAll(data)
|
||||
observable.emit(this)
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.client.net.CalculateSeatPositionsHandler
|
||||
import com.cobblemon.mod.common.client.net.OpenBehaviourEditorHandler
|
||||
import com.cobblemon.mod.common.client.net.PlayerInteractOptionsHandler
|
||||
import com.cobblemon.mod.common.client.net.SetClientPlayerDataHandler
|
||||
@@ -17,6 +18,14 @@ import com.cobblemon.mod.common.client.net.battle.*
|
||||
import com.cobblemon.mod.common.client.net.callback.move.OpenMoveCallbackHandler
|
||||
import com.cobblemon.mod.common.client.net.callback.party.OpenPartyCallbackHandler
|
||||
import com.cobblemon.mod.common.client.net.callback.partymove.OpenPartyMoveCallbackHandler
|
||||
import com.cobblemon.mod.common.client.net.camera.CameraDebugHandler
|
||||
import com.cobblemon.mod.common.client.net.camera.CameraEndHandler
|
||||
import com.cobblemon.mod.common.client.net.camera.CameraFocusHandler
|
||||
import com.cobblemon.mod.common.client.net.camera.CameraStartHandler
|
||||
import com.cobblemon.mod.common.client.net.camera.CameraStateHandler
|
||||
import com.cobblemon.mod.common.client.net.camera.CameraTransformHandler
|
||||
import com.cobblemon.mod.common.client.net.camera.CompositorHandler
|
||||
import com.cobblemon.mod.common.client.net.camera.SplitScreenHandler
|
||||
import com.cobblemon.mod.common.client.net.cooking.ToggleCookingPotLidHandler
|
||||
import com.cobblemon.mod.common.client.net.data.CobblemonMechanicsSyncHandler
|
||||
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
|
||||
@@ -32,10 +41,10 @@ import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormParticleHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.ExpGainedDataPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.InteractPokemonUIPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.PokedexUIPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.SetActiveTMPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.SetTMMachineContainerDataPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.molangui.MolangUiCloseHandler
|
||||
import com.cobblemon.mod.common.client.net.molangui.MolangUiOpenHandler
|
||||
import com.cobblemon.mod.common.client.net.molangui.MolangUiUpdateHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.habitat.OpenHabitatBlockEditorHandler
|
||||
import com.cobblemon.mod.common.client.net.npc.CloseNPCEditorHandler
|
||||
import com.cobblemon.mod.common.client.net.npc.OpenNPCEditorHandler
|
||||
import com.cobblemon.mod.common.client.net.orientation.ClientboundUpdateOrientationHandler
|
||||
@@ -45,13 +54,13 @@ import com.cobblemon.mod.common.client.net.pasture.PokemonPasturedHandler
|
||||
import com.cobblemon.mod.common.client.net.pasture.PokemonUnpasturedHandler
|
||||
import com.cobblemon.mod.common.client.net.pasture.UpdatePastureConflictFlagHandler
|
||||
import com.cobblemon.mod.common.client.net.pokedex.ServerConfirmedRegisterHandler
|
||||
import com.cobblemon.mod.common.client.net.pokemon.update.ClientboundSeatAssignmentPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.pokemon.update.ClientboundUpdateRidingStateHandler
|
||||
import com.cobblemon.mod.common.client.net.pokemon.update.PokemonUpdatePacketHandler
|
||||
import com.cobblemon.mod.common.client.net.riding.ClientboundUpdateDriverInputHandler
|
||||
import com.cobblemon.mod.common.client.net.settings.OpenCobblemonConfigEditorHandler
|
||||
import com.cobblemon.mod.common.client.net.settings.ServerSettingsPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.sound.UnvalidatedPlaySoundS2CPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.stadium.OpenStadiumConfigHandler
|
||||
import com.cobblemon.mod.common.client.net.spawn.SpawnExtraDataEntityHandler
|
||||
import com.cobblemon.mod.common.client.net.starter.StarterUIPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.storage.RemoveClientPokemonHandler
|
||||
@@ -71,11 +80,20 @@ import com.cobblemon.mod.common.client.net.trade.TradeProcessStartedHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeStartedHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeUpdatedHandler
|
||||
import com.cobblemon.mod.common.net.PacketRegisterInfo
|
||||
import com.cobblemon.mod.common.net.messages.client.CalculateSeatPositionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.OpenBehaviourEditorPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.*
|
||||
import com.cobblemon.mod.common.net.messages.client.camera.CameraDebugPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.camera.CameraEndPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.camera.CameraFocusPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.camera.CameraStartPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.camera.CameraStatePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.camera.CameraTransformPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.camera.CompositorPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.camera.SplitScreenPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenMoveCallbackPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyCallbackPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyMoveCallbackPacket
|
||||
@@ -83,6 +101,9 @@ import com.cobblemon.mod.common.net.messages.client.cooking.SeasoningRegistrySyn
|
||||
import com.cobblemon.mod.common.net.messages.client.cooking.ToggleCookingPotLidPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.data.*
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.RequestOpenRidingStatsDebugGUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.molangui.MolangUiClosePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.molangui.MolangUiOpenPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.molangui.MolangUiUpdatePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.PokeSnackBlockParticlesPacket
|
||||
@@ -90,7 +111,6 @@ import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacke
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.SaccharineLogBlockParticlesPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.habitat.OpenHabitatBlockEditorPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.npc.CloseNPCEditorPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.npc.OpenNPCEditorPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateDriverInputPacket
|
||||
@@ -104,12 +124,9 @@ import com.cobblemon.mod.common.net.messages.client.pokedex.ServerConfirmedRegis
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.*
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.AddEvolutionPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.ClearEvolutionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.ClientboundSeatAssignmentPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.RemoveEvolutionPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.form.AddFormPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.form.ClearFormsPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.form.RemoveFormPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.settings.OpenCobblemonConfigScreenPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.stadium.OpenStadiumConfigPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.sound.UnvalidatedPlaySoundS2CPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.spawn.SpawnGenericBedrockPacket
|
||||
@@ -146,16 +163,8 @@ import com.cobblemon.mod.common.net.messages.client.trade.TradeUpdatedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.ExpGainedDataPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.PokedexUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.SetActiveTMPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.SetTMMachineContainerDataPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.BattleChallengePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.BattleChallengeResponsePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.BenchMovePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.RequestMoveSwapPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.RequestPlayerInteractionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.SelectStarterPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.SendOutPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.*
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamLeavePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamRequestPacket
|
||||
@@ -165,7 +174,6 @@ import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.behaviour.DamageOnCollisionPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.behaviour.SetEntityBehaviourPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.block.TMMachineTeachMovePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonSelectedPacket
|
||||
@@ -178,8 +186,13 @@ import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidin
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidingStatsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.dialogue.EscapeDialoguePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.dialogue.InputToDialoguePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.habitat.SaveHabitatBlockSettingsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.molangui.MolangUiActionPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.molangui.MolangUiClosedPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.molangui.MolangUiVersionPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.npc.SaveNPCPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.stadium.RequestStadiumPlacementWandPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.stadium.StadiumPlacementSetPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.stadium.UpdateStadiumBlockPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pasture.PasturePokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pasture.SetPastureConflictPacket
|
||||
@@ -194,11 +207,8 @@ import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetItemHidden
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetMarkingsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetNicknamePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.update.evolution.AcceptEvolutionPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.update.form.AcceptFormPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pokemon.update.form.RevertFormPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.riding.DismountPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdatePassengerPositionPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateRiderRotationPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.starter.RequestStarterScreenPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.storage.SwapPCPartyPokemonPacket
|
||||
@@ -213,11 +223,9 @@ import com.cobblemon.mod.common.net.messages.server.trade.ChangeTradeAcceptanceP
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.OfferTradePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.PerformTradePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.UpdateTradeOfferPacket
|
||||
import com.cobblemon.mod.common.net.serverhandling.AcceptFormHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.ChallengeHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.ChallengeResponseHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.RequestInteractionsHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.RevertFormHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.BattleSelectActionsHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.RemoveSpectatorHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateBattleHandler
|
||||
@@ -227,7 +235,6 @@ import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestResponseHan
|
||||
import com.cobblemon.mod.common.net.serverhandling.behaviour.DamageOnCollisionPacketHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.behaviour.SetEntityBehaviourHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.block.TMMachineTeachMoveHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectCancelledHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectedHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.party.PartyPokemonSelectedHandler
|
||||
@@ -240,10 +247,16 @@ import com.cobblemon.mod.common.net.serverhandling.debug.ServerboundUpdateRiding
|
||||
import com.cobblemon.mod.common.net.serverhandling.debug.ServerboundUpdateRidingStatsHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.dialogue.EscapeDialogueHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.dialogue.InputToDialogueHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.molangui.MolangUiActionHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.molangui.MolangUiClosedHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.molangui.MolangUiVersionHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.evolution.AcceptEvolutionHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.habitat.SaveHabitatBlockSettingsHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.npc.SaveNPCHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.stadium.RequestStadiumPlacementWandHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.stadium.StadiumPlacementSetHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.stadium.UpdateStadiumBlockHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.orientation.ServerboundUpdateOrientationHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.party.PartyTutorialPacketHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pasture.PasturePokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pasture.SetPastureConflictHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pasture.UnpastureAllPokemonHandler
|
||||
@@ -258,7 +271,6 @@ import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetMarkingsHan
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetNicknameHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.riding.DismountPokemonPacketHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.riding.DriverInputPacketHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.riding.ServerboundUpdatePassengerPositionHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.riding.ServerboundUpdateRiderRotationHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.starter.RequestStarterScreenHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.starter.SelectStarterPacketHandler
|
||||
@@ -267,7 +279,6 @@ import com.cobblemon.mod.common.net.serverhandling.storage.RequestMoveSwapHandle
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.SwapPCPartyPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.party.MovePartyPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.party.PartyTutorialPacketHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.party.ReleasePCPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.party.SwapPartyPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.pc.*
|
||||
@@ -346,8 +357,6 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(MarkingsUpdatePacket.ID, MarkingsUpdatePacket::decode, PokemonUpdatePacketHandler()))
|
||||
list.add(PacketRegisterInfo(RideBoostsUpdatePacket.ID, RideBoostsUpdatePacket::decode, PokemonUpdatePacketHandler()))
|
||||
list.add(PacketRegisterInfo(RideStaminaUpdatePacket.ID, RideStaminaUpdatePacket::decode, PokemonUpdatePacketHandler()))
|
||||
list.add(PacketRegisterInfo(AlphaUpdatePacket.ID, AlphaUpdatePacket::decode, PokemonUpdatePacketHandler()))
|
||||
list.add(PacketRegisterInfo(ScaleModifierUpdatePacket.ID, ScaleModifierUpdatePacket::decode, PokemonUpdatePacketHandler()))
|
||||
|
||||
// Evolution start
|
||||
list.add(PacketRegisterInfo(AddEvolutionPacket.ID, AddEvolutionPacket::decode, PokemonUpdatePacketHandler()))
|
||||
@@ -406,9 +415,85 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(BattlePersistentStatusPacket.ID, BattlePersistentStatusPacket::decode, BattlePersistentStatusHandler))
|
||||
list.add(PacketRegisterInfo(BattleMadeInvalidChoicePacket.ID, BattleMadeInvalidChoicePacket::decode, BattleMadeInvalidChoiceHandler))
|
||||
list.add(PacketRegisterInfo(BattleMusicPacket.ID, BattleMusicPacket::decode, BattleMusicHandler))
|
||||
list.add(PacketRegisterInfo(BattleChallengeExpiredPacket.ID, BattleChallengeExpiredPacket::decode, BattleChallengeExpiredHandler))
|
||||
list.add(PacketRegisterInfo(BattleReplacePokemonPacket.ID, BattleReplacePokemonPacket::decode, BattleReplacePokemonHandler))
|
||||
list.add(PacketRegisterInfo(BattleTransformPokemonPacket.ID, BattleTransformPokemonPacket::decode, BattleTransformPokemonHandler))
|
||||
list.add(
|
||||
PacketRegisterInfo(
|
||||
BattleChallengeExpiredPacket.ID,
|
||||
BattleChallengeExpiredPacket::decode,
|
||||
BattleChallengeExpiredHandler
|
||||
)
|
||||
)
|
||||
list.add(
|
||||
PacketRegisterInfo(
|
||||
BattleReplacePokemonPacket.ID,
|
||||
BattleReplacePokemonPacket::decode,
|
||||
BattleReplacePokemonHandler
|
||||
)
|
||||
)
|
||||
list.add(
|
||||
PacketRegisterInfo(
|
||||
BattleTransformPokemonPacket.ID,
|
||||
BattleTransformPokemonPacket::decode,
|
||||
BattleTransformPokemonHandler
|
||||
)
|
||||
)
|
||||
|
||||
// Camera packets
|
||||
list.add(
|
||||
PacketRegisterInfo(
|
||||
CameraStartPacket.ID,
|
||||
CameraStartPacket::decode,
|
||||
CameraStartHandler
|
||||
)
|
||||
)
|
||||
list.add(
|
||||
PacketRegisterInfo(
|
||||
CameraEndPacket.ID,
|
||||
CameraEndPacket::decode,
|
||||
CameraEndHandler
|
||||
)
|
||||
)
|
||||
list.add(
|
||||
PacketRegisterInfo(
|
||||
CameraDebugPacket.ID,
|
||||
CameraDebugPacket::decode,
|
||||
CameraDebugHandler
|
||||
)
|
||||
)
|
||||
list.add(
|
||||
PacketRegisterInfo(
|
||||
CameraTransformPacket.ID,
|
||||
CameraTransformPacket::decode,
|
||||
CameraTransformHandler
|
||||
)
|
||||
)
|
||||
list.add(
|
||||
PacketRegisterInfo(
|
||||
CameraFocusPacket.ID,
|
||||
CameraFocusPacket::decode,
|
||||
CameraFocusHandler
|
||||
)
|
||||
)
|
||||
list.add(
|
||||
PacketRegisterInfo(
|
||||
CameraStatePacket.ID,
|
||||
CameraStatePacket::decode,
|
||||
CameraStateHandler
|
||||
)
|
||||
)
|
||||
list.add(
|
||||
PacketRegisterInfo(
|
||||
SplitScreenPacket.ID,
|
||||
SplitScreenPacket::decode,
|
||||
SplitScreenHandler
|
||||
)
|
||||
)
|
||||
list.add(
|
||||
PacketRegisterInfo(
|
||||
CompositorPacket.ID,
|
||||
CompositorPacket::decode,
|
||||
CompositorHandler
|
||||
)
|
||||
)
|
||||
|
||||
// MultiBattleTeam Packets
|
||||
list.add(PacketRegisterInfo(TeamRequestNotificationPacket.ID, TeamRequestNotificationPacket::decode, TeamRequestNotificationHandler))
|
||||
@@ -447,6 +532,11 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(MarkRegistrySyncPacket.ID, MarkRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(RideSettingsSyncPacket.ID, RideSettingsSyncPacket::decode, RideSettingsSyncHandler))
|
||||
|
||||
// Molang UI packets
|
||||
list.add(PacketRegisterInfo(MolangUiOpenPacket.ID, MolangUiOpenPacket::decode, MolangUiOpenHandler))
|
||||
list.add(PacketRegisterInfo(MolangUiClosePacket.ID, MolangUiClosePacket::decode, MolangUiCloseHandler))
|
||||
list.add(PacketRegisterInfo(MolangUiUpdatePacket.ID, MolangUiUpdatePacket::decode, MolangUiUpdateHandler))
|
||||
|
||||
// Effects
|
||||
list.add(PacketRegisterInfo(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler))
|
||||
list.add(PacketRegisterInfo(SpawnSnowstormEntityParticlePacket.ID, SpawnSnowstormEntityParticlePacket::decode, SpawnSnowstormEntityParticleHandler))
|
||||
@@ -476,8 +566,23 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(OpenPasturePacket.ID, OpenPasturePacket::decode, OpenPastureHandler))
|
||||
list.add(PacketRegisterInfo(ClosePasturePacket.ID, ClosePasturePacket::decode, ClosePastureHandler))
|
||||
list.add(PacketRegisterInfo(PokemonPasturedPacket.ID, PokemonPasturedPacket::decode, PokemonPasturedHandler))
|
||||
list.add(PacketRegisterInfo(PokemonUnpasturedPacket.ID, PokemonUnpasturedPacket::decode, PokemonUnpasturedHandler))
|
||||
list.add(PacketRegisterInfo(UpdatePastureConflictFlagPacket.ID, UpdatePastureConflictFlagPacket::decode, UpdatePastureConflictFlagHandler))
|
||||
list.add(
|
||||
PacketRegisterInfo(
|
||||
PokemonUnpasturedPacket.ID,
|
||||
PokemonUnpasturedPacket::decode,
|
||||
PokemonUnpasturedHandler
|
||||
)
|
||||
)
|
||||
list.add(
|
||||
PacketRegisterInfo(
|
||||
UpdatePastureConflictFlagPacket.ID,
|
||||
UpdatePastureConflictFlagPacket::decode,
|
||||
UpdatePastureConflictFlagHandler
|
||||
)
|
||||
)
|
||||
|
||||
// Stadium
|
||||
list.add(PacketRegisterInfo(OpenStadiumConfigPacket.ID, OpenStadiumConfigPacket::decode, OpenStadiumConfigHandler))
|
||||
|
||||
// Orientation
|
||||
list.add(PacketRegisterInfo(ClientboundUpdateOrientationPacket.ID, ClientboundUpdateOrientationPacket::decode, ClientboundUpdateOrientationHandler))
|
||||
@@ -508,23 +613,16 @@ object CobblemonNetwork {
|
||||
// Pokédex scanning
|
||||
list.add(PacketRegisterInfo(ServerConfirmedRegisterPacket.ID, ServerConfirmedRegisterPacket::decode, ServerConfirmedRegisterHandler))
|
||||
|
||||
// Form packets
|
||||
list.add(PacketRegisterInfo(AddFormPacket.ID, AddFormPacket::decode, PokemonUpdatePacketHandler()))
|
||||
list.add(PacketRegisterInfo(ClearFormsPacket.ID, ClearFormsPacket::decode, PokemonUpdatePacketHandler()))
|
||||
list.add(PacketRegisterInfo(RemoveFormPacket.ID, RemoveFormPacket::decode, PokemonUpdatePacketHandler()))
|
||||
// Debug / cheats
|
||||
list.add(PacketRegisterInfo(CalculateSeatPositionsPacket.ID, CalculateSeatPositionsPacket::decode, CalculateSeatPositionsHandler))
|
||||
|
||||
// Riding
|
||||
list.add(PacketRegisterInfo(ClientboundUpdateRidingStatePacket.ID, ClientboundUpdateRidingStatePacket::decode, ClientboundUpdateRidingStateHandler))
|
||||
list.add(PacketRegisterInfo(ClientboundUpdateDriverInputPacket.ID, ClientboundUpdateDriverInputPacket::decode, ClientboundUpdateDriverInputHandler))
|
||||
list.add(PacketRegisterInfo(ClientboundSeatAssignmentPacket.ID, ClientboundSeatAssignmentPacket::decode, ClientboundSeatAssignmentPacketHandler))
|
||||
|
||||
// Habitat blocks
|
||||
list.add(PacketRegisterInfo(OpenHabitatBlockEditorPacket.ID, OpenHabitatBlockEditorPacket::decode, OpenHabitatBlockEditorHandler))
|
||||
|
||||
// Debug
|
||||
list.add(PacketRegisterInfo(OpenRidingStatsDebugGUIPacket.ID, OpenRidingStatsDebugGUIPacket::decode, OpenRidingStatsDebugGUIHandler))
|
||||
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
@@ -604,7 +702,6 @@ object CobblemonNetwork {
|
||||
|
||||
// Block entity
|
||||
list.add(PacketRegisterInfo(AdjustBlockEntityViewerCountPacket.ID, AdjustBlockEntityViewerCountPacket::decode, AdjustBlockEntityViewerCountHandler))
|
||||
list.add(PacketRegisterInfo(TMMachineTeachMovePacket.ID, TMMachineTeachMovePacket::decode, TMMachineTeachMoveHandler))
|
||||
|
||||
// Move select packets
|
||||
list.add(PacketRegisterInfo(MoveSelectedPacket.ID, MoveSelectedPacket::decode, MoveSelectedHandler))
|
||||
@@ -628,10 +725,6 @@ object CobblemonNetwork {
|
||||
// NPC packets
|
||||
list.add(PacketRegisterInfo(SaveNPCPacket.ID, SaveNPCPacket::decode, SaveNPCHandler))
|
||||
|
||||
// Form Packets
|
||||
list.add(PacketRegisterInfo(AcceptFormPacket.ID, AcceptFormPacket::decode, AcceptFormHandler))
|
||||
list.add(PacketRegisterInfo(RevertFormPacket.ID, RevertFormPacket::decode, RevertFormHandler))
|
||||
|
||||
// Riding packet(s)
|
||||
list.add(PacketRegisterInfo(ServerboundUpdateOrientationPacket.ID, ServerboundUpdateOrientationPacket::decode, ServerboundUpdateOrientationHandler))
|
||||
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatePacket.ID, ServerboundUpdateRidingStatePacket::decode, ServerboundUpdateRidingStateHandler))
|
||||
@@ -641,26 +734,39 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(DismountPokemonPacket.ID, DismountPokemonPacket::decode, DismountPokemonPacketHandler))
|
||||
list.add(PacketRegisterInfo(ServerboundUpdateDriverInputPacket.ID, ServerboundUpdateDriverInputPacket::decode, DriverInputPacketHandler))
|
||||
list.add(PacketRegisterInfo(ServerboundUpdateRiderRotationPacket.ID, ServerboundUpdateRiderRotationPacket::decode, ServerboundUpdateRiderRotationHandler))
|
||||
list.add(PacketRegisterInfo(ServerboundUpdatePassengerPositionPacket.ID, ServerboundUpdatePassengerPositionPacket::decode, ServerboundUpdatePassengerPositionHandler))
|
||||
|
||||
// Cooking
|
||||
list.add(PacketRegisterInfo(ToggleCookingPotLidPacket.ID, ToggleCookingPotLidPacket::decode, ToggleCookingPotLidHandler))
|
||||
|
||||
// TM Machine
|
||||
list.add(PacketRegisterInfo(SetActiveTMPacket.ID, SetActiveTMPacket::decode, SetActiveTMPacketHandler))
|
||||
list.add(PacketRegisterInfo(SetTMMachineContainerDataPacket.ID, SetTMMachineContainerDataPacket::decode, SetTMMachineContainerDataPacketHandler))
|
||||
|
||||
// Behaviour Packets
|
||||
list.add(PacketRegisterInfo(SetEntityBehaviourPacket.ID, SetEntityBehaviourPacket::decode, SetEntityBehaviourHandler))
|
||||
list.add(PacketRegisterInfo(DamageOnCollisionPacket.ID, DamageOnCollisionPacket::decode, DamageOnCollisionPacketHandler))
|
||||
list.add(
|
||||
PacketRegisterInfo(
|
||||
SetEntityBehaviourPacket.ID,
|
||||
SetEntityBehaviourPacket::decode,
|
||||
SetEntityBehaviourHandler
|
||||
)
|
||||
)
|
||||
list.add(
|
||||
PacketRegisterInfo(
|
||||
DamageOnCollisionPacket.ID,
|
||||
DamageOnCollisionPacket::decode,
|
||||
DamageOnCollisionPacketHandler
|
||||
)
|
||||
)
|
||||
|
||||
// Habitat blocks
|
||||
list.add(PacketRegisterInfo(SaveHabitatBlockSettingsPacket.ID, SaveHabitatBlockSettingsPacket::decode, SaveHabitatBlockSettingsHandler))
|
||||
// Molang UI packets
|
||||
list.add(PacketRegisterInfo(MolangUiActionPacket.ID, MolangUiActionPacket::decode, MolangUiActionHandler))
|
||||
list.add(PacketRegisterInfo(MolangUiClosedPacket.ID, MolangUiClosedPacket::decode, MolangUiClosedHandler))
|
||||
list.add(PacketRegisterInfo(MolangUiVersionPacket.ID, MolangUiVersionPacket::decode, MolangUiVersionHandler))
|
||||
|
||||
// Debug / cheats
|
||||
// Stadium
|
||||
list.add(PacketRegisterInfo(UpdateStadiumBlockPacket.ID, UpdateStadiumBlockPacket::decode, UpdateStadiumBlockHandler))
|
||||
list.add(PacketRegisterInfo(RequestStadiumPlacementWandPacket.ID, RequestStadiumPlacementWandPacket::decode, RequestStadiumPlacementWandHandler))
|
||||
list.add(PacketRegisterInfo(StadiumPlacementSetPacket.ID, StadiumPlacementSetPacket::decode, StadiumPlacementSetHandler))
|
||||
|
||||
// Debug
|
||||
list.add(PacketRegisterInfo(RequestOpenRidingStatsDebugGUIPacket.ID, RequestOpenRidingStatsDebugGUIPacket::decode, RequestOpenRidingStatsDebugGUIHandler))
|
||||
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.npc.PartyComposition
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
|
||||
object CobblemonPartyCompositions : JsonDataRegistry<PartyComposition> {
|
||||
override val id: ResourceLocation = cobblemonResource("party_compositions")
|
||||
override val type = PackType.SERVER_DATA
|
||||
override val typeToken: TypeToken<PartyComposition> = TypeToken.get(PartyComposition::class.java)
|
||||
override val resourcePath: String = "party_compositions"
|
||||
override val observable = SimpleObservable<CobblemonPartyCompositions>()
|
||||
override val gson: Gson = GsonBuilder()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
|
||||
val partyCompositions = mutableMapOf<ResourceLocation, PartyComposition>()
|
||||
|
||||
override fun sync(player: ServerPlayer) {
|
||||
// probably worth syncing which exist, yeah. the deeper details probably not. I'll get to this eventually.
|
||||
}
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, PartyComposition>) {
|
||||
data.entries.forEach { it.value.id = it.key }
|
||||
partyCompositions.clear()
|
||||
partyCompositions.putAll(data)
|
||||
observable.emit(this)
|
||||
Cobblemon.LOGGER.info("Loaded ${data.size} party compositions.")
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.npc.PartyPool
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.util.ScriptableIntRange
|
||||
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.IntRangeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.MovesetBuilderReferenceAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.PokemonPropertiesAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.ScriptableIntRangeAdapter
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
|
||||
object CobblemonPartyPools : JsonDataRegistry<PartyPool> {
|
||||
override val id: ResourceLocation = cobblemonResource("party_pools")
|
||||
override val type = PackType.SERVER_DATA
|
||||
override val typeToken: TypeToken<PartyPool> = TypeToken.get(PartyPool::class.java)
|
||||
override val resourcePath: String = "party_pools"
|
||||
override val observable = SimpleObservable<CobblemonPartyPools>()
|
||||
override val gson: Gson = GsonBuilder()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(PokemonProperties::class.java, PokemonPropertiesAdapter(saveLong = false))
|
||||
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
|
||||
.registerTypeAdapter(ScriptableIntRange::class.java, ScriptableIntRangeAdapter)
|
||||
.registerTypeAdapter(MovesetBuilder::class.java, MovesetBuilderReferenceAdapter)
|
||||
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
|
||||
val partyPools = mutableMapOf<ResourceLocation, PartyPool>()
|
||||
|
||||
override fun sync(player: ServerPlayer) {
|
||||
// probably worth syncing. Knowing the details might help exploit the server? lot of effort for little though.
|
||||
}
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, PartyPool>) {
|
||||
data.entries.forEach { it.value.id = it.key }
|
||||
partyPools.clear()
|
||||
partyPools.putAll(data)
|
||||
observable.emit(this)
|
||||
}
|
||||
}
|
||||
@@ -30,10 +30,6 @@ object CobblemonPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Regist
|
||||
// TODO after 1.7
|
||||
// @JvmField
|
||||
// val INCENSE_SWEET_KEY = createKey("incense_sweet", CobblemonBlocks.INCENSE_SWEET, 0, 1)
|
||||
@JvmField
|
||||
val HABITAT_BLOCK_KEY: ResourceKey<PoiType> = createKey("habitat_block", CobblemonBlocks.HABITAT_BLOCK, 0, 1)
|
||||
@JvmField
|
||||
val HABITAT_BLOCK = create(HABITAT_BLOCK_KEY.location().path, PoiType(getBlockStates(CobblemonBlocks.HABITAT_BLOCK), 0, 1))
|
||||
|
||||
private fun createKey(string: String, block: Block, maxTickets: Int, validRange: Int ): ResourceKey<PoiType> =
|
||||
ResourceKey.create(Registries.POINT_OF_INTEREST_TYPE, cobblemonResource(string)).also { resourceKey ->
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.*
|
||||
import com.cobblemon.mod.common.pokemon.ai.PokemonDisturbancesSensor
|
||||
import com.cobblemon.mod.common.entity.sensor.BattlingPokemonSensor
|
||||
import com.cobblemon.mod.common.entity.sensor.NPCBattlingSensor
|
||||
import com.cobblemon.mod.common.pokemon.ai.PokemonDisturbancesSensor
|
||||
import java.util.function.Supplier
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.entity.ai.sensing.Sensor
|
||||
@@ -37,9 +37,6 @@ object CobblemonSensors {
|
||||
@JvmField
|
||||
val POKEMON_DEFEND_OWNER = register("pokemon_owner_under_attack", ::DefendOwnerSensor)
|
||||
|
||||
@JvmField
|
||||
val ALPHA_LEVEL_MATCHING = register("alpha_level_matching", ::AlphaLevelMatchingSensor)
|
||||
|
||||
@JvmField
|
||||
val NEARBY_GROWABLE_CROPS = register("nearby_growable_crops", ::PokemonGrowableCropSensor)
|
||||
|
||||
|
||||
@@ -108,9 +108,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
@JvmField
|
||||
val LEVELUP = this.create("gui.levelup")
|
||||
|
||||
@JvmField
|
||||
val MOVE_LEARN = this.create("gui.move_learn")
|
||||
|
||||
@JvmField
|
||||
val PVN_BATTLE = this.create("battle.pvn.default")
|
||||
@JvmField
|
||||
@@ -139,15 +136,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
@JvmField
|
||||
val MULCH_REMOVE = this.create("block.mulch.remove")
|
||||
|
||||
@JvmField
|
||||
val MONITOR_BREAK = this.create("block.monitor.break")
|
||||
@JvmField
|
||||
val MONITOR_INSERT = this.create("block.monitor.insert")
|
||||
@JvmField
|
||||
val MONITOR_LOADING = this.create("block.monitor.loading")
|
||||
@JvmField
|
||||
val MONITOR_GLITCHING = this.create("block.monitor.glitching")
|
||||
|
||||
@JvmField
|
||||
val FOSSIL_MACHINE_ACTIVATE = this.create("block.fossil_machine.activate")
|
||||
@JvmField
|
||||
@@ -212,28 +200,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
@JvmField
|
||||
val TUMBLESTONE_STEP = this.create("block.tumblestone.step")
|
||||
|
||||
@JvmField
|
||||
val TM_USE = this.create("item.tm.use")
|
||||
@JvmField
|
||||
val TM_MACHINE_BURN_LOOP = this.create("block.tm_machine.burn_loop")
|
||||
@JvmField
|
||||
val TM_MACHINE_CRAFT = this.create("block.tm_machine.craft")
|
||||
@JvmField
|
||||
val TM_MACHINE_START = this.create("block.tm_machine.start")
|
||||
@JvmField
|
||||
val TM_MACHINE_OPEN = this.create("block.tm_machine.open")
|
||||
@JvmField
|
||||
val TM_MACHINE_CLOSE = this.create("block.tm_machine.close")
|
||||
@JvmField
|
||||
val TM_MACHINE_TM_INSERT = this.create("block.tm_machine.tm_insert")
|
||||
@JvmField
|
||||
val TM_MACHINE_TM_RETRIEVE = this.create("block.tm_machine.tm_retrieve")
|
||||
@JvmField
|
||||
val TM_MACHINE_ITEM_INSERT = this.create("block.tm_machine.item_insert")
|
||||
@JvmField
|
||||
val TM_MACHINE_ITEM_RETRIEVE = this.create("block.tm_machine.item_retrieve")
|
||||
|
||||
|
||||
@JvmField
|
||||
val EVOLUTION_STONE_BLOCK_BREAK = this.create("block.evolution_stone_block.break")
|
||||
@JvmField
|
||||
@@ -272,9 +238,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
@JvmField
|
||||
val BERRY_EAT_FULL = this.create("item.berry.eat.full")
|
||||
|
||||
@JvmField
|
||||
val APRICORN_HARVEST = this.create("block.apricorn.harvest")
|
||||
|
||||
@JvmField
|
||||
val BIG_ROOT_BREAK = this.create("block.big_root.break")
|
||||
@JvmField
|
||||
@@ -349,13 +312,9 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val RIDE_LOOP_LEATHER_MONO = create("ride.loop.leather.mono")
|
||||
val RIDE_LOOP_LEATHER = create("ride.loop.leather")
|
||||
@JvmField
|
||||
val RIDE_LOOP_PLUMAGE_MONO = create("ride.loop.plumage.mono")
|
||||
@JvmField
|
||||
val RIDE_LOOP_LEATHER_STEREO = create("ride.loop.leather.stereo")
|
||||
@JvmField
|
||||
val RIDE_LOOP_PLUMAGE_STEREO = create("ride.loop.plumage.stereo")
|
||||
val RIDE_LOOP_PLUMAGE = create("ride.loop.plumage")
|
||||
|
||||
@JvmField
|
||||
val MOVE_QUICKATTACK_TARGET = this.create("move.quickattack.target")
|
||||
@@ -421,35 +380,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
TUMBLESTONE_STEP
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_BREAK = this.create("block.type_gem_block.break")
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_BREAK = this.create("block.type_gem_cluster.break")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_HIT = this.create("block.type_gem_block.hit")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_PLACE = this.create("block.type_gem_block.place")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_STEP = this.create("block.type_gem_block.step")
|
||||
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_SOUNDS = SoundType(1f, 1.1f,
|
||||
TYPE_GEM_CLUSTER_BREAK,
|
||||
TYPE_GEM_BLOCK_STEP,
|
||||
TYPE_GEM_BLOCK_PLACE,
|
||||
TYPE_GEM_BLOCK_HIT,
|
||||
TYPE_GEM_BLOCK_STEP
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_SOUNDS = SoundType(1f, 1.1f,
|
||||
TYPE_GEM_BLOCK_BREAK,
|
||||
TYPE_GEM_BLOCK_STEP,
|
||||
TYPE_GEM_BLOCK_PLACE,
|
||||
TYPE_GEM_BLOCK_HIT,
|
||||
TYPE_GEM_BLOCK_STEP
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val EVOLUTION_STONE_BLOCK_SOUNDS = SoundType(1f, 1.1f,
|
||||
EVOLUTION_STONE_BLOCK_BREAK,
|
||||
@@ -631,31 +561,5 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
CAMPFIRE_POT_STEP
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_PAPER_SMALL_PLACE = this.create("block.item_block.paper_small.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_PAPER_SMALL_BREAK = this.create("block.item_block.paper_small.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_PAPER_SMALL_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_PAPER_SMALL_BREAK,
|
||||
SoundEvents.WOOL_STEP,
|
||||
ITEM_BLOCK_PAPER_SMALL_PLACE,
|
||||
SoundEvents.WOOL_HIT,
|
||||
SoundEvents.WOOL_FALL
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val ITEM_BLOCK_PAPER_LARGE_PLACE = this.create("block.item_block.paper_large.place")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_PAPER_LARGE_BREAK = this.create("block.item_block.paper_large.break")
|
||||
@JvmField
|
||||
val ITEM_BLOCK_PAPER_LARGE_SOUNDS = SoundType(1f, 1.1f,
|
||||
ITEM_BLOCK_PAPER_LARGE_BREAK,
|
||||
SoundEvents.WOOL_STEP,
|
||||
ITEM_BLOCK_PAPER_LARGE_PLACE,
|
||||
SoundEvents.WOOL_HIT,
|
||||
SoundEvents.WOOL_FALL
|
||||
)
|
||||
|
||||
private fun create(name: String): SoundEvent = this.create(name, SoundEvent.createVariableRangeEvent(cobblemonResource(name)))
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import java.util.Optional
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.util.RandomSource
|
||||
import net.minecraft.world.entity.Entity
|
||||
@@ -19,6 +18,7 @@ import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.item.trading.ItemCost
|
||||
import net.minecraft.world.item.trading.MerchantOffer
|
||||
import java.util.Optional
|
||||
|
||||
/**
|
||||
* A generator for various trade offers in the mod.
|
||||
|
||||
@@ -32,9 +32,6 @@ object CobblemonCriteria : PlatformRegistry<Registry<CriterionTrigger<*>>, Resou
|
||||
@JvmField
|
||||
val CATCH_SHINY_POKEMON = this.create("catch_shiny_pokemon", SimpleCriterionTrigger(CountableCriterion.CODEC))
|
||||
|
||||
@JvmField
|
||||
val CATCH_ALPHA_POKEMON = this.create("catch_alpha_pokemon", SimpleCriterionTrigger(CountableCriterion.CODEC))
|
||||
|
||||
@JvmField
|
||||
val EGG_COLLECT = this.create("eggs_collected", SimpleCriterionTrigger(CountableCriterion.CODEC))
|
||||
|
||||
@@ -81,20 +78,8 @@ object CobblemonCriteria : PlatformRegistry<Registry<CriterionTrigger<*>>, Resou
|
||||
@JvmField
|
||||
val PLANT_TUMBLESTONE = this.create("plant_tumblestone", SimpleCriterionTrigger(PlantTumblestoneCriterion.CODEC))
|
||||
|
||||
@JvmField
|
||||
val PLANT_TYPE_GEM = this.create("plant_type_gem", SimpleCriterionTrigger(PlantTypeGemCriterion.CODEC))
|
||||
|
||||
val RIDING_STAT_BOOST = this.create("riding_stat_boost", SimpleCriterionTrigger(RidingStatBoostCriterion.CODEC))
|
||||
|
||||
@JvmField
|
||||
val LEARN_TM = this.create("has_learn_specific_tm", SimpleCriterionTrigger(LearnTMCriterion.CODEC))
|
||||
|
||||
@JvmField
|
||||
val LEARN_ALL_TM = this.create("has_learn_all_tm", SimpleCriterionTrigger(LearnAllTMCriterion.CODEC))
|
||||
|
||||
@JvmField
|
||||
val LEARN_TM_COUNT = this.create("tms_learned", SimpleCriterionTrigger(CountableCriterion.CODEC))
|
||||
|
||||
override val registry = BuiltInRegistries.TRIGGER_TYPES
|
||||
override val resourceKey = Registries.TRIGGER_TYPE
|
||||
}
|
||||
+1
-1
@@ -11,10 +11,10 @@ package com.cobblemon.mod.common.advancement.criterion
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import java.util.Optional
|
||||
import net.minecraft.advancements.critereon.ContextAwarePredicate
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import java.util.Optional
|
||||
|
||||
class AspectCriterion(
|
||||
playerCtx: Optional<ContextAwarePredicate>,
|
||||
|
||||
+1
-1
@@ -14,8 +14,8 @@ import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import java.util.Optional
|
||||
import net.minecraft.advancements.critereon.ContextAwarePredicate
|
||||
import net.minecraft.advancements.critereon.EntityPredicate
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
class CastPokeRodContext(val baitId: ResourceLocation)
|
||||
|
||||
|
||||
+1
-1
@@ -10,10 +10,10 @@ package com.cobblemon.mod.common.advancement.criterion
|
||||
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import java.util.Optional
|
||||
import net.minecraft.advancements.critereon.ContextAwarePredicate
|
||||
import net.minecraft.advancements.critereon.EntityPredicate
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import java.util.Optional
|
||||
|
||||
/**
|
||||
* A context that is used when you require a [CountableContext] along with some type string.
|
||||
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.advancement.criterion
|
||||
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.advancements.critereon.ContextAwarePredicate
|
||||
import net.minecraft.advancements.critereon.EntityPredicate
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import java.util.Optional
|
||||
|
||||
class LearnTMContext(val tm: ResourceLocation)
|
||||
|
||||
class LearnTMCriterion(
|
||||
playerCtx: Optional<ContextAwarePredicate>,
|
||||
val tm: String
|
||||
) : SimpleCriterionCondition<LearnTMContext>(playerCtx) {
|
||||
companion object {
|
||||
val CODEC: Codec<LearnTMCriterion> = RecordCodecBuilder.create { it.group(
|
||||
EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter(LearnTMCriterion::playerCtx),
|
||||
Codec.STRING.optionalFieldOf("tm", "any").forGetter(LearnTMCriterion::tm)
|
||||
).apply(it, ::LearnTMCriterion) }
|
||||
}
|
||||
|
||||
override fun matches(player: ServerPlayer, context: LearnTMContext): Boolean {
|
||||
return tm == "any" || context.tm == tm.asIdentifierDefaultingNamespace()
|
||||
}
|
||||
}
|
||||
|
||||
class LearnAllTMContext
|
||||
|
||||
class LearnAllTMCriterion(
|
||||
playerCtx: Optional<ContextAwarePredicate>
|
||||
) : SimpleCriterionCondition<LearnAllTMContext>(playerCtx) {
|
||||
companion object {
|
||||
val CODEC: Codec<LearnAllTMCriterion> = RecordCodecBuilder.create { it.group(
|
||||
EntityPredicate.ADVANCEMENT_CODEC.optionalFieldOf("player").forGetter(LearnAllTMCriterion::playerCtx)
|
||||
).apply(it, ::LearnAllTMCriterion) }
|
||||
}
|
||||
|
||||
override fun matches(player: ServerPlayer, context: LearnAllTMContext) = true
|
||||
}
|
||||
+39
-3
@@ -14,7 +14,7 @@ import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.advancements.critereon.ContextAwarePredicate
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import java.util.*
|
||||
import java.util.Optional
|
||||
|
||||
class PlantTumblestoneContext(val pos: BlockPos, val block: TumblestoneBlock)
|
||||
|
||||
@@ -29,6 +29,42 @@ class PlantTumblestoneCriterion(
|
||||
}
|
||||
|
||||
override fun matches(player: ServerPlayer, context: PlantTumblestoneContext): Boolean {
|
||||
return context.block.canGrow(player.level().getBlockState(context.pos), context.pos, player.level())
|
||||
return context.block.canGrow(context.pos, player.level())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///**
|
||||
// * A criterion condition for when a tumblestone block is planted
|
||||
// * @param id The identifier of the criterion
|
||||
// * @param predicate The predicate for the criterion
|
||||
// *
|
||||
// * @author Aethen
|
||||
// * @since 02/28/2024
|
||||
// */
|
||||
//class PlantTumblestoneCriterionCondition(id: Identifier, predicate: LootContextPredicate) :
|
||||
// SimpleCriterionCondition<PlantTumblestoneContext>(id, predicate) {
|
||||
// override fun toJson(json: JsonObject) {
|
||||
// // Add properties to json if needed. None needed for this criterion
|
||||
// }
|
||||
//
|
||||
// override fun fromJson(json: JsonObject) {
|
||||
// // Parse properties from json if needed. None needed for this criterion
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Checks if the tumblestone block can grow at the given position
|
||||
// * @param player The player that planted the tumblestone block
|
||||
// * @param context The context of the criterion
|
||||
// * @return True if the tumblestone block can grow at the given position, false otherwise
|
||||
// */
|
||||
// override fun matches(player: ServerPlayer, context: PlantTumblestoneContext): Boolean {
|
||||
// return context.tumbleStoneBlock.canGrow(context.pos, player.level())
|
||||
// }
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * The context of the [PlantTumblestoneCriterionCondition]
|
||||
// * @param pos The position of the tumblestone block
|
||||
// * @param tumbleStoneBlock The tumblestone block
|
||||
// */
|
||||
//open class PlantTumblestoneContext(var pos: BlockPos, var tumbleStoneBlock: TumblestoneBlock)
|
||||
-35
@@ -1,35 +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.advancement.criterion
|
||||
|
||||
import com.cobblemon.mod.common.block.TypeGemClusterBlock
|
||||
import com.cobblemon.mod.common.block.TypeGemCoreBlock
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.advancements.critereon.ContextAwarePredicate
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import java.util.*
|
||||
|
||||
class PlantTypeGemContext(val pos: BlockPos, val block: TypeGemClusterBlock)
|
||||
|
||||
class PlantTypeGemCriterion(
|
||||
playerCtx: Optional<ContextAwarePredicate>
|
||||
): SimpleCriterionCondition<PlantTypeGemContext>(playerCtx) {
|
||||
|
||||
companion object {
|
||||
val CODEC: Codec<PlantTypeGemCriterion> = RecordCodecBuilder.create { it.group(
|
||||
ContextAwarePredicate.CODEC.optionalFieldOf("player").forGetter(PlantTypeGemCriterion::playerCtx)
|
||||
).apply(it, ::PlantTypeGemCriterion) }
|
||||
}
|
||||
|
||||
override fun matches(player: ServerPlayer, context: PlantTypeGemContext): Boolean {
|
||||
return player.level().getBlockState(context.pos).block is TypeGemCoreBlock
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -14,8 +14,8 @@ import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import java.util.Optional
|
||||
import net.minecraft.advancements.critereon.ContextAwarePredicate
|
||||
import net.minecraft.advancements.critereon.EntityPredicate
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
class ReelInPokemonContext(val species : ResourceLocation, val baitId: ResourceLocation)
|
||||
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ class RidingStatBoostCriterion(
|
||||
val max = context.pokemon.pokemon.getMaxRideBoost(rideStat).toDouble()
|
||||
val min = context.pokemon.pokemon.getBaseRideStat(rideStat).toDouble()
|
||||
val value = context.pokemon.getRideStat(rideStat, ridingStyle, min, max)
|
||||
val isSufficient = (value >= this.statValue && this.statValue > 0.0) || !context.pokemon.pokemon.canApplyRideBoost(rideStat)
|
||||
val isSufficient = (value >= this.statValue && this.statValue > 0.0) || !context.pokemon.pokemon.canAddRideBoost(rideStat)
|
||||
if (isSufficient && this.rideStat == "any") {
|
||||
return true
|
||||
} else if (!isSufficient && this.rideStat == "all") {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
package com.cobblemon.mod.common.api.abilities
|
||||
|
||||
import com.cobblemon.mod.common.api.Priority
|
||||
import com.cobblemon.mod.common.api.data.ShowdownIdentifiable
|
||||
import com.cobblemon.mod.common.util.codec.CodecUtils
|
||||
import com.google.gson.JsonObject
|
||||
import com.mojang.serialization.Codec
|
||||
@@ -26,7 +25,7 @@ class AbilityTemplate(
|
||||
var builder: (AbilityTemplate, forced: Boolean, priority: Priority) -> Ability = { template, forced, priority -> Ability(template, forced, priority) },
|
||||
val displayName: String = "cobblemon.ability.$name",
|
||||
val description: String = "cobblemon.ability.$name.desc"
|
||||
) : ShowdownIdentifiable {
|
||||
) {
|
||||
/**
|
||||
* Returns the Ability or if applicable the extension connected to this template
|
||||
*/
|
||||
@@ -46,8 +45,6 @@ class AbilityTemplate(
|
||||
*/
|
||||
fun create(json: JsonObject) = create().loadFromJSON(json)
|
||||
|
||||
override fun showdownId(): String = ShowdownIdentifiable.REGEX.replace(this.name.lowercase(), "")
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
|
||||
+1
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
|
||||
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
|
||||
import com.cobblemon.mod.common.util.activityRegistry
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.ai.config.BehaviourConfig
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.resolve
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.util.getBooleanOrNull
|
||||
import com.cobblemon.mod.common.util.getDoubleOrNull
|
||||
import kotlin.math.sqrt
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
|
||||
/**
|
||||
* Used to maintain some settings around how the entity should wander this tick. The idea is you
|
||||
|
||||
@@ -8,12 +8,16 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
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
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
|
||||
@@ -11,8 +11,13 @@ package com.cobblemon.mod.common.api.ai.config.task
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.weightedSelection
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
|
||||
+2
@@ -8,10 +8,12 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask.Companion.wrapped
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
|
||||
+2
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
import net.minecraft.world.entity.ai.behavior.StopBeingAngryIfTargetDead
|
||||
|
||||
+2
@@ -9,7 +9,9 @@
|
||||
package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import net.minecraft.world.Difficulty
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
|
||||
+1
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.npc.ai.SwitchToBattleTask
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
|
||||
class SwitchToNPCBattleTaskConfig : SingleTaskConfig {
|
||||
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = emptyList<MoLangConfigVariable>()
|
||||
|
||||
+2
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
|
||||
+2
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.api.ai.config.task
|
||||
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
|
||||
+1
@@ -25,6 +25,7 @@ import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
|
||||
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.entity.ai.memory.MemoryStatus
|
||||
import net.minecraft.world.entity.ai.sensing.SensorType
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -22,7 +22,6 @@ import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import kotlin.math.ceil
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
@@ -35,6 +34,7 @@ import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.level.material.Fluids
|
||||
import net.minecraft.world.level.pathfinder.PathType
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.ceil
|
||||
|
||||
class WaterWanderTaskConfig : SingleTaskConfig {
|
||||
val condition = booleanVariable(WANDER, "water_wanders", true).asExpressible()
|
||||
|
||||
+26
-13
@@ -54,9 +54,9 @@ import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleQueueRequestPacket
|
||||
import com.cobblemon.mod.common.pokemon.transformation.progress.DefeatTransformationProgress
|
||||
import com.cobblemon.mod.common.pokemon.transformation.progress.LastBattleCriticalHitsTransformationProgress
|
||||
import com.cobblemon.mod.common.pokemon.transformation.requirements.DefeatRequirement
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.DefeatEvolutionProgress
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
|
||||
import com.cobblemon.mod.common.pokemon.requirements.DefeatRequirement
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import com.cobblemon.mod.common.util.giveOrDropItemStack
|
||||
@@ -85,7 +85,9 @@ open class PokemonBattle(
|
||||
/** Whether logging will be silenced for this battle. */
|
||||
var mute = !CobblemonBuildDetails.SNAPSHOT
|
||||
val struct = this.asMoLangValue()
|
||||
val runtime = MoLangRuntime().also { it.environment.query = struct }
|
||||
val runtime = MoLangRuntime().also {
|
||||
it.environment.query = struct
|
||||
}
|
||||
|
||||
val onEndHandlers: MutableList<(PokemonBattle) -> Unit> = mutableListOf()
|
||||
|
||||
@@ -97,8 +99,8 @@ open class PokemonBattle(
|
||||
this.actors.forEach { actor ->
|
||||
actor.battle = this
|
||||
actor.pokemonList.forEach { battlePokemon ->
|
||||
battlePokemon.effectedPokemon.requirementProgress
|
||||
.filterIsInstance<LastBattleCriticalHitsTransformationProgress>()
|
||||
battlePokemon.effectedPokemon.evolutionProxy.current().progress()
|
||||
.filterIsInstance<LastBattleCriticalHitsEvolutionProgress>()
|
||||
.forEach { it.reset() }
|
||||
}
|
||||
}
|
||||
@@ -137,6 +139,7 @@ open class PokemonBattle(
|
||||
|
||||
var dispatchResult = GO
|
||||
val dispatches = ConcurrentLinkedDeque<BattleDispatch>()
|
||||
val afterDispatches = mutableListOf<() -> Unit>()
|
||||
|
||||
val captureActions = mutableListOf<BattleCaptureAction>()
|
||||
|
||||
@@ -271,13 +274,12 @@ open class PokemonBattle(
|
||||
val facedFainted = opponentPokemon.facedOpponents.contains(faintedPokemon)
|
||||
val pokemon = opponentPokemon.effectedPokemon
|
||||
if (facedFainted) {
|
||||
if (pokemon.hasMatchingRequirement<DefeatRequirement>({ it.target.matches(faintedPokemon.effectedPokemon) })) {
|
||||
val requiredPokemonProperties = pokemon.getRelevantRequirements<DefeatRequirement> { it.target.matches(faintedPokemon.effectedPokemon) }
|
||||
.map { it.target }
|
||||
.distinctBy { it.asString() }
|
||||
for (props in requiredPokemonProperties) {
|
||||
val progress = pokemon.progressFirstOrCreate({ it is DefeatTransformationProgress && it.currentProgress().target.asString().equals(props.asString(), ignoreCase = true) }, ::DefeatTransformationProgress)
|
||||
progress.updateProgress(DefeatTransformationProgress.Progress(props.copy(), progress.currentProgress().amount + 1))
|
||||
pokemon.lockedEvolutions.forEach { evolution ->
|
||||
evolution.requirements.filterIsInstance<DefeatRequirement>().forEach { defeatRequirement ->
|
||||
if (defeatRequirement.target.matches(faintedPokemon.effectedPokemon)) {
|
||||
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is DefeatEvolutionProgress && it.currentProgress().target == defeatRequirement.target }) { DefeatEvolutionProgress() }
|
||||
progress.updateProgress(DefeatEvolutionProgress.Progress(defeatRequirement.target, progress.currentProgress().amount + 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -400,10 +402,12 @@ open class PokemonBattle(
|
||||
|
||||
fun dispatch(dispatcher: () -> DispatchResult) {
|
||||
dispatches.add(BattleDispatch { dispatcher() })
|
||||
|
||||
}
|
||||
|
||||
fun dispatchToFront(dispatcher: () -> DispatchResult) {
|
||||
dispatches.addFirst(BattleDispatch { dispatcher() })
|
||||
|
||||
}
|
||||
|
||||
fun dispatchWaitingToFront(delaySeconds: Float = 1F, dispatcher: () -> Unit) {
|
||||
@@ -454,12 +458,21 @@ open class PokemonBattle(
|
||||
dispatches.addFirst(dispatcher)
|
||||
}
|
||||
|
||||
fun doWhenClear(action: () -> Unit) {
|
||||
afterDispatches.add(action)
|
||||
}
|
||||
|
||||
fun tick() {
|
||||
try {
|
||||
while (dispatchResult.canProceed()) {
|
||||
val dispatch = dispatches.poll() ?: break
|
||||
dispatchResult = dispatch(this)
|
||||
}
|
||||
|
||||
if (dispatches.isEmpty()) {
|
||||
afterDispatches.toList().forEach { it() }
|
||||
afterDispatches.clear()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LOGGER.error("Exception while ticking a battle. Saving battle log.", e)
|
||||
val message = battleLang("crash").red()
|
||||
|
||||
+12
-9
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.battles.model.actor
|
||||
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
@@ -22,10 +24,11 @@ import com.cobblemon.mod.common.item.battle.BagItem
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleApplyPassResponsePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
|
||||
import java.util.UUID
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.UUID
|
||||
|
||||
abstract class BattleActor(
|
||||
val uuid: UUID,
|
||||
@@ -52,6 +55,11 @@ abstract class BattleActor(
|
||||
var itemsUsed = mutableListOf<BagItem>()
|
||||
/** For when battles start, it's the number of Pokémon that are still in the process of being sent out (animation wise) */
|
||||
var stillSendingOutCount = 0
|
||||
/**
|
||||
* Optional override for send-out positions. When present the indexes follow the
|
||||
* actor's active slot order (e.g. slot `a` -> index 0).
|
||||
*/
|
||||
var stadiumSendOutPositions: List<Vec3>? = null
|
||||
|
||||
abstract val type: ActorType
|
||||
|
||||
@@ -74,7 +82,7 @@ abstract class BattleActor(
|
||||
fun turn() {
|
||||
val request = request ?: return
|
||||
responses.clear()
|
||||
if (activePokemon.any { it.isAlive() }) {
|
||||
if(activePokemon.any { it.isAlive() }) {
|
||||
mustChoose = true
|
||||
sendUpdate(BattleMakeChoicePacket())
|
||||
}
|
||||
@@ -88,15 +96,15 @@ abstract class BattleActor(
|
||||
}
|
||||
}
|
||||
|
||||
fun postUpdate() {
|
||||
fun upkeep() {
|
||||
val request = request ?: return
|
||||
val forceSwitchPokemon = request.forceSwitch.mapIndexedNotNull { index, b -> if (b) activePokemon[index] else null }
|
||||
if (forceSwitchPokemon.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
mustChoose = true // This must happen before the sendUpdate, not after, otherwise AI-driven actors will get locked (they may reply instantly)
|
||||
sendUpdate(BattleMakeChoicePacket())
|
||||
mustChoose = true
|
||||
}
|
||||
|
||||
fun setActionResponses(responses: List<ShowdownActionResponse>) {
|
||||
@@ -152,11 +160,6 @@ abstract class BattleActor(
|
||||
open fun sendMessage(component: Component) {
|
||||
sendUpdate(BattleMessagePacket(component))
|
||||
}
|
||||
/**
|
||||
* Called when this actor's Pokemon uses a move. Default implementation does nothing.
|
||||
* Subclasses (NPC-backed actors, players, etc) may override to trigger animations or UI.
|
||||
*/
|
||||
open fun onUseMove(user: BattlePokemon, target: BattlePokemon?) {}
|
||||
open fun awardExperience(battlePokemon: BattlePokemon, experience: Int) {}
|
||||
open fun sendUpdate(packet: NetworkPacket<*>) {}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import com.cobblemon.mod.common.util.adapters.*
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import java.awt.Color
|
||||
import net.minecraft.advancements.critereon.MinMaxBounds
|
||||
import net.minecraft.core.registries.Registries
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
@@ -31,6 +30,7 @@ import net.minecraft.tags.TagKey
|
||||
import net.minecraft.world.level.biome.Biome
|
||||
import net.minecraft.world.phys.AABB
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.awt.Color
|
||||
|
||||
/**
|
||||
* The data registry for [Berry].
|
||||
|
||||
@@ -26,10 +26,10 @@ import com.cobblemon.mod.common.util.writeBox
|
||||
import com.cobblemon.mod.common.util.writeEnumConstant
|
||||
import com.cobblemon.mod.common.util.writeIdentifier
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import java.awt.Color
|
||||
import java.util.EnumSet
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import java.awt.Color
|
||||
import java.util.EnumSet
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.tags.TagKey
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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.camera
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import net.minecraft.resources.ResourceKey
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.level.ChunkPos
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.chunk.LevelChunk
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus
|
||||
import java.util.ArrayDeque
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Handles chunk forceloading for cameras.
|
||||
* Chunks are queued and processed in small batches each tick to avoid overwhelming the server.
|
||||
*/
|
||||
object CameraChunkLoader {
|
||||
private const val MAX_CHUNKS_PER_TICK = 16
|
||||
|
||||
private data class CameraState(
|
||||
var dimension: ResourceKey<Level>,
|
||||
val chunks: MutableSet<Long>
|
||||
)
|
||||
|
||||
private data class ChunkRequest(
|
||||
val dimension: ResourceKey<Level>,
|
||||
val chunkPos: ChunkPos,
|
||||
val load: Boolean
|
||||
)
|
||||
|
||||
private val cameraStates = mutableMapOf<UUID, CameraState>()
|
||||
private val chunkOwners = mutableMapOf<ResourceKey<Level>, MutableMap<Long, MutableSet<UUID>>>()
|
||||
private val pendingRequests: ArrayDeque<ChunkRequest> = ArrayDeque()
|
||||
private val pendingAsyncLoads = mutableSetOf<Pair<ResourceKey<Level>, Long>>()
|
||||
|
||||
fun ensureArea(level: ServerLevel, cameraId: UUID, centerChunk: ChunkPos, radius: Int) {
|
||||
val dimension = level.dimension()
|
||||
val state = cameraStates[cameraId]?.takeIf { it.dimension == dimension } ?: run {
|
||||
cameraStates.remove(cameraId)?.let { oldState ->
|
||||
oldState.chunks.forEach { chunkLong ->
|
||||
removeOwnership(oldState.dimension, chunkLong, cameraId)
|
||||
}
|
||||
}
|
||||
|
||||
CameraState(dimension, mutableSetOf()).also { cameraStates[cameraId] = it }
|
||||
}
|
||||
|
||||
val newChunks = buildChunkSet(centerChunk, radius)
|
||||
val currentChunks = state.chunks
|
||||
|
||||
val toRemove = currentChunks.minus(newChunks)
|
||||
val toAdd = newChunks.minus(currentChunks)
|
||||
|
||||
if (toRemove.isNotEmpty()) {
|
||||
toRemove.forEach { chunkLong ->
|
||||
removeOwnership(dimension, chunkLong, cameraId)
|
||||
currentChunks.remove(chunkLong)
|
||||
}
|
||||
}
|
||||
|
||||
if (toAdd.isNotEmpty()) {
|
||||
toAdd.forEach { chunkLong ->
|
||||
addOwnership(dimension, chunkLong, cameraId)
|
||||
currentChunks.add(chunkLong)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clear(cameraId: UUID) {
|
||||
val state = cameraStates.remove(cameraId) ?: return
|
||||
val dimension = state.dimension
|
||||
state.chunks.forEach { chunkLong ->
|
||||
removeOwnership(dimension, chunkLong, cameraId)
|
||||
}
|
||||
}
|
||||
|
||||
fun tick(server: MinecraftServer) {
|
||||
if (pendingRequests.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
var processed = 0
|
||||
while (processed < MAX_CHUNKS_PER_TICK && pendingRequests.isNotEmpty()) {
|
||||
val request = pendingRequests.removeFirst()
|
||||
val level = server.getLevel(request.dimension) ?: continue
|
||||
|
||||
level.setChunkForced(request.chunkPos.x, request.chunkPos.z, request.load)
|
||||
|
||||
if (request.load) {
|
||||
scheduleAsyncLoad(level, request.dimension, request.chunkPos)
|
||||
}
|
||||
|
||||
processed++
|
||||
}
|
||||
}
|
||||
|
||||
private fun addOwnership(dimension: ResourceKey<Level>, chunkLong: Long, cameraId: UUID) {
|
||||
val ownersForDimension = chunkOwners.computeIfAbsent(dimension) { mutableMapOf() }
|
||||
val owners = ownersForDimension.computeIfAbsent(chunkLong) { mutableSetOf() }
|
||||
|
||||
if (owners.add(cameraId) && owners.size == 1) {
|
||||
pendingRequests.add(
|
||||
ChunkRequest(
|
||||
dimension,
|
||||
ChunkPos(chunkLong),
|
||||
true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeOwnership(dimension: ResourceKey<Level>, chunkLong: Long, cameraId: UUID) {
|
||||
val ownersForDimension = chunkOwners[dimension] ?: return
|
||||
val owners = ownersForDimension[chunkLong] ?: return
|
||||
|
||||
if (!owners.remove(cameraId)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (owners.isEmpty()) {
|
||||
ownersForDimension.remove(chunkLong)
|
||||
if (ownersForDimension.isEmpty()) {
|
||||
chunkOwners.remove(dimension)
|
||||
}
|
||||
|
||||
pendingRequests.add(
|
||||
ChunkRequest(
|
||||
dimension,
|
||||
ChunkPos(chunkLong),
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun scheduleAsyncLoad(level: ServerLevel, dimension: ResourceKey<Level>, chunkPos: ChunkPos) {
|
||||
val key = dimension to chunkPos.toLong()
|
||||
|
||||
if (!pendingAsyncLoads.add(key)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (Cobblemon.LOGGER.isDebugEnabled) {
|
||||
Cobblemon.LOGGER.debug(
|
||||
"[CobblemonCamera] Scheduling async load for chunk {} in {}",
|
||||
chunkPos,
|
||||
dimension.location()
|
||||
)
|
||||
}
|
||||
|
||||
level.chunkSource
|
||||
.getChunkFuture(chunkPos.x, chunkPos.z, ChunkStatus.FULL, true)
|
||||
.thenAcceptAsync({ result ->
|
||||
result.ifSuccess { chunkAccess ->
|
||||
if (chunkAccess is LevelChunk && Cobblemon.LOGGER.isDebugEnabled) {
|
||||
Cobblemon.LOGGER.debug(
|
||||
"[CobblemonCamera] Async loaded chunk {} (light={}, entities={})",
|
||||
chunkPos,
|
||||
chunkAccess.isLightCorrect,
|
||||
chunkAccess.blockEntities.size
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pendingAsyncLoads.remove(key)
|
||||
}, level.server)
|
||||
.exceptionally { throwable ->
|
||||
level.server.execute {
|
||||
pendingAsyncLoads.remove(key)
|
||||
Cobblemon.LOGGER.warn(
|
||||
"[CobblemonCamera] Exception while async loading chunk {}: {}",
|
||||
chunkPos,
|
||||
throwable.message
|
||||
)
|
||||
}
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildChunkSet(centerChunk: ChunkPos, radius: Int): MutableSet<Long> {
|
||||
val chunks = mutableSetOf<Long>()
|
||||
|
||||
for (x in centerChunk.x - radius..centerChunk.x + radius) {
|
||||
for (z in centerChunk.z - radius..centerChunk.z + radius) {
|
||||
chunks.add(ChunkPos.asLong(x, z))
|
||||
}
|
||||
}
|
||||
|
||||
return chunks
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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.camera
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.MoParams
|
||||
import com.bedrockk.molang.runtime.value.MoValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.camera.integration.server.CameraExecutor
|
||||
import com.cobblemon.mod.common.api.camera.integration.server.CameraInstance
|
||||
import com.cobblemon.mod.common.api.camera.integration.server.CameraNetworkSync
|
||||
import com.cobblemon.mod.common.api.camera.registry.TimelineRegistry
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import net.minecraft.core.SectionPos
|
||||
import net.minecraft.server.level.ChunkTrackingView
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.level.ChunkPos
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Server-side camera controller.
|
||||
*
|
||||
* Orchestrates camera instance, executor, network sync, and chunk/viewer management.
|
||||
* Delegates responsibilities to focused components.
|
||||
*/
|
||||
class CameraController(
|
||||
override val id: UUID = UUID.randomUUID(),
|
||||
val context: Map<String, MoValue> = emptyMap(),
|
||||
override val origin: Vec3 = Vec3.ZERO,
|
||||
val queryFunctions: Map<String, java.util.function.Function<MoParams, Any>> = emptyMap()
|
||||
) : ICameraController {
|
||||
private val instance: CameraInstance
|
||||
private val runtime: MoLangRuntime
|
||||
private lateinit var executor: CameraExecutor
|
||||
private lateinit var networkSync: CameraNetworkSync
|
||||
|
||||
private val viewers = mutableSetOf<UUID>()
|
||||
private val viewerChunkStates = mutableMapOf<UUID, ViewerChunkState>()
|
||||
private var level: ServerLevel? = null
|
||||
|
||||
init {
|
||||
runtime = MoLangRuntime().setup()
|
||||
runtime.environment.query.functions.putAll(queryFunctions)
|
||||
instance = CameraInstance(id, origin, context, queryFunctions)
|
||||
executor = CameraExecutor(instance, runtime, context)
|
||||
networkSync = CameraNetworkSync(id, viewers)
|
||||
Cobblemon.LOGGER.debug("[CameraController] Initialized $id")
|
||||
}
|
||||
|
||||
private data class ViewerChunkState(
|
||||
val viewDistance: Int,
|
||||
var centerChunk: ChunkPos,
|
||||
var chunkView: ChunkTrackingView
|
||||
)
|
||||
|
||||
override fun startTimeline(timelineId: String): Boolean {
|
||||
val timeline = TimelineRegistry.getTimeline(timelineId)
|
||||
if (timeline == null) {
|
||||
Cobblemon.LOGGER.error("[CameraController] Timeline '$timelineId' not found")
|
||||
return false
|
||||
}
|
||||
|
||||
return if (instance.startTimeline(timeline)) {
|
||||
Cobblemon.LOGGER.info("[CameraController] Started '$timelineId'")
|
||||
true
|
||||
} else {
|
||||
Cobblemon.LOGGER.error("[CameraController] Engine start failed for '$timelineId'")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun tick() {
|
||||
val currentLevel = level ?: return
|
||||
val stillActive = executor.tick(currentLevel, networkSync) { chunkPos ->
|
||||
updateViewerChunkViews(chunkPos)
|
||||
}
|
||||
|
||||
// If timeline ended, stop the camera and notify all viewers
|
||||
if (!stillActive && viewers.isNotEmpty()) {
|
||||
Cobblemon.LOGGER.info("[CameraController] Timeline ended naturally, stopping camera")
|
||||
stop()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateViewerChunkViews(chunkPos: ChunkPos) {
|
||||
viewerChunkStates.forEach { (viewerId, state) ->
|
||||
if (state.centerChunk == chunkPos) return@forEach
|
||||
|
||||
state.centerChunk = chunkPos
|
||||
val newView = ChunkTrackingView.of(chunkPos, state.viewDistance)
|
||||
state.chunkView = newView
|
||||
CameraManager.registerViewerChunkView(viewerId, newView)
|
||||
}
|
||||
|
||||
val serverLevel = level
|
||||
if (serverLevel != null && viewerChunkStates.isNotEmpty()) {
|
||||
val maxDistance = viewerChunkStates.values.maxOf { it.viewDistance }
|
||||
CameraChunkLoader.ensureArea(serverLevel, id, chunkPos, maxDistance)
|
||||
}
|
||||
}
|
||||
|
||||
override fun addViewer(player: ServerPlayer) {
|
||||
viewers.add(player.uuid)
|
||||
registerViewerChunkView(player, origin)
|
||||
networkSync.sendStart(origin)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a player as a feed viewer without sending CameraStartPacket.
|
||||
*
|
||||
* The player receives transform/focus updates like a normal viewer, but
|
||||
* the camera will NOT become the primary camera on the client. This is
|
||||
* used for secondary cameras (e.g. Camera B in split-screen) that render
|
||||
* to an off-screen RenderTarget via CameraFeedHandler.
|
||||
*/
|
||||
fun addFeedViewer(player: ServerPlayer) {
|
||||
viewers.add(player.uuid)
|
||||
registerViewerChunkView(player, origin)
|
||||
// No sendStart() — intentionally skipped so the client doesn't set this as primary
|
||||
}
|
||||
|
||||
private fun registerViewerChunkView(player: ServerPlayer, centerPosition: Vec3) {
|
||||
val playerLevel = player.serverLevel()
|
||||
if (level == null || level != playerLevel) {
|
||||
level?.let { CameraChunkLoader.clear(id) }
|
||||
level = playerLevel
|
||||
}
|
||||
|
||||
val maxViewDistance = player.server.playerList.viewDistance
|
||||
val viewDistance = Mth.clamp(player.requestedViewDistance(), 2, maxViewDistance)
|
||||
val chunkPos = chunkPosFrom(centerPosition)
|
||||
val chunkView = ChunkTrackingView.of(chunkPos, viewDistance)
|
||||
|
||||
viewerChunkStates[player.uuid] = ViewerChunkState(viewDistance, chunkPos, chunkView)
|
||||
CameraManager.registerViewerChunkView(player.uuid, chunkView)
|
||||
|
||||
val maxDistance = viewerChunkStates.values.maxOfOrNull { it.viewDistance } ?: viewDistance
|
||||
CameraChunkLoader.ensureArea(playerLevel, id, chunkPos, maxDistance)
|
||||
}
|
||||
|
||||
override fun removeViewer(player: ServerPlayer) {
|
||||
// Send end packet to THIS player specifically
|
||||
networkSync.sendEndToPlayer(player)
|
||||
|
||||
viewers.remove(player.uuid)
|
||||
viewerChunkStates.remove(player.uuid)
|
||||
CameraManager.clearViewerChunkView(player.uuid)
|
||||
|
||||
if (viewers.isEmpty()) {
|
||||
CameraChunkLoader.clear(id)
|
||||
level = null
|
||||
} else {
|
||||
val serverLevel = level
|
||||
if (serverLevel != null && viewerChunkStates.isNotEmpty()) {
|
||||
val state = viewerChunkStates.values.first()
|
||||
val maxDistance = viewerChunkStates.values.maxOf { it.viewDistance }
|
||||
CameraChunkLoader.ensureArea(serverLevel, id, state.centerChunk, maxDistance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
instance.stop()
|
||||
networkSync.sendEnd()
|
||||
viewerChunkStates.keys.toList().forEach { CameraManager.clearViewerChunkView(it) }
|
||||
viewerChunkStates.clear()
|
||||
CameraChunkLoader.clear(id)
|
||||
level = null
|
||||
viewers.clear()
|
||||
}
|
||||
|
||||
override fun isActive() = instance.isActive()
|
||||
|
||||
fun setFlag(name: String, value: Boolean) = setVariable(name, if (value) 1.0 else 0.0)
|
||||
|
||||
override fun setVariable(name: String, value: Double) = instance.setVariable(name, value)
|
||||
|
||||
override fun setFocusOverride(position: Vec3?) {
|
||||
instance.focusOverride = position
|
||||
}
|
||||
|
||||
override fun triggerInstantTransition() = instance.triggerInstantTransition()
|
||||
|
||||
override fun addQueryFunctions(functions: Map<String, java.util.function.Function<MoParams, Any>>) {
|
||||
runtime.environment.query.functions.putAll(functions)
|
||||
instance.addQueryFunctions(functions)
|
||||
}
|
||||
|
||||
private fun chunkPosFrom(position: Vec3): ChunkPos {
|
||||
val chunkX = SectionPos.blockToSectionCoord(Mth.floor(position.x))
|
||||
val chunkZ = SectionPos.blockToSectionCoord(Mth.floor(position.z))
|
||||
return ChunkPos(chunkX, chunkZ)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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.camera
|
||||
|
||||
import com.bedrockk.molang.runtime.MoParams
|
||||
import com.bedrockk.molang.runtime.value.MoValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.camera.components.CameraChunkManager
|
||||
import com.cobblemon.mod.common.api.camera.components.CameraLifecycleManager
|
||||
import com.cobblemon.mod.common.api.camera.components.CameraViewerManager
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.camera.CameraStartedEvent
|
||||
import com.cobblemon.mod.common.api.events.camera.CameraStoppedEvent
|
||||
import com.cobblemon.mod.common.api.events.camera.CameraViewerAddedEvent
|
||||
import com.cobblemon.mod.common.api.events.camera.CameraViewerRemovedEvent
|
||||
import com.cobblemon.mod.common.battles.stadium.StadiumBattleCameraCoordinator
|
||||
import net.minecraft.server.level.ChunkTrackingView
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.UUID
|
||||
import java.util.function.Function
|
||||
|
||||
object CameraManager : ICameraManager {
|
||||
|
||||
private val lifecycleManager = CameraLifecycleManager()
|
||||
private val viewerManager = CameraViewerManager()
|
||||
private val chunkManager = CameraChunkManager()
|
||||
|
||||
override fun startCamera(
|
||||
timelineId: String,
|
||||
context: Map<String, MoValue>,
|
||||
viewers: List<ServerPlayer>,
|
||||
origin: Vec3,
|
||||
queryFunctions: Map<String, Function<MoParams, Any>>
|
||||
): UUID? {
|
||||
val camera = CameraController(
|
||||
context = context,
|
||||
origin = origin,
|
||||
queryFunctions = queryFunctions
|
||||
)
|
||||
|
||||
val preEvent = CameraStartedEvent.Pre(camera.id, camera, timelineId, viewers)
|
||||
CobblemonEvents.CAMERA_STARTED_PRE.emit(preEvent)
|
||||
if (preEvent.isCanceled) {
|
||||
Cobblemon.LOGGER.info("[CobblemonCamera] Camera start canceled by event listener")
|
||||
return null
|
||||
}
|
||||
|
||||
if (!camera.startTimeline(timelineId)) {
|
||||
Cobblemon.LOGGER.warn("[CobblemonCamera] Failed to start timeline '$timelineId'")
|
||||
return null
|
||||
}
|
||||
|
||||
lifecycleManager.registerCamera(camera)
|
||||
|
||||
viewers.forEach { player ->
|
||||
camera.addViewer(player)
|
||||
viewerManager.assignViewerToCamera(player.uuid, camera.id)
|
||||
|
||||
CobblemonEvents.CAMERA_VIEWER_ADDED.emit(
|
||||
CameraViewerAddedEvent(camera.id, camera, player)
|
||||
)
|
||||
}
|
||||
|
||||
CobblemonEvents.CAMERA_STARTED_POST.emit(
|
||||
CameraStartedEvent.Post(camera.id, camera, timelineId, viewers)
|
||||
)
|
||||
|
||||
Cobblemon.LOGGER.info("[CobblemonCamera] Started camera ${camera.id} with timeline '$timelineId' for ${viewers.size} viewers")
|
||||
return camera.id
|
||||
}
|
||||
|
||||
override fun addViewerToCamera(player: ServerPlayer, cameraId: UUID): Boolean {
|
||||
val camera = lifecycleManager.getCamera(cameraId) ?: return false
|
||||
|
||||
camera.addViewer(player)
|
||||
viewerManager.assignViewerToCamera(player.uuid, camera.id)
|
||||
|
||||
CobblemonEvents.CAMERA_VIEWER_ADDED.emit(
|
||||
CameraViewerAddedEvent(camera.id, camera, player)
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun stopCamera(cameraId: UUID) {
|
||||
lifecycleManager.getCamera(cameraId)?.let { camera ->
|
||||
CobblemonEvents.CAMERA_STOPPED_PRE.emit(
|
||||
CameraStoppedEvent.Pre(cameraId, camera, CameraStoppedEvent.StopReason.MANUAL)
|
||||
)
|
||||
|
||||
camera.stop()
|
||||
lifecycleManager.unregisterCamera(cameraId)
|
||||
viewerManager.removeViewersForCamera(cameraId)
|
||||
|
||||
CobblemonEvents.CAMERA_STOPPED_POST.emit(
|
||||
CameraStoppedEvent.Post(cameraId, camera, CameraStoppedEvent.StopReason.MANUAL)
|
||||
)
|
||||
|
||||
Cobblemon.LOGGER.info("[CobblemonCamera] Stopped camera $cameraId")
|
||||
}
|
||||
}
|
||||
|
||||
override fun stopCameraForPlayer(player: ServerPlayer) {
|
||||
viewerManager.getCameraForPlayer(player.uuid)?.let { cameraId ->
|
||||
lifecycleManager.getCamera(cameraId)?.let { camera ->
|
||||
camera.removeViewer(player)
|
||||
viewerManager.removeViewer(player.uuid)
|
||||
|
||||
CobblemonEvents.CAMERA_VIEWER_REMOVED.emit(
|
||||
CameraViewerRemovedEvent(cameraId, camera, player)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPlayerCamera(player: ServerPlayer): ICameraController? {
|
||||
val cameraId = viewerManager.getCameraForPlayer(player.uuid) ?: return null
|
||||
return lifecycleManager.getCamera(cameraId)
|
||||
}
|
||||
|
||||
override fun getCamera(cameraId: UUID): ICameraController? {
|
||||
return lifecycleManager.getCamera(cameraId)
|
||||
}
|
||||
|
||||
override fun tick() {
|
||||
lifecycleManager.tick()
|
||||
StadiumBattleCameraCoordinator.tickSecondaryCameras()
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
lifecycleManager.clear()
|
||||
viewerManager.clear()
|
||||
chunkManager.clear()
|
||||
}
|
||||
|
||||
internal fun registerViewerChunkView(playerId: UUID, newView: ChunkTrackingView) {
|
||||
chunkManager.registerViewerChunkView(playerId, newView)
|
||||
}
|
||||
|
||||
internal fun clearViewerChunkView(playerId: UUID) {
|
||||
chunkManager.clearViewerChunkView(playerId)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getPlayerChunkView(player: ServerPlayer): ChunkTrackingView? {
|
||||
return chunkManager.getPlayerChunkView(player.uuid)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun consumeChunkViewUpdate(player: ServerPlayer): ChunkViewUpdate? {
|
||||
return chunkManager.consumeChunkViewUpdate(player.uuid)?.let {
|
||||
ChunkViewUpdate(it.previous, it.current)
|
||||
}
|
||||
}
|
||||
|
||||
data class ChunkViewUpdate(val previous: ChunkTrackingView?, val current: ChunkTrackingView?)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.camera
|
||||
|
||||
/**
|
||||
* Represents a camera rotation in 3D space.
|
||||
*
|
||||
* @property yaw Horizontal rotation (left/right) in degrees
|
||||
* @property pitch Vertical rotation (up/down) in degrees
|
||||
*/
|
||||
data class CameraRotation(
|
||||
val yaw: Float,
|
||||
val pitch: Float
|
||||
)
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.camera
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.camera.model.CameraTrack
|
||||
import com.google.gson.JsonObject
|
||||
|
||||
object CameraTrackRegistry {
|
||||
private val trackDeserializers = mutableMapOf<String, (JsonObject) -> CameraTrack>()
|
||||
|
||||
fun registerTrackType(trackType: String, deserializer: (JsonObject) -> CameraTrack) {
|
||||
if (trackDeserializers.containsKey(trackType)) {
|
||||
Cobblemon.LOGGER.warn("[CameraTrackRegistry] Track type '$trackType' is already registered, overwriting")
|
||||
}
|
||||
trackDeserializers[trackType] = deserializer
|
||||
Cobblemon.LOGGER.debug("[CameraTrackRegistry] Registered camera track type: $trackType")
|
||||
}
|
||||
|
||||
fun deserializeTrack(trackType: String, json: JsonObject): CameraTrack? {
|
||||
val deserializer = trackDeserializers[trackType]
|
||||
if (deserializer == null) {
|
||||
Cobblemon.LOGGER.warn("[CameraTrackRegistry] Unknown track type: $trackType")
|
||||
return null
|
||||
}
|
||||
return try {
|
||||
deserializer(json)
|
||||
} catch (e: Exception) {
|
||||
Cobblemon.LOGGER.error("[CameraTrackRegistry] Failed to deserialize track type '$trackType': ${e.message}", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun getRegisteredTrackTypes(): Set<String> = trackDeserializers.keys.toSet()
|
||||
|
||||
fun isTrackTypeRegistered(trackType: String): Boolean = trackDeserializers.containsKey(trackType)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.camera
|
||||
|
||||
import com.bedrockk.molang.runtime.MoParams
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.UUID
|
||||
import java.util.function.Function
|
||||
|
||||
interface ICameraController {
|
||||
val id: UUID
|
||||
val origin: Vec3
|
||||
fun startTimeline(timelineId: String): Boolean
|
||||
fun tick()
|
||||
fun stop()
|
||||
fun isActive(): Boolean
|
||||
fun addViewer(player: ServerPlayer)
|
||||
fun removeViewer(player: ServerPlayer)
|
||||
fun setVariable(name: String, value: Double)
|
||||
fun setFocusOverride(position: Vec3?)
|
||||
/** Triggers an instant camera transition (no client-side interpolation) on the next tick. */
|
||||
fun triggerInstantTransition()
|
||||
/** Injects additional MoLang query functions into the camera's runtime after creation. */
|
||||
fun addQueryFunctions(functions: Map<String, Function<MoParams, Any>>)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.camera
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
interface ICameraCoordinator {
|
||||
fun bind(cameraId: UUID)
|
||||
fun release()
|
||||
fun getCameraId(): UUID?
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.camera
|
||||
|
||||
import com.bedrockk.molang.runtime.MoParams
|
||||
import com.bedrockk.molang.runtime.value.MoValue
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.UUID
|
||||
import java.util.function.Function
|
||||
|
||||
interface ICameraManager {
|
||||
fun startCamera(
|
||||
timelineId: String,
|
||||
context: Map<String, MoValue> = emptyMap(),
|
||||
viewers: List<ServerPlayer> = emptyList(),
|
||||
origin: Vec3 = Vec3.ZERO,
|
||||
queryFunctions: Map<String, Function<MoParams, Any>> = emptyMap()
|
||||
): UUID?
|
||||
|
||||
fun addViewerToCamera(player: ServerPlayer, cameraId: UUID): Boolean
|
||||
fun stopCamera(cameraId: UUID)
|
||||
fun stopCameraForPlayer(player: ServerPlayer)
|
||||
fun getPlayerCamera(player: ServerPlayer): ICameraController?
|
||||
fun getCamera(cameraId: UUID): ICameraController?
|
||||
fun tick()
|
||||
fun clear()
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.camera.components
|
||||
|
||||
import net.minecraft.server.level.ChunkTrackingView
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class CameraChunkManager {
|
||||
private val playerChunkViews = ConcurrentHashMap<UUID, ChunkTrackingView>()
|
||||
private val chunkViewUpdates = ConcurrentHashMap<UUID, ChunkViewUpdate>()
|
||||
|
||||
data class ChunkViewUpdate(val previous: ChunkTrackingView?, val current: ChunkTrackingView?)
|
||||
|
||||
fun registerViewerChunkView(playerId: UUID, newView: ChunkTrackingView) {
|
||||
val previous = playerChunkViews.put(playerId, newView)
|
||||
|
||||
if (previous == null || previous != newView) {
|
||||
chunkViewUpdates[playerId] = ChunkViewUpdate(previous, newView)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearViewerChunkView(playerId: UUID) {
|
||||
val previous = playerChunkViews.remove(playerId)
|
||||
|
||||
if (previous != null) {
|
||||
chunkViewUpdates[playerId] = ChunkViewUpdate(previous, null)
|
||||
}
|
||||
}
|
||||
|
||||
fun getPlayerChunkView(playerId: UUID): ChunkTrackingView? = playerChunkViews[playerId]
|
||||
|
||||
fun consumeChunkViewUpdate(playerId: UUID): ChunkViewUpdate? = chunkViewUpdates.remove(playerId)
|
||||
|
||||
fun clear() {
|
||||
playerChunkViews.clear()
|
||||
chunkViewUpdates.clear()
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.camera.components
|
||||
|
||||
import com.cobblemon.mod.common.api.camera.ICameraController
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.camera.CameraStoppedEvent
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class CameraLifecycleManager {
|
||||
private val cameras = ConcurrentHashMap<UUID, ICameraController>()
|
||||
|
||||
fun registerCamera(camera: ICameraController) {
|
||||
cameras[camera.id] = camera
|
||||
}
|
||||
|
||||
fun unregisterCamera(cameraId: UUID): ICameraController? {
|
||||
return cameras.remove(cameraId)
|
||||
}
|
||||
|
||||
fun getCamera(cameraId: UUID): ICameraController? = cameras[cameraId]
|
||||
|
||||
fun getAllCameras(): Collection<ICameraController> = cameras.values
|
||||
|
||||
fun tick() {
|
||||
cameras.values.forEach { it.tick() }
|
||||
|
||||
cameras.entries.removeIf { (id, camera) ->
|
||||
if (!camera.isActive()) {
|
||||
CobblemonEvents.CAMERA_STOPPED_POST.emit(
|
||||
CameraStoppedEvent.Post(id, camera, CameraStoppedEvent.StopReason.COMPLETED)
|
||||
)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
cameras.values.forEach {
|
||||
it.stop()
|
||||
CobblemonEvents.CAMERA_STOPPED_POST.emit(
|
||||
CameraStoppedEvent.Post(it.id, it, CameraStoppedEvent.StopReason.MANUAL)
|
||||
)
|
||||
}
|
||||
cameras.clear()
|
||||
}
|
||||
|
||||
fun size(): Int = cameras.size
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.camera.components
|
||||
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class CameraViewerManager {
|
||||
private val playerCameras = ConcurrentHashMap<UUID, UUID>()
|
||||
|
||||
fun assignViewerToCamera(playerId: UUID, cameraId: UUID) {
|
||||
playerCameras[playerId] = cameraId
|
||||
}
|
||||
|
||||
fun removeViewer(playerId: UUID): UUID? {
|
||||
return playerCameras.remove(playerId)
|
||||
}
|
||||
|
||||
fun getCameraForPlayer(playerId: UUID): UUID? {
|
||||
return playerCameras[playerId]
|
||||
}
|
||||
|
||||
fun removeViewersForCamera(cameraId: UUID) {
|
||||
playerCameras.entries.removeIf { it.value == cameraId }
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
playerCameras.clear()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,591 @@
|
||||
/*
|
||||
* 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.camera.engine
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.bedrockk.molang.runtime.value.MoValue
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.camera.CameraRotation
|
||||
import com.cobblemon.mod.common.api.camera.model.*
|
||||
import com.cobblemon.mod.common.api.camera.result.ValidationError
|
||||
import com.cobblemon.mod.common.api.camera.track.EventTrack
|
||||
import com.cobblemon.mod.common.api.camera.track.FiredGuiAction
|
||||
import com.cobblemon.mod.common.api.camera.track.GuiTrack
|
||||
import com.cobblemon.mod.common.api.tween.EasingRegistry
|
||||
import com.cobblemon.mod.common.client.camera.CameraInterpolationUtils
|
||||
import com.cobblemon.mod.common.util.resolve
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* Thread-safe timeline execution engine.
|
||||
*
|
||||
* Uses immutable state snapshots for thread safety. MoLang evaluation is NOT
|
||||
* thread-safe - callers must ensure tick() is called from the same thread.
|
||||
*
|
||||
* @param id Unique identifier for this engine instance
|
||||
* @param timeline The timeline to execute
|
||||
* @param origin The world position origin for relative coordinates
|
||||
* @param runtime The MoLang runtime for expression evaluation
|
||||
* @param providedContext Initial context values (parameter values) to inject into MoLang
|
||||
*/
|
||||
class TimelineEngine(
|
||||
val id: UUID,
|
||||
private val timeline: Timeline,
|
||||
val origin: Vec3,
|
||||
private val runtime: MoLangRuntime,
|
||||
private val providedContext: Map<String, MoValue> = emptyMap()
|
||||
) {
|
||||
@Volatile
|
||||
private var state: ExecutionState = ExecutionState.Stopped
|
||||
|
||||
private var previousExecutionState: ExecutionState.Playing? = null
|
||||
|
||||
private var instantTransitionOccurred = false
|
||||
|
||||
private val customVariables = ConcurrentHashMap<String, Double>()
|
||||
|
||||
/** GUI actions fired during the current tick, consumed by the executor. */
|
||||
private val pendingGuiActions = mutableListOf<FiredGuiAction>()
|
||||
|
||||
private val timeOnCurrentPathValue = DoubleValue(0.0)
|
||||
private val totalTimeValue = DoubleValue(0.0)
|
||||
private val runnerIdValue = StringValue(id.toString())
|
||||
|
||||
fun start(): Boolean {
|
||||
if (state !is ExecutionState.Stopped) {
|
||||
Cobblemon.LOGGER.warn("[TimelineEngine] Cannot start: already active (id=$id)")
|
||||
return false
|
||||
}
|
||||
|
||||
val errors = timeline.validate()
|
||||
if (errors.any { it.severity == ValidationError.Severity.ERROR }) {
|
||||
Cobblemon.LOGGER.error("[TimelineEngine] Timeline '${timeline.id}' validation failed: ${errors.joinToString { it.toString() }}")
|
||||
return false
|
||||
}
|
||||
|
||||
if (!validateAndInjectParameters()) {
|
||||
return false
|
||||
}
|
||||
|
||||
val startState = timeline.getState(timeline.startStateId)
|
||||
if (startState == null) {
|
||||
Cobblemon.LOGGER.error("[TimelineEngine] Start state '${timeline.startStateId}' not found in timeline '${timeline.id}'")
|
||||
return false
|
||||
}
|
||||
|
||||
state = ExecutionState.Playing(
|
||||
currentState = startState,
|
||||
stateTime = 0f,
|
||||
totalTime = 0f,
|
||||
lastTickNanos = System.nanoTime(),
|
||||
transitionState = TransitionState.None,
|
||||
progressMemory = emptyMap(),
|
||||
executedEvents = emptySet()
|
||||
)
|
||||
|
||||
Cobblemon.LOGGER.info("[TimelineEngine] Started timeline '${timeline.id}' in state '${startState.id}'")
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that all required parameters are provided and injects parameters
|
||||
* (including defaults) into the MoLang runtime.
|
||||
*/
|
||||
private fun validateAndInjectParameters(): Boolean {
|
||||
if (timeline.parameters.isEmpty()) return true
|
||||
|
||||
val providedVarNames = providedContext.keys
|
||||
val missingParams = timeline.validateParameters(providedVarNames)
|
||||
if (missingParams.isNotEmpty()) {
|
||||
Cobblemon.LOGGER.error("[TimelineEngine] Timeline '${timeline.id}' missing required parameters: ${missingParams.joinToString(", ")}")
|
||||
return false
|
||||
}
|
||||
|
||||
providedContext.forEach { (name, value) ->
|
||||
runtime.environment.variable.map[name] = value
|
||||
}
|
||||
|
||||
for ((paramName, definition) in timeline.parameters) {
|
||||
val default = definition.default ?: continue
|
||||
|
||||
when (default) {
|
||||
is ParameterDefault.NumberDefault -> {
|
||||
if (paramName !in providedVarNames) {
|
||||
runtime.environment.variable.map[paramName] = DoubleValue(default.value)
|
||||
Cobblemon.LOGGER.debug("[TimelineEngine] Using default for parameter '$paramName': ${default.value}")
|
||||
}
|
||||
}
|
||||
is ParameterDefault.Vec3Default -> {
|
||||
val varNames = definition.getVariableNames(paramName)
|
||||
val xVar = varNames[0]
|
||||
val yVar = varNames[1]
|
||||
val zVar = varNames[2]
|
||||
|
||||
if (xVar !in providedVarNames) {
|
||||
runtime.environment.variable.map[xVar] = DoubleValue(default.x)
|
||||
}
|
||||
if (yVar !in providedVarNames) {
|
||||
runtime.environment.variable.map[yVar] = DoubleValue(default.y)
|
||||
}
|
||||
if (zVar !in providedVarNames) {
|
||||
runtime.environment.variable.map[zVar] = DoubleValue(default.z)
|
||||
}
|
||||
|
||||
if (xVar !in providedVarNames || yVar !in providedVarNames || zVar !in providedVarNames) {
|
||||
Cobblemon.LOGGER.debug("[TimelineEngine] Using default for parameter '$paramName': (${default.x}, ${default.y}, ${default.z})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun tick(): CameraTransform? {
|
||||
val current = state
|
||||
if (current !is ExecutionState.Playing) {
|
||||
return null
|
||||
}
|
||||
|
||||
previousExecutionState = current
|
||||
instantTransitionOccurred = false
|
||||
|
||||
val nowNanos = System.nanoTime()
|
||||
val deltaNanos = nowNanos - current.lastTickNanos
|
||||
val deltaSeconds = (deltaNanos / 1_000_000_000.0).toFloat()
|
||||
.coerceIn(0f, 0.5f)
|
||||
|
||||
val newStateTime = current.stateTime + deltaSeconds * current.currentState.speed
|
||||
val newTotalTime = current.totalTime + deltaSeconds
|
||||
|
||||
val transitionResult = checkTransitions(current, newStateTime, newTotalTime)
|
||||
if (transitionResult is TransitionCheckResult.TransitionNow) {
|
||||
val targetState = timeline.getState(transitionResult.targetStateId)
|
||||
?: run {
|
||||
Cobblemon.LOGGER.error("[TimelineEngine] Transition target state '${transitionResult.targetStateId}' not found")
|
||||
return null
|
||||
}
|
||||
|
||||
state = when (transitionResult.type) {
|
||||
TransitionType.INSTANT -> {
|
||||
instantTransitionOccurred = true
|
||||
|
||||
val savedSnapshot = if (targetState.rememberProgress) {
|
||||
current.progressMemory[targetState.id]
|
||||
} else null
|
||||
|
||||
val restoredTime = savedSnapshot?.normalizedProgress?.let {
|
||||
it * targetState.getEffectiveDuration()
|
||||
} ?: 0f
|
||||
|
||||
val restoredFocus = savedSnapshot?.lastFocus
|
||||
|
||||
val currentSnapshot = StateSnapshot(
|
||||
normalizedProgress = normalizeProgress(current.stateTime, current.currentState),
|
||||
lastFocus = current.lastEvaluatedFocus
|
||||
)
|
||||
|
||||
ExecutionState.Playing(
|
||||
currentState = targetState,
|
||||
stateTime = restoredTime,
|
||||
totalTime = newTotalTime,
|
||||
lastTickNanos = nowNanos,
|
||||
transitionState = TransitionState.None,
|
||||
progressMemory = current.progressMemory + (current.currentState.id to currentSnapshot),
|
||||
executedEvents = if (targetState.rememberProgress) current.executedEvents else emptySet(),
|
||||
lastEvaluatedFocus = restoredFocus
|
||||
)
|
||||
}
|
||||
TransitionType.BLEND -> {
|
||||
val currentTransform = evaluateState(current.currentState, current.stateTime)
|
||||
ExecutionState.Playing(
|
||||
currentState = current.currentState,
|
||||
stateTime = newStateTime,
|
||||
totalTime = newTotalTime,
|
||||
lastTickNanos = nowNanos,
|
||||
transitionState = TransitionState.Blending(
|
||||
targetState = targetState,
|
||||
startTime = newTotalTime,
|
||||
duration = transitionResult.blendDuration,
|
||||
startTransform = currentTransform
|
||||
),
|
||||
progressMemory = current.progressMemory,
|
||||
executedEvents = current.executedEvents
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val effectiveDuration = current.currentState.getEffectiveDuration()
|
||||
val (finalStateTime, finalEvents) = if (current.currentState.isLooping && effectiveDuration > 0f && newStateTime >= effectiveDuration) {
|
||||
newStateTime % effectiveDuration to emptySet<EventKey>()
|
||||
} else {
|
||||
val clampedTime = if (!current.currentState.isLooping && effectiveDuration > 0f) {
|
||||
newStateTime.coerceAtMost(effectiveDuration)
|
||||
} else {
|
||||
newStateTime
|
||||
}
|
||||
clampedTime to current.executedEvents
|
||||
}
|
||||
|
||||
state = current.copy(
|
||||
stateTime = finalStateTime,
|
||||
totalTime = newTotalTime,
|
||||
lastTickNanos = nowNanos,
|
||||
executedEvents = finalEvents
|
||||
)
|
||||
}
|
||||
|
||||
val updatedState = state
|
||||
if (updatedState is ExecutionState.Playing && updatedState.transitionState is TransitionState.Blending) {
|
||||
val blend = updatedState.transitionState
|
||||
val blendProgress = if (blend.duration > 0f) {
|
||||
((updatedState.totalTime - blend.startTime) / blend.duration).coerceIn(0f, 1f)
|
||||
} else 1f
|
||||
|
||||
if (blendProgress >= 1f) {
|
||||
val savedSnapshot = updatedState.progressMemory[blend.targetState.id]
|
||||
val restoredTime = if (blend.targetState.rememberProgress && savedSnapshot != null) {
|
||||
savedSnapshot.normalizedProgress * blend.targetState.getEffectiveDuration()
|
||||
} else 0f
|
||||
|
||||
val currentSnapshot = StateSnapshot(
|
||||
normalizedProgress = normalizeProgress(updatedState.stateTime, updatedState.currentState),
|
||||
lastFocus = null
|
||||
)
|
||||
|
||||
state = ExecutionState.Playing(
|
||||
currentState = blend.targetState,
|
||||
stateTime = restoredTime,
|
||||
totalTime = updatedState.totalTime,
|
||||
lastTickNanos = nowNanos,
|
||||
transitionState = TransitionState.None,
|
||||
progressMemory = updatedState.progressMemory + (updatedState.currentState.id to currentSnapshot),
|
||||
executedEvents = if (blend.targetState.rememberProgress) updatedState.executedEvents else emptySet()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
executeEvents()
|
||||
executeGuiActions()
|
||||
return calculateTransform()
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
state = ExecutionState.Stopped
|
||||
customVariables.clear()
|
||||
|
||||
// Clear MoLang runtime to prevent memory leaks
|
||||
runtime.environment.variable.map.clear()
|
||||
runtime.environment.query.functions.clear()
|
||||
}
|
||||
|
||||
fun setVariable(name: String, value: Double) {
|
||||
customVariables[name] = value
|
||||
}
|
||||
|
||||
/** Triggers an instant transition on the next update (no client-side interpolation). */
|
||||
fun triggerInstantTransition() {
|
||||
instantTransitionOccurred = true
|
||||
}
|
||||
|
||||
fun isActive(): Boolean = state is ExecutionState.Playing
|
||||
|
||||
fun getCurrentStateId(): String? = (state as? ExecutionState.Playing)?.currentState?.id
|
||||
|
||||
fun getCurrentRenderPlayerBody(): Boolean = (state as? ExecutionState.Playing)?.currentState?.renderPlayerBody ?: true
|
||||
|
||||
fun getCurrentHiddenHudElements(): Set<HudElement> = (state as? ExecutionState.Playing)?.currentState?.hiddenHudElements ?: emptySet()
|
||||
|
||||
fun consumeInstantTransitionFlag(): Boolean {
|
||||
val result = instantTransitionOccurred
|
||||
instantTransitionOccurred = false
|
||||
return result
|
||||
}
|
||||
|
||||
private fun checkTransitions(
|
||||
current: ExecutionState.Playing,
|
||||
newStateTime: Float,
|
||||
newTotalTime: Float
|
||||
): TransitionCheckResult {
|
||||
if (current.transitionState !is TransitionState.None) {
|
||||
return TransitionCheckResult.NoTransition
|
||||
}
|
||||
|
||||
val context = createMoLangContext(current.currentState, newStateTime, newTotalTime)
|
||||
|
||||
for (transition in timeline.getTransitionsFrom(current.currentState.id)) {
|
||||
try {
|
||||
val shouldTransition = runtime.resolveBoolean(transition.condition, context)
|
||||
if (shouldTransition) {
|
||||
return TransitionCheckResult.TransitionNow(
|
||||
targetStateId = transition.targetStateId,
|
||||
type = transition.type,
|
||||
blendDuration = transition.blendDuration
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Cobblemon.LOGGER.error("[TimelineEngine] Error evaluating transition condition: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
return TransitionCheckResult.NoTransition
|
||||
}
|
||||
|
||||
private fun executeEvents() {
|
||||
val current = state as? ExecutionState.Playing ?: return
|
||||
val previousState = previousExecutionState ?: return
|
||||
|
||||
val context = createMoLangContext(current.currentState, current.stateTime, current.totalTime)
|
||||
val previousTime = previousState.stateTime
|
||||
val currentTime = current.stateTime
|
||||
|
||||
current.currentState.tracks.filterIsInstance<EventTrack>().forEach { track ->
|
||||
track.events.forEach { timedEvent ->
|
||||
val eventKey = EventKey(current.currentState.id, timedEvent.eventId, timedEvent.time.toInt())
|
||||
|
||||
if (eventKey in current.executedEvents) return@forEach
|
||||
|
||||
val crossedEvent = if (previousTime <= currentTime) {
|
||||
timedEvent.time in previousTime..currentTime
|
||||
} else {
|
||||
timedEvent.time <= currentTime
|
||||
}
|
||||
|
||||
if (crossedEvent) {
|
||||
try {
|
||||
runtime.resolve(timedEvent.expression, context)
|
||||
|
||||
state = current.copy(
|
||||
executedEvents = current.executedEvents + eventKey
|
||||
)
|
||||
|
||||
if (timedEvent.eventId != null) {
|
||||
Cobblemon.LOGGER.debug("[TimelineEngine] Executed event '${timedEvent.eventId}' at time ${timedEvent.time}s")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Cobblemon.LOGGER.error("[TimelineEngine] Failed to execute event: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks GUI tracks for actions whose time has been crossed and queues them.
|
||||
* Follows the same pattern as [executeEvents].
|
||||
*/
|
||||
private fun executeGuiActions() {
|
||||
val current = state as? ExecutionState.Playing ?: return
|
||||
val previousState = previousExecutionState ?: return
|
||||
|
||||
val previousTime = previousState.stateTime
|
||||
val currentTime = current.stateTime
|
||||
|
||||
current.currentState.tracks.filterIsInstance<GuiTrack>().forEach { track ->
|
||||
track.actions.forEach { guiAction ->
|
||||
val actionKey = EventKey(current.currentState.id, "gui_${guiAction.guiId}_${guiAction.action}", guiAction.time.toInt())
|
||||
|
||||
if (actionKey in current.executedEvents) return@forEach
|
||||
|
||||
val crossedAction = if (previousTime <= currentTime) {
|
||||
guiAction.time in previousTime..currentTime
|
||||
} else {
|
||||
guiAction.time <= currentTime
|
||||
}
|
||||
|
||||
if (crossedAction) {
|
||||
pendingGuiActions.add(FiredGuiAction(
|
||||
action = guiAction.action,
|
||||
guiId = guiAction.guiId,
|
||||
args = guiAction.args,
|
||||
argsExpressions = guiAction.argsExpressions
|
||||
))
|
||||
|
||||
state = current.copy(
|
||||
executedEvents = current.executedEvents + actionKey
|
||||
)
|
||||
|
||||
Cobblemon.LOGGER.debug("[TimelineEngine] Fired GUI action ${guiAction.action} for '${guiAction.guiId}' at time ${guiAction.time}s")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes and returns all pending GUI actions since the last tick.
|
||||
* Called by the CameraExecutor after tick().
|
||||
*/
|
||||
fun consumeGuiActions(): List<FiredGuiAction> {
|
||||
if (pendingGuiActions.isEmpty()) return emptyList()
|
||||
val actions = pendingGuiActions.toList()
|
||||
pendingGuiActions.clear()
|
||||
return actions
|
||||
}
|
||||
|
||||
private fun calculateTransform(): CameraTransform {
|
||||
val current = state as? ExecutionState.Playing ?: return CameraTransform(origin, null, null)
|
||||
|
||||
return when (val transition = current.transitionState) {
|
||||
is TransitionState.None -> {
|
||||
evaluateState(current.currentState, current.stateTime)
|
||||
}
|
||||
is TransitionState.Blending -> {
|
||||
val targetTransform = evaluateState(transition.targetState, 0f)
|
||||
val progress = if (transition.duration > 0f) {
|
||||
((current.totalTime - transition.startTime) / transition.duration).coerceIn(0f, 1f)
|
||||
} else 1f
|
||||
|
||||
val easedProgress = EasingRegistry.easeOutQuad(progress)
|
||||
|
||||
blendTransforms(transition.startTransform, targetTransform, easedProgress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun evaluateState(state: State, time: Float): CameraTransform {
|
||||
val context = createMoLangContext(state, time, (this.state as? ExecutionState.Playing)?.totalTime ?: 0f)
|
||||
|
||||
var position: Vec3? = null
|
||||
var focus: CameraFocus? = null
|
||||
var rotation: CameraRotation? = null
|
||||
|
||||
for (track in state.tracks) {
|
||||
val outputs = track.evaluate(time, context, runtime, origin)
|
||||
|
||||
for (output in outputs) {
|
||||
when (output) {
|
||||
is TrackOutput.Position -> {
|
||||
if (position == null) position = output.position
|
||||
}
|
||||
is TrackOutput.Focus -> {
|
||||
if (focus == null) focus = output.target
|
||||
}
|
||||
is TrackOutput.Rotation -> {
|
||||
if (rotation == null) rotation = output.rotation
|
||||
}
|
||||
is TrackOutput.Event -> {
|
||||
// Events handled separately
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CameraTransform(
|
||||
position = position ?: origin,
|
||||
focus = focus,
|
||||
rotation = rotation
|
||||
)
|
||||
}
|
||||
|
||||
private fun blendTransforms(
|
||||
from: CameraTransform,
|
||||
to: CameraTransform,
|
||||
progress: Float
|
||||
): CameraTransform {
|
||||
val blendedPosition = CameraInterpolationUtils.lerpPosition(from.position, to.position, progress)
|
||||
|
||||
val blendedRotation = if (from.rotation != null && to.rotation != null) {
|
||||
CameraInterpolationUtils.slerpRotation(from.rotation, to.rotation, progress)
|
||||
} else {
|
||||
to.rotation ?: from.rotation
|
||||
}
|
||||
|
||||
return CameraTransform(
|
||||
position = blendedPosition,
|
||||
focus = to.focus ?: from.focus,
|
||||
rotation = blendedRotation
|
||||
)
|
||||
}
|
||||
|
||||
private fun createMoLangContext(
|
||||
state: State,
|
||||
stateTime: Float,
|
||||
totalTime: Float
|
||||
): Map<String, MoValue> {
|
||||
timeOnCurrentPathValue.value = stateTime.toDouble()
|
||||
totalTimeValue.value = totalTime.toDouble()
|
||||
|
||||
runtime.environment.variable.apply {
|
||||
map["time_on_current_path"] = timeOnCurrentPathValue
|
||||
map["total_time"] = totalTimeValue
|
||||
map["runner_id"] = runnerIdValue
|
||||
|
||||
customVariables.forEach { (name, value) ->
|
||||
map[name] = DoubleValue(value)
|
||||
}
|
||||
}
|
||||
|
||||
return emptyMap() // Context passed via runtime.environment
|
||||
}
|
||||
|
||||
private fun normalizeProgress(time: Float, state: State): Float {
|
||||
val duration = state.getEffectiveDuration()
|
||||
return if (duration > 0f) {
|
||||
(time / duration).coerceIn(0f, 1f)
|
||||
} else 0f
|
||||
}
|
||||
}
|
||||
|
||||
data class CameraTransform(
|
||||
val position: Vec3,
|
||||
val focus: CameraFocus?,
|
||||
val rotation: CameraRotation?
|
||||
)
|
||||
|
||||
private sealed class ExecutionState {
|
||||
object Stopped : ExecutionState()
|
||||
|
||||
data class Playing(
|
||||
val currentState: State,
|
||||
val stateTime: Float,
|
||||
val totalTime: Float,
|
||||
val lastTickNanos: Long,
|
||||
val transitionState: TransitionState,
|
||||
val progressMemory: Map<String, StateSnapshot>,
|
||||
val executedEvents: Set<EventKey>,
|
||||
val lastEvaluatedFocus: CameraFocus? = null
|
||||
) : ExecutionState()
|
||||
}
|
||||
|
||||
private sealed class TransitionState {
|
||||
object None : TransitionState()
|
||||
|
||||
data class Blending(
|
||||
val targetState: State,
|
||||
val startTime: Float,
|
||||
val duration: Float,
|
||||
val startTransform: CameraTransform
|
||||
) : TransitionState()
|
||||
}
|
||||
|
||||
private sealed class TransitionCheckResult {
|
||||
object NoTransition : TransitionCheckResult()
|
||||
|
||||
data class TransitionNow(
|
||||
val targetStateId: String,
|
||||
val type: TransitionType,
|
||||
val blendDuration: Float
|
||||
) : TransitionCheckResult()
|
||||
}
|
||||
|
||||
private data class EventKey(
|
||||
val stateId: String,
|
||||
val eventId: String?,
|
||||
val timeMillis: Int
|
||||
)
|
||||
|
||||
private data class StateSnapshot(
|
||||
val normalizedProgress: Float,
|
||||
val lastFocus: CameraFocus?
|
||||
)
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.camera.integration.server
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.MoValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.camera.model.CameraFocus
|
||||
import com.cobblemon.mod.common.api.camera.track.FiredGuiAction
|
||||
import com.cobblemon.mod.common.api.camera.track.GuiActionType
|
||||
import net.minecraft.core.SectionPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.level.ChunkPos
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
/**
|
||||
* Executes camera timeline and manages focus resolution.
|
||||
*
|
||||
* Coordinates between CameraInstance, viewer/chunk managers, and network sync.
|
||||
*/
|
||||
class CameraExecutor(
|
||||
private val instance: CameraInstance,
|
||||
private val runtime: MoLangRuntime,
|
||||
private val context: Map<String, MoValue>,
|
||||
private val guiActionHandler: ((FiredGuiAction) -> Unit)? = null
|
||||
) {
|
||||
companion object {
|
||||
private const val CHUNK_UPDATE_DISTANCE_SQ = (16 * 16).toDouble()
|
||||
}
|
||||
|
||||
private var lastCenterChunkUpdate: Vec3? = null
|
||||
|
||||
fun tick(
|
||||
level: ServerLevel,
|
||||
networkSync: CameraNetworkSync,
|
||||
onChunkUpdate: (ChunkPos) -> Unit
|
||||
): Boolean {
|
||||
if (!instance.isActive()) return false
|
||||
|
||||
val transform = instance.tick() ?: run {
|
||||
if (!instance.isActive()) instance.stop()
|
||||
return false
|
||||
}
|
||||
|
||||
val shouldUpdateCenter = lastCenterChunkUpdate?.let { last ->
|
||||
transform.position.distanceToSqr(last) > CHUNK_UPDATE_DISTANCE_SQ
|
||||
} ?: true
|
||||
|
||||
if (shouldUpdateCenter) {
|
||||
val chunkPos = chunkPosFrom(transform.position)
|
||||
onChunkUpdate(chunkPos)
|
||||
lastCenterChunkUpdate = transform.position
|
||||
}
|
||||
|
||||
val instantTransition = instance.consumeInstantTransitionFlag()
|
||||
|
||||
networkSync.sendTransform(transform, shouldUpdateCenter)
|
||||
|
||||
when (val focus = transform.focus) {
|
||||
null -> networkSync.sendFocusNone()
|
||||
is CameraFocus.Bone -> networkSync.sendFocusBone(focus, context, runtime)
|
||||
else -> {
|
||||
val resolvedFocus = resolveFocusPosition(focus)
|
||||
val finalFocusPos = instance.focusOverride ?: resolvedFocus
|
||||
networkSync.sendFocusPoint(finalFocusPos)
|
||||
}
|
||||
}
|
||||
|
||||
networkSync.sendState(
|
||||
instantTransition = instantTransition,
|
||||
renderPlayerBody = instance.getCurrentRenderPlayerBody(),
|
||||
hiddenHudElements = instance.getCurrentHiddenHudElements()
|
||||
)
|
||||
|
||||
if (guiActionHandler != null) {
|
||||
val guiActions = instance.consumeGuiActions()
|
||||
for (action in guiActions) {
|
||||
guiActionHandler.invoke(action)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun resolveFocusPosition(focus: CameraFocus): Vec3? {
|
||||
return when (focus) {
|
||||
is CameraFocus.Entity -> {
|
||||
val entity = CameraFocus.resolveEntity(focus.selector, context, runtime)
|
||||
if (entity != null) {
|
||||
val position = entity.position()
|
||||
instance.updateLastValidFocusPosition(position)
|
||||
position
|
||||
} else {
|
||||
val cached = instance.lastValidFocusPosition
|
||||
if (cached != null) {
|
||||
Cobblemon.LOGGER.debug("[CameraExecutor] Entity focus invalid, using last valid position")
|
||||
cached
|
||||
} else {
|
||||
Cobblemon.LOGGER.warn("[CameraExecutor] Entity focus invalid and no cached position, using origin")
|
||||
instance.origin
|
||||
}
|
||||
}
|
||||
}
|
||||
is CameraFocus.Point -> {
|
||||
val position = focus.position.toVec3(context, runtime, instance.origin, false)
|
||||
instance.updateLastValidFocusPosition(position)
|
||||
position
|
||||
}
|
||||
is CameraFocus.Bone -> {
|
||||
// Bone focus is resolved client-side, so return entity position as fallback
|
||||
CameraFocus.resolveEntity(focus.selector, context, runtime)?.position()
|
||||
?: instance.lastValidFocusPosition
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun chunkPosFrom(position: Vec3): ChunkPos {
|
||||
val chunkX = SectionPos.blockToSectionCoord(Mth.floor(position.x))
|
||||
val chunkZ = SectionPos.blockToSectionCoord(Mth.floor(position.z))
|
||||
return ChunkPos(chunkX, chunkZ)
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.camera.integration.server
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.MoParams
|
||||
import com.bedrockk.molang.runtime.value.MoValue
|
||||
import com.cobblemon.mod.common.api.camera.engine.TimelineEngine
|
||||
import com.cobblemon.mod.common.api.camera.model.State
|
||||
import com.cobblemon.mod.common.api.camera.model.Timeline
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Server-side camera instance - pure state and identity.
|
||||
*
|
||||
* Represents a single camera with its configuration and execution engine.
|
||||
* Does NOT handle networking, chunk loading, or viewer management.
|
||||
*
|
||||
* @param id Unique identifier for this camera instance
|
||||
* @param origin The world position origin for relative coordinates
|
||||
* @param initialContext Initial context values (parameter values) to inject into MoLang.
|
||||
* These are validated against the timeline's declared parameters when starting.
|
||||
* @param queryFunctions Custom MoLang query functions to register
|
||||
*/
|
||||
class CameraInstance(
|
||||
val id: UUID = UUID.randomUUID(),
|
||||
val origin: Vec3,
|
||||
private val initialContext: Map<String, MoValue> = emptyMap(),
|
||||
queryFunctions: Map<String, java.util.function.Function<MoParams, Any>> = emptyMap()
|
||||
) {
|
||||
private val runtime: MoLangRuntime = MoLangRuntime().setup().apply {
|
||||
environment.query.functions.putAll(queryFunctions)
|
||||
}
|
||||
|
||||
private var engine: TimelineEngine = TimelineEngine(id, createStubTimeline(), origin, runtime, initialContext)
|
||||
|
||||
@Volatile
|
||||
var focusOverride: Vec3? = null
|
||||
|
||||
var lastValidFocusPosition: Vec3? = null
|
||||
private set
|
||||
|
||||
fun startTimeline(timeline: Timeline): Boolean {
|
||||
engine = TimelineEngine(id, timeline, origin, runtime, initialContext)
|
||||
return engine.start()
|
||||
}
|
||||
|
||||
fun tick() = engine.tick()
|
||||
|
||||
fun stop() = engine.stop()
|
||||
|
||||
fun isActive() = engine.isActive()
|
||||
|
||||
fun getCurrentRenderPlayerBody() = engine.getCurrentRenderPlayerBody()
|
||||
|
||||
fun getCurrentHiddenHudElements() = engine.getCurrentHiddenHudElements()
|
||||
|
||||
fun setVariable(name: String, value: Double) = engine.setVariable(name, value)
|
||||
|
||||
fun triggerInstantTransition() = engine.triggerInstantTransition()
|
||||
|
||||
fun addQueryFunctions(functions: Map<String, java.util.function.Function<MoParams, Any>>) {
|
||||
runtime.environment.query.functions.putAll(functions)
|
||||
}
|
||||
|
||||
fun consumeInstantTransitionFlag() = engine.consumeInstantTransitionFlag()
|
||||
|
||||
fun consumeGuiActions() = engine.consumeGuiActions()
|
||||
|
||||
fun updateLastValidFocusPosition(position: Vec3) {
|
||||
lastValidFocusPosition = position
|
||||
}
|
||||
|
||||
private fun createStubTimeline(): Timeline {
|
||||
return Timeline(
|
||||
id = "stub",
|
||||
startStateId = "stub",
|
||||
states = mapOf(
|
||||
"stub" to State(
|
||||
id = "stub",
|
||||
tracks = emptyList()
|
||||
)
|
||||
),
|
||||
transitions = emptyList()
|
||||
)
|
||||
}
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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.camera.integration.server
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.MoValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.camera.engine.CameraTransform
|
||||
import com.cobblemon.mod.common.api.camera.model.CameraFocus
|
||||
import com.cobblemon.mod.common.api.camera.model.HudElement
|
||||
import com.cobblemon.mod.common.net.messages.client.camera.CameraEndPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.camera.CameraFocusPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.camera.CameraStartPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.camera.CameraStatePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.camera.CameraTransformPacket
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Handles camera packet transmission to clients.
|
||||
*
|
||||
* Manages synchronization of camera state with connected viewers.
|
||||
* Uses modular packets with change tracking to minimize network traffic.
|
||||
*/
|
||||
class CameraNetworkSync(
|
||||
private val cameraId: UUID,
|
||||
private val viewers: Set<UUID>
|
||||
) {
|
||||
private var lastFocusPosition: Vec3? = null
|
||||
private var lastRenderPlayerBody: Boolean = true
|
||||
private var lastHiddenHudElements: Set<HudElement> = emptySet()
|
||||
private var lastInstantTransition: Boolean = false
|
||||
|
||||
fun sendStart(startPosition: Vec3) {
|
||||
getPlayers().forEach { player ->
|
||||
player.sendPacket(CameraStartPacket(cameraId, startPosition))
|
||||
}
|
||||
|
||||
sendFocusNone(force = true)
|
||||
sendState(
|
||||
instantTransition = false,
|
||||
renderPlayerBody = true,
|
||||
hiddenHudElements = emptySet(),
|
||||
force = true
|
||||
)
|
||||
}
|
||||
|
||||
fun sendTransform(
|
||||
transform: CameraTransform,
|
||||
updateCenterChunk: Boolean
|
||||
) {
|
||||
val packet = CameraTransformPacket(
|
||||
cameraId = cameraId,
|
||||
position = transform.position,
|
||||
rotation = transform.rotation,
|
||||
updateCenterChunk = updateCenterChunk
|
||||
)
|
||||
|
||||
getPlayers().forEach { it.sendPacket(packet) }
|
||||
}
|
||||
|
||||
fun sendFocusNone(force: Boolean = false) {
|
||||
if (!force && lastFocusPosition == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val packet = CameraFocusPacket(
|
||||
cameraId = cameraId,
|
||||
focus = CameraFocusPacket.FocusData.None
|
||||
)
|
||||
|
||||
getPlayers().forEach { it.sendPacket(packet) }
|
||||
lastFocusPosition = null
|
||||
}
|
||||
|
||||
fun sendFocusPoint(focusPosition: Vec3?, force: Boolean = false) {
|
||||
if (!force && focusPosition == lastFocusPosition) {
|
||||
return
|
||||
}
|
||||
|
||||
val focusData = when (focusPosition) {
|
||||
null -> CameraFocusPacket.FocusData.None
|
||||
else -> CameraFocusPacket.FocusData.Point(focusPosition)
|
||||
}
|
||||
|
||||
val packet = CameraFocusPacket(
|
||||
cameraId = cameraId,
|
||||
focus = focusData
|
||||
)
|
||||
|
||||
getPlayers().forEach { it.sendPacket(packet) }
|
||||
lastFocusPosition = focusPosition
|
||||
}
|
||||
|
||||
fun sendFocusBone(
|
||||
boneFocus: CameraFocus.Bone,
|
||||
context: Map<String, MoValue>,
|
||||
runtime: MoLangRuntime,
|
||||
force: Boolean = false
|
||||
) {
|
||||
val entity = CameraFocus.resolveEntity(boneFocus.selector, context, runtime)
|
||||
|
||||
if (entity == null) {
|
||||
Cobblemon.LOGGER.debug("[CameraNetworkSync] Bone focus entity invalid")
|
||||
sendFocusNone(force)
|
||||
return
|
||||
}
|
||||
|
||||
val offset = boneFocus.offset.toVec3(context, runtime)
|
||||
|
||||
Cobblemon.LOGGER.debug("[CameraNetworkSync] Sending bone focus: entityId=${entity.id}, bone=${boneFocus.boneName}, offset=$offset, smooth=${boneFocus.smooth}")
|
||||
|
||||
val focusData = CameraFocusPacket.FocusData.Bone(
|
||||
entityId = entity.id,
|
||||
boneName = boneFocus.boneName,
|
||||
offset = offset,
|
||||
smooth = boneFocus.smooth
|
||||
)
|
||||
|
||||
val packet = CameraFocusPacket(
|
||||
cameraId = cameraId,
|
||||
focus = focusData
|
||||
)
|
||||
|
||||
getPlayers().forEach { it.sendPacket(packet) }
|
||||
|
||||
// Invalidate lastFocusPosition when switching to bone focus.
|
||||
// This ensures that when transitioning back to a point focus,
|
||||
// the packet will always be sent (not incorrectly deduplicated).
|
||||
lastFocusPosition = null
|
||||
}
|
||||
|
||||
fun sendState(
|
||||
instantTransition: Boolean,
|
||||
renderPlayerBody: Boolean,
|
||||
hiddenHudElements: Set<HudElement>,
|
||||
force: Boolean = false
|
||||
) {
|
||||
if (!force &&
|
||||
instantTransition == lastInstantTransition &&
|
||||
renderPlayerBody == lastRenderPlayerBody &&
|
||||
hiddenHudElements == lastHiddenHudElements) {
|
||||
return
|
||||
}
|
||||
|
||||
val packet = CameraStatePacket(
|
||||
cameraId = cameraId,
|
||||
instantTransition = instantTransition,
|
||||
renderPlayerBody = renderPlayerBody,
|
||||
hiddenHudElements = hiddenHudElements
|
||||
)
|
||||
|
||||
getPlayers().forEach { it.sendPacket(packet) }
|
||||
lastInstantTransition = instantTransition
|
||||
lastRenderPlayerBody = renderPlayerBody
|
||||
lastHiddenHudElements = hiddenHudElements
|
||||
}
|
||||
|
||||
fun sendEnd() {
|
||||
val packet = CameraEndPacket(cameraId)
|
||||
getPlayers().forEach { it.sendPacket(packet) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Send end packet to a specific player.
|
||||
* Used when removing a single viewer without stopping the entire camera.
|
||||
*/
|
||||
fun sendEndToPlayer(player: ServerPlayer) {
|
||||
player.sendPacket(CameraEndPacket(cameraId))
|
||||
}
|
||||
|
||||
private fun getPlayers(): List<ServerPlayer> {
|
||||
val server = Cobblemon.implementation.server() ?: return emptyList()
|
||||
return viewers.mapNotNull { server.playerList.getPlayer(it) }
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.camera.integration.server
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.client.render.camera.compositor.CompositorLayout
|
||||
import com.cobblemon.mod.common.net.messages.client.camera.CompositorPacket
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import java.util.UUID
|
||||
|
||||
class CompositorSession(
|
||||
val compositorId: UUID = UUID.randomUUID(),
|
||||
private val viewers: () -> Collection<ServerPlayer>,
|
||||
val fullscreen: Boolean = true,
|
||||
val width: Int = 1920,
|
||||
val height: Int = 1080
|
||||
) {
|
||||
fun sendCreate(layout: CompositorLayout) {
|
||||
val packet = CompositorPacket.create(compositorId, layout, fullscreen, width, height)
|
||||
viewers().forEach { it.sendPacket(packet) }
|
||||
}
|
||||
|
||||
fun sendLayoutUpdate(layout: CompositorLayout) {
|
||||
val packet = CompositorPacket.updateLayout(compositorId, layout)
|
||||
viewers().forEach { it.sendPacket(packet) }
|
||||
}
|
||||
|
||||
fun sendDestroy() {
|
||||
val packet = CompositorPacket.destroy(compositorId)
|
||||
viewers().forEach { it.sendPacket(packet) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* 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.camera.model
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.MoValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import com.cobblemon.mod.common.util.resolve
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
/**
|
||||
* Represents where the camera should look.
|
||||
*/
|
||||
sealed class CameraFocus {
|
||||
abstract fun resolve(
|
||||
context: Map<String, MoValue>,
|
||||
runtime: MoLangRuntime,
|
||||
fallback: Vec3?
|
||||
): Vec3?
|
||||
|
||||
data class Point(val position: PositionExpression) : CameraFocus() {
|
||||
override fun resolve(
|
||||
context: Map<String, MoValue>,
|
||||
runtime: MoLangRuntime,
|
||||
fallback: Vec3?
|
||||
): Vec3 {
|
||||
return position.toVec3(context, runtime)
|
||||
}
|
||||
}
|
||||
|
||||
data class Entity(val selector: ExpressionLike) : CameraFocus() {
|
||||
override fun resolve(
|
||||
context: Map<String, MoValue>,
|
||||
runtime: MoLangRuntime,
|
||||
fallback: Vec3?
|
||||
): Vec3? {
|
||||
return resolveEntity(selector, context, runtime)?.position() ?: fallback
|
||||
}
|
||||
}
|
||||
|
||||
data class Bone(
|
||||
val selector: ExpressionLike,
|
||||
val boneName: String,
|
||||
val offset: PositionExpression = PositionExpression(
|
||||
CoordinateValue.Static(0.0),
|
||||
CoordinateValue.Static(0.0),
|
||||
CoordinateValue.Static(0.0)
|
||||
),
|
||||
val smooth: Boolean = true
|
||||
) : CameraFocus() {
|
||||
override fun resolve(
|
||||
context: Map<String, MoValue>,
|
||||
runtime: MoLangRuntime,
|
||||
fallback: Vec3?
|
||||
): Vec3? {
|
||||
// Server-side: just resolve the entity position as fallback
|
||||
// Client-side bone resolution happens in BoneFocusResolver
|
||||
return resolveEntity(selector, context, runtime)?.position() ?: fallback
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Resolves a MoLang entity selector to a live Entity, or null if the selector
|
||||
* doesn't resolve, the entity is removed, or the entity is dead.
|
||||
*/
|
||||
fun resolveEntity(
|
||||
selector: ExpressionLike,
|
||||
context: Map<String, MoValue>,
|
||||
runtime: MoLangRuntime
|
||||
): net.minecraft.world.entity.Entity? {
|
||||
return try {
|
||||
val result = runtime.resolve(selector, context)
|
||||
if (result is ObjectValue<*>) {
|
||||
val entity = result.obj as? net.minecraft.world.entity.Entity
|
||||
if (entity != null && !entity.isRemoved && entity.isAlive) entity else null
|
||||
} else null
|
||||
} catch (e: Exception) {
|
||||
Cobblemon.LOGGER.error("[CameraFocus] Failed to resolve entity from selector", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun fromJson(obj: JsonObject): CameraFocus {
|
||||
val type = obj.get("type")?.asString
|
||||
?: throw IllegalArgumentException("CameraFocus missing 'type' field")
|
||||
|
||||
return when (type) {
|
||||
"entity" -> {
|
||||
val selector = obj.get("selector")?.asString
|
||||
?: throw IllegalArgumentException("Entity focus missing 'selector' field")
|
||||
Entity(selector.asExpressionLike())
|
||||
}
|
||||
"position", "point" -> {
|
||||
val posElement = obj.get("position")
|
||||
?: obj // If no nested position, use the object itself
|
||||
Point(PositionExpression.fromJson(posElement))
|
||||
}
|
||||
"bone" -> {
|
||||
val selector = obj.get("selector")?.asString
|
||||
?: throw IllegalArgumentException("Bone focus missing 'selector' field")
|
||||
val boneName = obj.get("bone")?.asString
|
||||
?: obj.get("boneName")?.asString
|
||||
?: throw IllegalArgumentException("Bone focus missing 'bone' field")
|
||||
val offset = if (obj.has("offset")) {
|
||||
PositionExpression.fromJson(obj.get("offset"))
|
||||
} else {
|
||||
PositionExpression(
|
||||
CoordinateValue.Static(0.0),
|
||||
CoordinateValue.Static(0.0),
|
||||
CoordinateValue.Static(0.0)
|
||||
)
|
||||
}
|
||||
val smooth = obj.get("smooth")?.asBoolean ?: true
|
||||
|
||||
Bone(selector.asExpressionLike(), boneName, offset, smooth)
|
||||
}
|
||||
else -> {
|
||||
throw IllegalArgumentException("Unknown CameraFocus type: $type")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Position defined by three coordinates (x, y, z), each either static or dynamic.
|
||||
*/
|
||||
data class PositionExpression(
|
||||
val x: CoordinateValue,
|
||||
val y: CoordinateValue,
|
||||
val z: CoordinateValue
|
||||
) {
|
||||
fun toVec3(
|
||||
context: Map<String, MoValue> = emptyMap(),
|
||||
runtime: MoLangRuntime,
|
||||
origin: Vec3 = Vec3.ZERO,
|
||||
isRelative: Boolean = false
|
||||
): Vec3 {
|
||||
val base = Vec3(
|
||||
x.evaluate(context, runtime),
|
||||
y.evaluate(context, runtime),
|
||||
z.evaluate(context, runtime)
|
||||
)
|
||||
|
||||
return if (isRelative) base.add(origin) else base
|
||||
}
|
||||
|
||||
fun isFinite(context: Map<String, MoValue>, runtime: MoLangRuntime): Boolean {
|
||||
val vec = toVec3(context, runtime)
|
||||
return vec.x.isFinite() && vec.y.isFinite() && vec.z.isFinite()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromJson(element: JsonElement): PositionExpression {
|
||||
return when {
|
||||
element.isJsonObject -> {
|
||||
val obj = element.asJsonObject
|
||||
PositionExpression(
|
||||
x = deserializeCoordinate(obj.get("x")),
|
||||
y = deserializeCoordinate(obj.get("y")),
|
||||
z = deserializeCoordinate(obj.get("z"))
|
||||
)
|
||||
}
|
||||
element.isJsonArray -> {
|
||||
val arr = element.asJsonArray
|
||||
if (arr.size() != 3) {
|
||||
throw IllegalArgumentException("Position array must have exactly 3 elements [x, y, z], got ${arr.size()}")
|
||||
}
|
||||
PositionExpression(
|
||||
x = deserializeCoordinate(arr[0]),
|
||||
y = deserializeCoordinate(arr[1]),
|
||||
z = deserializeCoordinate(arr[2])
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
throw IllegalArgumentException("Position must be either an object {x, y, z} or array [x, y, z]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deserializeCoordinate(element: JsonElement?): CoordinateValue {
|
||||
if (element == null) {
|
||||
return CoordinateValue.Static(0.0)
|
||||
}
|
||||
|
||||
return when {
|
||||
element.isJsonPrimitive && element.asJsonPrimitive.isNumber -> {
|
||||
CoordinateValue.Static(element.asDouble)
|
||||
}
|
||||
element.isJsonPrimitive && element.asJsonPrimitive.isString -> {
|
||||
// String is treated as expression
|
||||
CoordinateValue.Dynamic(element.asString.asExpressionLike())
|
||||
}
|
||||
element.isJsonObject -> {
|
||||
val obj = element.asJsonObject
|
||||
val type = obj.get("type")?.asString
|
||||
val expression = obj.get("expression")?.asString
|
||||
|
||||
when (type) {
|
||||
"expression" -> {
|
||||
if (expression == null) {
|
||||
throw IllegalArgumentException("Expression coordinate missing 'expression' field")
|
||||
}
|
||||
CoordinateValue.Dynamic(expression.asExpressionLike())
|
||||
}
|
||||
else -> {
|
||||
throw IllegalArgumentException("Unknown coordinate type: $type")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
throw IllegalArgumentException("Invalid coordinate value: $element")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class CoordinateValue {
|
||||
data class Static(val value: Double) : CoordinateValue()
|
||||
data class Dynamic(val expression: ExpressionLike) : CoordinateValue()
|
||||
|
||||
fun evaluate(
|
||||
context: Map<String, MoValue>,
|
||||
runtime: MoLangRuntime
|
||||
): Double {
|
||||
return when (this) {
|
||||
is Static -> value
|
||||
is Dynamic -> {
|
||||
try {
|
||||
runtime.resolve(expression, context).asDouble()
|
||||
} catch (e: Exception) {
|
||||
// Log error but don't crash - coordinate expressions shouldn't break cameras
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.camera.model
|
||||
|
||||
enum class HudElement(val id: String) {
|
||||
CROSSHAIR("crosshair"),
|
||||
HOTBAR("hotbar"),
|
||||
EXPERIENCE_BAR("experience_bar"),
|
||||
PLAYER_HEALTH("player_health"),
|
||||
ARMOR("armor"),
|
||||
FOOD("food"),
|
||||
AIR("air"),
|
||||
EFFECTS("effects"),
|
||||
BOSS_BAR("boss_bar"),
|
||||
SCOREBOARD("scoreboard"),
|
||||
CHAT("chat"),
|
||||
PLAYER_LIST("player_list"),
|
||||
SELECTED_ITEM_NAME("selected_item_name"),
|
||||
COBBLEMON_OVERLAY("cobblemon_overlay");
|
||||
|
||||
companion object {
|
||||
val ALL: Set<HudElement> = entries.toSet()
|
||||
|
||||
private val BY_ID = entries.associateBy { it.id }
|
||||
|
||||
fun fromId(id: String): HudElement? = BY_ID[id]
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.camera.model
|
||||
|
||||
/**
|
||||
* Defines a parameter that can be passed to a camera timeline.
|
||||
*
|
||||
* Parameters allow timelines to be reusable with different values.
|
||||
* For example, an arena camera can accept `arena_center` as a Vec3 parameter
|
||||
* to position the camera relative to any arena location.
|
||||
*
|
||||
* @property type The type of the parameter (NUMBER or VEC3)
|
||||
* @property description Optional description for documentation
|
||||
* @property default Optional default value. If null, the parameter is required.
|
||||
*/
|
||||
data class ParameterDefinition(
|
||||
val type: ParameterType,
|
||||
val description: String? = null,
|
||||
val default: ParameterDefault? = null
|
||||
) {
|
||||
/**
|
||||
* Whether this parameter must be provided when starting the timeline.
|
||||
* Parameters with defaults are optional.
|
||||
*/
|
||||
val isRequired: Boolean get() = default == null
|
||||
|
||||
/**
|
||||
* Gets the variable names this parameter will inject into the MoLang runtime.
|
||||
* - NUMBER type: returns just the parameter name
|
||||
* - VEC3 type: returns name_x, name_y, name_z
|
||||
*/
|
||||
fun getVariableNames(paramName: String): List<String> {
|
||||
return when (type) {
|
||||
ParameterType.NUMBER -> listOf(paramName)
|
||||
ParameterType.VEC3 -> listOf("${paramName}_x", "${paramName}_y", "${paramName}_z")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Types of parameters supported by camera timelines.
|
||||
*/
|
||||
enum class ParameterType {
|
||||
/**
|
||||
* A single numeric value (Double).
|
||||
* Accessible in MoLang as `variable.param_name`
|
||||
*/
|
||||
NUMBER,
|
||||
|
||||
/**
|
||||
* A 3D vector value.
|
||||
* Expands to three variables in MoLang:
|
||||
* - `variable.param_name_x`
|
||||
* - `variable.param_name_y`
|
||||
* - `variable.param_name_z`
|
||||
*/
|
||||
VEC3
|
||||
}
|
||||
|
||||
/**
|
||||
* Default value for a parameter.
|
||||
*/
|
||||
sealed class ParameterDefault {
|
||||
/**
|
||||
* Default value for a NUMBER parameter.
|
||||
*/
|
||||
data class NumberDefault(val value: Double) : ParameterDefault()
|
||||
|
||||
/**
|
||||
* Default value for a VEC3 parameter.
|
||||
*/
|
||||
data class Vec3Default(val x: Double, val y: Double, val z: Double) : ParameterDefault()
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* 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.camera.model
|
||||
|
||||
import com.cobblemon.mod.common.api.camera.result.ValidationError
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
|
||||
/**
|
||||
* Immutable timeline definition - a state machine of camera animations with transitions.
|
||||
*
|
||||
* Timelines can declare required and optional parameters that must be provided when
|
||||
* starting the camera. Parameters are injected into the MoLang runtime as variables,
|
||||
* allowing tracks to use dynamic values like `variable.arena_center_x`.
|
||||
*
|
||||
* @property id Unique identifier for this timeline
|
||||
* @property startStateId The ID of the initial state when the timeline starts
|
||||
* @property parameters Map of parameter names to their definitions. Parameters without
|
||||
* defaults are required and must be provided when starting the camera.
|
||||
* @property states Map of state IDs to state definitions
|
||||
* @property transitions List of transitions between states
|
||||
*/
|
||||
data class Timeline(
|
||||
val id: String,
|
||||
val startStateId: String,
|
||||
val parameters: Map<String, ParameterDefinition> = emptyMap(),
|
||||
val states: Map<String, State>,
|
||||
val transitions: List<Transition>
|
||||
) {
|
||||
fun getState(stateId: String): State? = states[stateId]
|
||||
|
||||
fun getTransitionsFrom(stateId: String): List<Transition> =
|
||||
transitions.filter { it.sourceStateId == stateId }
|
||||
|
||||
/**
|
||||
* Gets the names of all required parameters (those without defaults).
|
||||
*/
|
||||
fun getRequiredParameterNames(): Set<String> =
|
||||
parameters.filter { it.value.isRequired }.keys
|
||||
|
||||
/**
|
||||
* Gets all variable names that this timeline's parameters will inject.
|
||||
* For NUMBER parameters, returns the parameter name.
|
||||
* For VEC3 parameters, returns name_x, name_y, name_z.
|
||||
*/
|
||||
fun getAllParameterVariableNames(): Set<String> =
|
||||
parameters.flatMap { (name, def) -> def.getVariableNames(name) }.toSet()
|
||||
|
||||
/**
|
||||
* Validates that all required parameters are provided in the given context.
|
||||
*
|
||||
* @param providedVariables Set of variable names provided in the context
|
||||
* @return List of missing parameter names, empty if all required params are present
|
||||
*/
|
||||
fun validateParameters(providedVariables: Set<String>): List<String> {
|
||||
val missingParams = mutableListOf<String>()
|
||||
|
||||
for ((paramName, definition) in parameters) {
|
||||
if (!definition.isRequired) continue
|
||||
|
||||
val requiredVars = definition.getVariableNames(paramName)
|
||||
val allProvided = requiredVars.all { it in providedVariables }
|
||||
|
||||
if (!allProvided) {
|
||||
missingParams.add(paramName)
|
||||
}
|
||||
}
|
||||
|
||||
return missingParams
|
||||
}
|
||||
|
||||
fun validate(): List<ValidationError> {
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
// Check start state exists
|
||||
if (startStateId !in states) {
|
||||
errors.add(ValidationError(
|
||||
path = "startStateId",
|
||||
message = "Start state '$startStateId' not found in states map"
|
||||
))
|
||||
}
|
||||
|
||||
// Check all transition states exist
|
||||
transitions.forEachIndexed { index, transition ->
|
||||
if (transition.sourceStateId !in states) {
|
||||
errors.add(ValidationError(
|
||||
path = "transitions[$index].sourceStateId",
|
||||
message = "Source state '${transition.sourceStateId}' not found"
|
||||
))
|
||||
}
|
||||
if (transition.targetStateId !in states) {
|
||||
errors.add(ValidationError(
|
||||
path = "transitions[$index].targetStateId",
|
||||
message = "Target state '${transition.targetStateId}' not found"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
states.forEach { (stateId, state) ->
|
||||
if (state.tracks.isEmpty()) {
|
||||
errors.add(ValidationError(
|
||||
path = "states[$stateId].tracks",
|
||||
message = "State has no tracks defined",
|
||||
severity = ValidationError.Severity.WARNING
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
val reachableStates = findReachableStates()
|
||||
states.keys.forEach { stateId ->
|
||||
if (stateId !in reachableStates) {
|
||||
errors.add(ValidationError(
|
||||
path = "states[$stateId]",
|
||||
message = "State is unreachable from start state '$startStateId'",
|
||||
severity = ValidationError.Severity.WARNING
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
val cycles = detectCycles()
|
||||
if (cycles.isNotEmpty()) {
|
||||
errors.add(ValidationError(
|
||||
path = "transitions",
|
||||
message = "Detected transition cycles: ${cycles.joinToString(", ") { it.joinToString(" -> ") }}",
|
||||
severity = ValidationError.Severity.WARNING
|
||||
))
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
private fun findReachableStates(): Set<String> {
|
||||
val reachable = mutableSetOf<String>()
|
||||
val queue = ArrayDeque<String>()
|
||||
|
||||
if (startStateId in states) {
|
||||
queue.add(startStateId)
|
||||
reachable.add(startStateId)
|
||||
}
|
||||
|
||||
while (queue.isNotEmpty()) {
|
||||
val current = queue.removeFirst()
|
||||
transitions
|
||||
.filter { it.sourceStateId == current }
|
||||
.forEach { transition ->
|
||||
if (transition.targetStateId !in reachable && transition.targetStateId in states) {
|
||||
reachable.add(transition.targetStateId)
|
||||
queue.add(transition.targetStateId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return reachable
|
||||
}
|
||||
|
||||
private fun detectCycles(): List<List<String>> {
|
||||
val cycles = mutableListOf<List<String>>()
|
||||
val visited = mutableSetOf<String>()
|
||||
val recStack = mutableSetOf<String>()
|
||||
|
||||
fun dfs(node: String, path: MutableList<String>): Boolean {
|
||||
if (node !in states) return false
|
||||
|
||||
visited.add(node)
|
||||
recStack.add(node)
|
||||
path.add(node)
|
||||
|
||||
for (transition in getTransitionsFrom(node)) {
|
||||
val target = transition.targetStateId
|
||||
if (target !in visited) {
|
||||
if (dfs(target, path)) return true
|
||||
} else if (target in recStack) {
|
||||
val cycleStart = path.indexOf(target)
|
||||
if (cycleStart >= 0) {
|
||||
cycles.add(path.subList(cycleStart, path.size) + target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
path.removeAt(path.size - 1)
|
||||
recStack.remove(node)
|
||||
return false
|
||||
}
|
||||
|
||||
states.keys.forEach { stateId ->
|
||||
if (stateId !in visited) {
|
||||
dfs(stateId, mutableListOf())
|
||||
}
|
||||
}
|
||||
|
||||
return cycles
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Immutable camera state definition - a collection of tracks that play together.
|
||||
*/
|
||||
data class State(
|
||||
val id: String,
|
||||
val tracks: List<CameraTrack>,
|
||||
val duration: Float? = null,
|
||||
val isLooping: Boolean = false,
|
||||
val speed: Float = 1.0f,
|
||||
val rememberProgress: Boolean = false,
|
||||
val renderPlayerBody: Boolean = true,
|
||||
val hiddenHudElements: Set<HudElement> = emptySet()
|
||||
) {
|
||||
init {
|
||||
require(speed > 0f) { "State speed must be positive, got $speed" }
|
||||
duration?.let { d ->
|
||||
require(d >= 0f) { "State duration must be non-negative, got $d" }
|
||||
}
|
||||
}
|
||||
|
||||
fun getEffectiveDuration(): Float {
|
||||
return duration ?: tracks.maxOfOrNull { it.duration } ?: 0f
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Immutable transition definition - defines when and how to move from one state to another.
|
||||
*/
|
||||
data class Transition(
|
||||
val sourceStateId: String,
|
||||
val targetStateId: String,
|
||||
val condition: ExpressionLike,
|
||||
val type: TransitionType = TransitionType.BLEND,
|
||||
val blendDuration: Float = 0.4f
|
||||
) {
|
||||
init {
|
||||
require(blendDuration >= 0f) { "Blend duration must be non-negative, got $blendDuration" }
|
||||
}
|
||||
}
|
||||
|
||||
enum class TransitionType {
|
||||
INSTANT,
|
||||
BLEND
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user