Compare commits

..
Author SHA1 Message Date
terrainwax c38630bd6f Merge branch 'main' into feat/stadium-camera
# Conflicts:
#	fabric/build.gradle.kts
#	neoforge/build.gradle.kts
2026-04-16 18:34:15 +02:00
terrainwax 4dba646de9 feat(molang-ui): rename Scripted GUI to Molang UI and add manifest/action/slot systems
Major refactor renaming the Scripted GUI system to Molang UI, migrating
definitions from datapacks to resource packs, and introducing a pluggable
action system, container/slot support, and hot-reload.

Renaming & restructuring:
- Rename all ScriptedGui classes, packets, handlers, and commands to MolangUi
  (command literal: scriptedgui → molangui)
- Move client/gui/scripted → client/gui/molangui, api/gui/scripted → api/molangui
- Rename resource paths: scripted_guis → molang_ui, scripted_gui_templates
  → molang_ui_templates
- Consolidate related classes into single files (Animation, WidgetDefinition,
  WidgetType/Anchor, TemplateDefinition, WidgetEvent, ScriptedGuiSessionManager)

Resource pack migration:
- Move GUI definitions from data/ to assets/ so they load client-side via
  resource reload rather than server-side data registry
- Add MolangUiRegistry as a simple client-side loader
- Add version field to definitions and MolangUiVersionPacket for client/server
  version validation
- Hot-reload active Molang UIs on resource pack reload (F3+T)

Action system:
- Add MolangUiActionRouter with tiered handlers: Kotlin handlers first,
  then manifest-referenced .molang script fallback, then legacy session
  action listeners
- Add MolangUiManifestRegistry for server-side manifest loading
- Add action@ syntax for routing widget events directly to manifest actions
  (e.g. "on_click": "action@smelt")
- Map positional action data to named manifest params (v.amount instead of v.0)
- Add on_open manifest hook and persistent per-player data support
- Introduce proper struct pattern for action context: q.session.arg(),
  q.session.update_arg(), q.session.close(), q.player.count_item(),
  q.player.remove_item(), q.player.run_command(), q.player.data/save_data()
2026-04-16 18:27:21 +02:00
terrainwax 201da371a5 feat(scripted-gui, stadium, npc-shop): rewrite scripted GUI system and stadium config screen
scripted-gui:
- Replace GraalVM/JS runtime with declarative JSON + MoLang widget tree system
- Add core widget model (WidgetType, WidgetProperty, WidgetDefinition, Anchor)
- Add SizeExpression parser (pixel, percent, fill, auto, MoLang)
- Add LayoutEngine with anchor, flow, and grid layout
- Add animation infrastructure (Easing, TransitionState, EnterExitAnimation)
- Add template system with @inheritance, $input substitution, and library imports
- Add WidgetTree JSON parser with template resolution
- Add MoLang context with client/server runtime setup and gui_update_args function
- Add client event processor with hit testing and server dispatch
- Add widget renderers: Panel, Label, Image, Fill, Button, ProgressBar, List
- Add ScriptedGuiWidgetTree as central client-side orchestrator
- Add server-side MoLang event handler evaluation in ScriptedGuiSession
- Extend ScriptedGuiUpdatePacket with server-to-client variable push (v.*)
- Add variable push, dynamic visibility, data lists, and NUMBER_INPUT widget
- Add common template library with styled base widget defaults
- Migrate test_overlay and battle_intro from JS to MoLang widget trees
- Remove GraalVM scripted GUI files (Runtime, DrawingContext, ScriptSandbox, ArgConverter)

stadium:
- Rewrite StadiumConfigScreen with two-tab (Battle/Arena) layout
- Add StadiumTabWidget, StadiumCycleSelectorWidget, StadiumToggleWidget
- Add StadiumFormatListWidget with selection and toggle
- Add StadiumSlotEntryWidget with color indicator and actions
- Add StadiumOverlayWidget for ban/clause/gimmick editing
- Implement Battle tab with format list, detail panel, and overlays
- Implement Arena tab with Placements, Camera, and Music sub-tabs
- Add interactive marker selection to ArenaMapWidget
- Implement Arena Placements sub-tab with interactive map-slot linking
- Add wand auto-remove on use and screen reopen after placement
- Add in-game placement highlight rendering when holding wand
- Fix panel overflow, overlay z-order, and replace hardcoded data with registry lookups
- Fix overlay opacity and add scroll to custom format detail panel
- Add MPL license headers to widget files
- Add placeholder texture assets for config screen

npc-shop:
- Add shop NPC interaction configuration (See Articles/Leave dialogue)
- Add shop GUI with item list page and centered card purchase page
- Add server-side purchase validation with emerald counting
- Add client send_action function for scripted GUI MoLang runtime
- Add Shopkeeper test NPC preset
2026-04-15 18:18:22 +02:00
terrainwax d6828baeec fix: rename function ( relica from early stage of stadium camera ) 2026-02-26 03:24:32 +01:00
terrainwax 34f1fd2809 fix: add back rotation for compiling purpose :) 2026-02-26 02:17:41 +01:00
terrainwax 09a5fe4c5d feat: bring back camera accessor 2026-02-26 01:50:52 +01:00
terrainwax c42bd7a293 Merge branch 'main' into feat/stadium-camera
# Conflicts:
#	common/src/main/java/com/cobblemon/mod/common/mixin/accessor/BlockEntityAccessor.java
#	common/src/main/java/com/cobblemon/mod/common/mixin/client/CameraMixin.java
#	common/src/main/java/com/cobblemon/mod/common/mixin/client/GameRendererMixin.java
#	common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/events/CobblemonEvents.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/battles/interpreter/instructions/MoveInstruction.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/keybind/keybinds/DebugKeybindings.kt
#	common/src/main/resources/mixins.cobblemon-common.json
#	fabric/build.gradle.kts
#	neoforge/build.gradle.kts
2026-02-26 01:49:35 +01:00
terrainwax 33a303a2f5 remove: Stadium block Specification 2026-02-26 01:23:31 +01:00
terrainwax 9f7dd84ace feat: add Iris / Sodium compat, create a TweenEngine to be use in camera path and track 2026-02-26 01:19:56 +01:00
terrainwax 68e93a13d8 feat: New track system , Scripted GUI , BoneFocus , BattleIntro , StadiumBlock 2026-02-26 01:18:35 +01:00
terrainwax 611bfd9792 chore: improve error logging and remove dead code in camera engine 2026-02-26 01:18:13 +01:00
terrainwax 0c428526e7 fix: clear BoneFocusResolver smoothing state when cameras stop 2026-02-26 01:16:47 +01:00
terrainwax b28355b8f2 refactor: deduplicate entity resolution logic across camera focus handlers 2026-02-26 01:13:24 +01:00
terrainwax 8e163d0e14 chore: remove dead coordinator factory from CameraPluginRegistry 2026-02-26 01:05:44 +01:00
terrainwax 1660a53867 refactor: consolidate duplicate timeline registries into single TimelineRegistry 2026-02-26 01:05:03 +01:00
terrainwax c50ae2d478 chore: remove unused imports in camera engine 2026-02-26 01:04:21 +01:00
terrainwax 497de9a8f1 chore: remove CameraMetrics, CameraConfig, and CameraResult abstractions 2026-02-26 01:03:30 +01:00
terrainwax f57300dc18 chore: remove unused CameraTimelineBuilder 2026-02-26 01:02:47 +01:00
terrainwax 0e3c197d9b chore: remove unused CameraAPI facade, update tests 2026-02-26 01:02:12 +01:00
terrainwax 4a12a39287 fix: remove debug println and trailing blank lines in camera handlers 2026-02-26 01:00:05 +01:00
terrainwax 2da2222540 feat: cameraAPI and stadium camera 2026-01-04 19:22:44 +01:00
8892 changed files with 124951 additions and 620718 deletions
-3
View File
@@ -24,7 +24,6 @@ eclipse
run
runClient
runServer
utilityscripts/implementation/*.txt
# Files from Forge MDK
forge*changelog.txt
@@ -37,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 -6
View File
@@ -16,7 +16,6 @@ check_assets_cache:
stage: preprocessing
script:
- echo "Checking if cached hash matches current hash"
- yum install -y findutils
# Calculate the current hash
- find common/src/main/resources/assets common/src/main/resources/data -type f -exec sha256sum {} + | sort -k 2 | sha256sum > current_hash.txt
@@ -72,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
@@ -149,7 +148,6 @@ build:
dependencies:
- replace_resources
script:
- yum install -y findutils
- export GRADLE_OPTS="-Xmx4G -Xms4G"
- chmod +x ./gradlew
- ./gradlew build
@@ -165,7 +163,6 @@ publish:
rules:
- if: '$CI_PROJECT_PATH == "cable-mc/cobblemon" && $CI_COMMIT_BRANCH == "main"'
script:
- yum install -y findutils
- export GRADLE_OPTS="-Xmx4G -Xms4G"
- chmod +x ./gradlew
- ./gradlew publish
@@ -179,7 +176,6 @@ deploy_artifacts:
dependencies:
- build
script:
- yum install -y findutils
- echo "Collecting artifacts..."
- find neoforge/build/libs/ -type f -name "*.jar" | grep -vE "dev-shadow|sources|javadoc" | xargs -I {} mv {} ./
- find fabric/build/libs/ -type f -name "*.jar" | grep -vE "dev-shadow|sources|javadoc" | xargs -I {} mv {} ./
+21 -479
View File
@@ -1,518 +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 Pokémon (+-5%). Some rendering cases, such as shoulder mounting, revert to 1.0 scaling to preserve animation accuracy.
- Added scaling to "baby" Pokémon (smaller (-20%) at level 1 → normally sized at level 10)
- Added conditional seats for riding. Seats can be gated by molang such as "is_alpha".
- Added Alpha Pokémon
- Added alpha scaling (alphas are larger than their normal counterparts)
- Alphas will always lead herds and will spawn in herds
- Alphas spawn with two random TM moves from their learnset
- Alpha Pokémon will always defend themselves from players that attack them
- Added alpha level matching and stat boost (based on level)
- Added pitch-down and reverb to alpha cries
- Added alpha Pokémon bait effect
- Added alpha eye rendering including eye overlay, bloom, eye trail, and particles.
- Added cobweb block slowness immunity for spider Pokémon
- Added 49 new habitat structures. 34 as typical Minecraft structures and 15 as structures that get placed as biome features to allow for more natural placement in generation. Structures placed as biome features will not be able to be located with the `/locate structure` command.
- Vanilla village house template pools have been adjusted to account for the new abandoned village structure habitat. Weights have changed within the JSON file for every piece to ensure too many habitats don't generate in a single village.
- Added 10 new ruin structures.
- rooted_arch_ruins & crumbling_arch_ruins have been transferred over to biome features, allowing for better placement in generation.
- All ruin structures that did not generate with a gimmighoul/gilded chest before will now generate with one.
- All ruin structures gilded chest loot tables have been updated to include a guaranteed TM not learned naturally by any Pokémon through their level-up moveset. Each ruin gets its own specific TM, so you can now go hunting ruins to complete your move dex!
- All fossil structures have been transferred over to biome features, allowing for better placement in generation.
- The prehistoric_underwater_fissure & prehistoric_submerged_impact fossil structures now use the biome tag `#cobblemon:is_temperate_ocean`.
- Gimmighoul towers now generate at ground level rather than sinking one block into the ground.
- A new shipwreck cove structure called the "magma_shipwreck_cove" has been added.
- Shipwreck coves now generate slightly more frequently.
- Added equipment loot tables for shipwreck cove enemies.
- Maps from abandoned fishing boats leading to shipwreck coves now will skip explored chunks, allowing the player to find more shipwreck coves easier.
- The lush shipwreck cove's phantom mob spawner has been replaced with breezes.
- The lush shipwreck cove structure itself has been slightly cleaned up to be less noisy with the blocks it is built out of.
- All shipwreck coves' spawners have been adjusted to give compatible mobs enchanted iron or diamond armour and weapons while in its ominous state. The armour given to the mobs will also use the automaton trim. We upped the difficulty because it was too easy now that your Pokémon can fight for you.
- One TM of the elemental hyper beams has been added as guaranteed loot to the main treasure chest in every shipwreck cove. (Magma cove gets blast burn, lush cove gets frenzy plant, and submerged cove gets hydro cannon)
- Added 7 new advancements
- Pressing the inventory key now closes the Pokémon interact wheel GUI
- Pokémon cries now play when clicking Pokémon in the summary screen.
- Pressing the Summary key while either the starter selection or summary screen are open now closes those screens
- Ice type Pokémon are now immune to Packed Snow
- Held item visibility can now be controlled by the held_item_visible command argument.
### New rideable Pokémon
- Crobat
- Skarmory
- Milotic
- Archeops
- Goodra
- Goodra (Hisuian Form)
- Drampa
- Duraludon
- Archaludon
### Pokémon Added
#### Generation 3
- Wurmple
- Silcoon
- Beautifly
- Cascoon
- Dustox
- Skitty
- Delcatty
- Seviper
- Zangoose
#### Generation 4
- Cherubi
- Cherrim
- Stunky
- Skuntank
#### Generation 5
- Audino
- Pawniard
- Bisharp
#### Generation 6
- Pancham (+ Cosmetic with Black Glasses)
- Pangoro (+ Cosmetic with Black Glasses)
#### Generation 7
- Rockruff
- Lycanroc
#### Generation 8
- Rolycoly
- Carkol
- Coalossal
- Runerigus
- Snom
- Frosmoth
- Indeedee
- Duraludon
- Applin
- Flapple
- Appletun
#### Generation 9
- Nymble
- Lokix
- Kingambit
- Dipplin
- Archaludon
- Hydrapple
#### Regional Forms
- Sandshrew (Alolan Form)
- Sandslash (Alolan Form)
- Yamask (Galarian Form)
- Avalugg (Hisuian Form)
### Model updates for the following Pokémon
- Sandshrew
- Sandslash
- Nidoking
- Zubat
- Golbat
- Crobat
- Ditto
- Porygon
- Porygon2
- Porygon-Z
- Skarmory
- Smoochum
- Jynx
- Popplio
- Brionne
- Primarina
- Fletchling
- Fletchinder
- Talonflame
- Sableye
- Mawile
- Goomy
- Sliggoo
- Goodra
- Nacli
- Naclstack
- Garganacl
- Bidoof
- Cranidos
- Chatot
- Yamask
- Cofagrigus
- Tyranitar
- Turtonator
- Golett
- Corsola
- Arbok (New Patterns: Angry, Cartoon, Clown, Comedic, Crow, Grin, Ink, Modern, Rain, Scribble, Silence, Sloth, Suspicious, Will)
- ... and Substitute
#### Model changes to the regional bias resource pack:
- Added Mime Jr. (Galar Bias)
- Added Bergmite (Hisui Bias)
- Changed models for every existing regional bias
### Animation updates for the following Pokémon
- Kakuna
- Grimer
- Nidoran♀ (+ New shoulder mount)
- Nidorina
- Nidoran♂ (+ New shoulder mount)
- Sandshrew
- Diglett
- Dugtrio
- Graveler
- Happiny
- Chansey
- Blissey
- Poliwhirl
- Poliwrath
- Zubat
- Vulpix
- Drowzee
- Hypno
- Noctowl
- Skarmory
- Skitty
- Flygon
- Shellos
- Riolu
- Lucario
- Purrloin
- Liepard
- Joltik
- Galvantula
- Nidoking
- Fletchling
- Fletchinder
- Talonflame
- Archeops
- Drampa
- Sewaddle
- Swadloon
- Leavanny
- Goomy
- Sliggoo
- Goodra
- Shedinja
- Skwovet
- Greedent
- Bidoof
- Cranidos
- Chatot
- Yamask
- Cofagrigus
- Luvdisc
- Staryu
- Starmie
- Togetic
- Rellor
- Rabsca
- Yungoos
- Gumshoos
- Cottonee
- Whimsicott
- Corsola
#### Added sounds to the following moves
- Absorb
- Aurora Beam
- Double Team
- Draco Meteor
- Fire Punch
- Fire Spin
- Flame Charge
- Flame Wheel
- Giga Drain
- Ice Punch
- Leech Life
- Magical Leaf
- Mega Drain
- Minimize
- Poison Gas
- Rock Throw
- Scary Face
- Seed bomb
- Smokescreen
- String Shot
- Tail Whip
### Changes
- Stat increase and decrease particles have been changed, with the addition of alpha boost particles.
- Dragonite's mailbag cosmetic can now also be obtained with a chest in the cosmetic slot
- Adjusted Croagunk's walkspeed
- Adjusted Aerodactyl's walkspeed
- Sharp Beak can now be crafted
- Metal Alloy can now be crafted
- Ability Capsules can now be brewed
- Using an Ability Capsule will now return an Empty Bottle
- The following items can now be obtained by brushing the following Pokémon:
- Pink Wool - Wigglytuff
- White Wool - Swablu, Altaria
- Shed Shell - Ekans, Arbok, Dratini, Dragonair, Dunsparce, Treecko, Grovyle, Sceptile, Seviper, Scraggy, Scrafty, Helioptile, Heliolisk, Salandit, Salazzle, Sobble, Drizzile, Inteleon, Silicoblra, Sandaconda, Toxel, Toxtricity, Orthworm, Dudunsparce
- Phantom Membrane - Dreepy, Drakloak, Dragapult
- The following items can now be obtained by evolving the following Pokémon into any of their evolutions:
- Turtle Scute - Squirtle, Wartortle, Turtwig, Grotle, Tirtouga, Chewtle
- Shed Shell - Metapod, Kakuna, Ekans, Paras, Venonat, Krabby, Scyther, Kabuto, Dratini, Dragonair, Ledyba, Spinarak, Yanma, Pineco, Dunsparce, GLigar, Pupitar, Treecko, Grovyle, Silcoon, Cascoon, Surskit, Nincada, Trapinch, Vibrava, Corphish, Anorith, Bagon, Shelgon, Kricketot, Burmy, Combee, Skorupi, Swadloon, Whirlipede, Dwebble, Scraggy, Karrablast, Joltik, Larvesta, Spewpa, Clauncher, Helioptile, Charjabug, Crabrawler, Cutiefly, Dewpider, Salandit, Wimpod, Poipole, Sobble, Drizzile, Dottler, Silicobra, Toxel, Sizzlipede, Snom, Tarountula, Nymble, Rellor
- Saccharine Leaves now drop Tart, Sweet, or Syrupy Apples, depending on their honey coating stage, instead of vanilla Apples
- Fixed the height of the Tumblestone Bud items when held in hand, placed on head, viewed in a GUI, and placed in an Item Frame
- Refactored all advancements, removing several deemed unnecessary. Due to this refactor, all previously obtained advancements will be reset besides the root (for picking a starter Pokémon). We are sorry for the inconvience, but it makes the tab cleaner and simplifies future advancements.
- Restructured Botany Pots integration folder structure
- Decrease the default pastured mon wander radius to 32.
- Factor in the weight of Pokémon when calculating pushing forces between hitboxes.
- 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 Armour and Upgrades can be Haunted using Create's crafting systems
- Applin Apples can be used to make Honeyed Apples in Create
- Golden Apples can now be crafted using the Applin Apples
- Prismarine Crystals and Amethyst Shards can now be used to craft Pokedexes
- Updated Pokédex Screens item tag to support tagged variants of Redstone and Glowstone Dust
- Sounds for interacting with various Cobblemon blocks now play from the centre of the block, rather than the corner
- The `/pc` command no longer plays PC activation sounds
### Fixes
- Fixed Blitzle's base scale being `1` rather than the intended `0.8`
- Fixed Magnezone's emissive layer for the shiny having the wrong name, thus displaying wrongly
- Fixed Litleo not blinking
- Fixed Omanyte and Omastar incorrectly using water animations on land.
- Fixed Pokédex layout for genderless variations
- Fixed odd stacking behaviour with the poke_bait item.
- 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 gilded chests being more accessible than intended
- Fixed fished up Pokémon not being added as "seen" to the Pokedex
- Fixed surprised Gimmighoul encounters not being added as "seen" to the Pokedex
- Fixed a crash involving berry trees that have been world edited.
- Fixed visual desync in multiplayer for players mounting Pokémon outside other players' loaded chunks
- Fixed Pokémon entities leveled by rare candy not getting stronger as Minecraft entities unless recalled.
- Fixed a freeze during game load that occurred when multiple mods tried to load Cobblemon API classes at the same time.
- Fixed random tick crashes from berry trees
- Lowered the volume of Blitzle and Zebstrika's cry
- Regional Foods will now prioritise being eaten instead of used for healing Pokémon, shift clicking will use it as healing
- Resolved Z-Fighting on Pokédex Models
- Restored Ninjask's ability to do barrel rolls
- Fixed Raichu (male) and Alolan Raichu (both genders) not displaying the correct Partner Cap when Pewter Crunchies are in the cosmetic slot.
- Fixed a crash caused by adding enchanted items to the Resurrection Machine
- Fixed a crash that could occur when starting a PvN battle with only fainted Pokémon.
- Fixed the Remember Riding Camera config not being respected when dismounting and resolved the incompatibility with the Auto Third Person mod
- Fixed Full Heal block not dropping Full Heals
- Fixed status cure items not returning an empty bottle when used
- Fixed Panpour consuming its held item when evolving with a Water Stone
- Fixed Smoked Tail Curry returning a Wooden Bowl when consumed despite it not being included in the recipe
- Fixed `/giveallpokemon` not respecting the min and max parameters
- Fixed the choose starter toast not disappearing when capturing a Pokémon instead of opening the starter selection screen
- Fixed an issue where certain Sinister Tea seasonings were only applying as instant effects and would not apply over time
- Long config option labels now scroll instead of overflowing
### Developer
- Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters
- Added `FishingSpawnerFactory` which allows influence registration on fishing encounters
- Added `BattleFleeAttemptEvent` fired from `PokemonBattle.checkFleeAttempt`, allowing developers to intercept and control flee attempts
- Tweaked the spawning selector interface to take a bucket function rather than a bucket directly.
- Added a new `cobblemon:height_range` processor type to allow developers to restrict structure-spawning ranges.
- Separated all the functions of MoLangFunctions.kt into their own files.
- Spawn buckets and their base weights are now provided by the spawner, with different base weights for world, fishing, snack, and habitat spawn buckets.
- Removed "SpawnBucket" class in favour of just storing maps of bucket names to weights.
- Fixed nicknames not being applied to Pokémon spawned from `spawn_pool_world` files.
- Added support for `profilePokedexScale`, `profilePokedexTranslation`, `profileSummaryScale`, and `profileSummaryTranslation` in poser files for finer control over profile displays.
- Fixed riding camera offsets defined on Pokémon forms not being applied
### Molang & Datapacks
- Added `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 `has_tm_move_unlocked`, `unlock_tm_move`, & `lock_tm_move` as available Molang functions for Players
- Added `marks`, `has_mark`, & `remove_marks` as available Molang functions for Pokémon
- Added Data Component support for species drops, eg `"components": {"minecraft:custom_model_data": 123}`
- Added the new `sortOrder` field to mark entries, (defaults to 0 if not specified) which controls the order in which marks are rendered on the HUD, with higher numbers being rendered on top of lower numbers
- Added optional `order` field to starter categories. It will allow explicitly sorting categories in the starter selection screen
- SpeciesAdditions append vs override now matches wiki documentation
- Added `weighted_choice` as a new feature type. It requires setting weights for each choice and allows developers to simplify the process of assigning random aspect choice based on marble chance
- Added `q.world.spawn_loot_table_items(loot_table_id, x, y, z)` which spawns the items from the loot table at the position. (Use type: "minecraft:chest" for the loot table.)
- Added `q.pokemon.entity` to access the entity of Pokémon.
- Flamethrower and Seismic Toss had their action effect JSON cleaned up
- Ride sounds can now be set as exclusive to passengers
- Ride sounds no longer play when submerged unless specified with a new setting
- NPC Trainers can now call a `command` animation when issuing a command in battle.
- Added a q.failed function to move action effects.
- Herd spawned Pokémon have their levels controlled differently. The `levelRange` property now acts as a clamp instead of a filter, with a new `herdLevelRange` property to do the filter behaviour.
- `q.sound` now accepts three parameters (sound event, volume and pitch) instead of just the sound event and pitch.
### Particles
#### New Move Animations
- Blizzard
- Disarming Voice
- Draco Meteor
- Dragon Breath
- Dragon Rage
- Drain Punch
- Fairy Wind
- Fire Fang
- Gust
- Horn Drill
- Hydro Pump
- Ice Fang
- Leafage
- Leaf Storm
- Light Screen
- Night Shade
- Play Rough
- Pyro Ball
- Reflect
- Rock Slide
- Sonic Boom
- Sucker Punch
- Supersonic
- Thunder Fang
#### Tweaks and Fixes (Moves)
- Confusion
- Dragon Claw
- Eruption
- Fire Punch
- Flame Burst
- Flame Charge
- Flamethrower
- Frost Breath
- Hypnosis
- Ice Punch
- Icy Wind
- Mud Sport
- Poison Fang
- Protect
- Psychic
- Quick Attack
- Rage Powder
- Razor Leaf
- Sand Attack
- Scratch
- Shadow Claw
- Sing
- Spore
- String Shot
- Tackle
- Tail Whip
- Thunder
- Thunder Punch
- Thunder Wave
- Water Gun
- Water Pulse
- Water Sport
#### Miscellaneous Particles
+ Added Wild Might particles for Alpha Pokémon
+ Updated stat increase and decrease particles
- Added `marks`, `has_mark`, & `remove_marks` as available Molang functions for pokemon
### Localization
- Updated translations for:
- Japanese
- German
- Spanish
- Ecuadorian Spanish
- Portuguese
- Brazilian Portuguese
- Simplified Chinese
- Traditional Chinese
- Korean
-9
View File
@@ -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.
+510
View File
@@ -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]
}
]
}
]
}
]
}
-4
View File
@@ -72,10 +72,6 @@ tasks.withType<Test> {
}
}
tasks.processResources {
exclude("data/**/README.md")
}
sourceSets {
main {
blossom {
@@ -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;
}
}
@@ -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());
}
}
@@ -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;
}
}
}
@@ -36,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,133 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.mixin.accessor.SinglePoolElementAccessor;
import com.cobblemon.mod.common.world.structureprocessors.HeightRangeStructureProcessor;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.pools.*;
import net.minecraft.world.level.levelgen.structure.pools.alias.PoolAliasLookup;
import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Optional;
@Mixin(JigsawPlacement.class)
public class JigsawPlacementMixin {
@Unique
private static final Logger cobblemon$LOGGER = LoggerFactory.getLogger("Cobblemon/HeightRangeVeto");
@Inject(
method = "addPieces(Lnet/minecraft/world/level/levelgen/structure/Structure$GenerationContext;Lnet/minecraft/core/Holder;Ljava/util/Optional;ILnet/minecraft/core/BlockPos;ZLjava/util/Optional;ILnet/minecraft/world/level/levelgen/structure/pools/alias/PoolAliasLookup;Lnet/minecraft/world/level/levelgen/structure/pools/DimensionPadding;Lnet/minecraft/world/level/levelgen/structure/templatesystem/LiquidSettings;)Ljava/util/Optional;",
at = @At("RETURN"),
cancellable = true
)
private static void onAddPieces(
Structure.GenerationContext context,
Holder<StructureTemplatePool> startPool,
Optional<ResourceLocation> startJigsawName,
int maxDepth,
BlockPos pos,
boolean useExpansionHack,
Optional<Heightmap.Types> projectStartToHeightmap,
int maxDistanceFromCenter,
PoolAliasLookup aliasLookup,
DimensionPadding dimensionPadding,
LiquidSettings liquidSettings,
CallbackInfoReturnable<Optional<Structure.GenerationStub>> cir
) {
Optional<Structure.GenerationStub> result = cir.getReturnValue();
if (result.isEmpty()) {
return;
}
BlockPos spawnPos = result.get().position();
String poolId = startPool.unwrapKey().map(key -> key.location().toString()).orElse("unknown");
boolean canPlace = cobblemon$canStructurePlace(context, startPool, spawnPos);
cobblemon$LOGGER.info(
"[HeightRangeVeto] addPieces evaluated requestedPos={} spawnPos={} startPool={} -> canPlace={}",
pos, spawnPos, poolId, canPlace
);
if (canPlace) {
return;
}
cobblemon$LOGGER.info(
"[HeightRangeVeto] Vetoing structure start at {} (startPool={})",
spawnPos, poolId
);
cir.setReturnValue(Optional.empty());
}
@Unique
private static boolean cobblemon$canStructurePlace(
Structure.GenerationContext generationContext,
Holder<StructureTemplatePool> startPool,
BlockPos startPos
) {
for (StructurePoolElement element : startPool.value().templates) {
Optional<HeightRangeStructureProcessor> heightRangeProcessor = cobblemon$getHeightRangeProcessor(element);
if (heightRangeProcessor.isEmpty()) {
continue;
}
HeightRangeStructureProcessor processor = heightRangeProcessor.get();
boolean valid = processor.isValidPlacement(
generationContext.chunkGenerator(),
generationContext.heightAccessor(),
generationContext.randomState(),
startPos
);
cobblemon$LOGGER.info(
"[HeightRangeVeto] template={} pos={} valid={}",
element, startPos, valid
);
if (!valid) {
return false;
}
}
return true;
}
@Unique
private static Optional<HeightRangeStructureProcessor> cobblemon$getHeightRangeProcessor(StructurePoolElement element) {
if (!(element instanceof SinglePoolElement)) {
return Optional.empty();
}
StructureProcessorList processorList = ((SinglePoolElementAccessor) element).getProcessors().value();
for (StructureProcessor processor : processorList.list()) {
if (processor instanceof HeightRangeStructureProcessor heightRangeStructureProcessor) {
return Optional.of(heightRangeStructureProcessor);
}
}
return Optional.empty();
}
}
@@ -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);
}
@@ -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);
}
@@ -1,22 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.accessor;
import net.minecraft.core.Holder;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(SinglePoolElement.class)
public interface SinglePoolElementAccessor {
@Accessor("processors")
Holder<StructureProcessorList> getProcessors();
}
@@ -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);
}
}
@@ -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,22 +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)
@@ -32,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;
@@ -43,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;
@@ -66,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);
}
}
}
@@ -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;
}
}
}
@@ -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);
}
}
@@ -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,11 +93,9 @@ 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.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
@@ -108,7 +105,6 @@ import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.pokemon.properties.AspectPropertyType
import com.cobblemon.mod.common.pokemon.properties.BattleCloneProperty
import com.cobblemon.mod.common.pokemon.properties.FreezeFrameProperty
import com.cobblemon.mod.common.pokemon.properties.HeldItemVisibleProperty
import com.cobblemon.mod.common.pokemon.properties.HiddenAbilityPropertyType
import com.cobblemon.mod.common.pokemon.properties.LabelProperty
import com.cobblemon.mod.common.pokemon.properties.NoAIProperty
@@ -116,7 +112,13 @@ 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.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)
@@ -301,6 +302,7 @@ object Cobblemon {
HeldItemProvider.register(CobblemonHeldItemManager, Priority.LOWEST)
}
fun sendServerSettingsPacketToPlayer(player: ServerPlayer) {
ServerSettingsPacket(
this.config.preventCompletePartyDeposit,
@@ -309,9 +311,6 @@ object Cobblemon {
this.config.maxPokemonLevel,
this.config.maxPokemonFriendship,
this.config.maxDynamaxLevel,
this.config.unlockAllMoveDexMovesByDefault,
this.config.pokemonIntrinsicSizeMin,
this.config.pokemonIntrinsicSizeMax
).sendToPlayer(player)
}
@@ -325,11 +324,8 @@ object Cobblemon {
GENDER_ASPECT.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
@@ -351,7 +347,10 @@ object Cobblemon {
CustomPokemonProperty.register(UnaspectPropertyType)
CustomPokemonProperty.register(FreezeFrameProperty)
CustomPokemonProperty.register(NoAIProperty)
CustomPokemonProperty.register(HeldItemVisibleProperty)
// Tween & camera system initialization
EasingRegistry.registerBuiltinEasings()
CameraTrackDeserializers.registerBuiltinTracks()
CobblemonEvents.POKEMON_PROPERTY_INITIALISED.emit(Unit)
@@ -365,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()
@@ -373,7 +373,6 @@ object Cobblemon {
val server = event.server
MoLangLoadedFilesCache.initialize(server)
playerDataManager = PlayerInstancedDataStoreManager().also { it.setup(server) }
HabitatPools.onServerLoading(server)
val mongoClient: MongoClient?
@@ -386,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)
@@ -418,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.")
@@ -609,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))
}
}
@@ -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)
}
}
}
@@ -19,7 +19,6 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.PreparableReloadListener
import net.minecraft.tags.TagKey
import net.minecraft.world.inventory.MenuType
import net.minecraft.world.level.GameRules
import net.minecraft.world.level.ItemLike
import net.minecraft.world.level.biome.Biome
@@ -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
@@ -405,7 +410,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val BIG_MALASADA = regionalFoodItem("big_malasada", 64, 7, 0.3f, false)
@JvmField
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 64, 10, 0.6f, false)
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 64, 10, 0.6f, false, ItemStack(Items.BOWL, 1))
@JvmField
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 64, 7, 0.3f, false)
@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)
@@ -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,11 +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.gui.SynchronizeActiveTMHandler
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
@@ -46,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
@@ -72,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
@@ -84,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
@@ -91,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
@@ -105,9 +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.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
@@ -132,7 +151,6 @@ import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.ChangeP
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.RequestPCBoxWallpapersPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.SetPCBoxWallpapersPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.UnlockPCBoxWallpaperPacket
import com.cobblemon.mod.common.net.messages.client.tms.TechnicalMachineRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.toast.ToastPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeAcceptanceChangedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeCancelledPacket
@@ -145,17 +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.client.ui.SynchronizeActiveTMPacket
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
@@ -196,7 +209,6 @@ import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetNicknamePa
import com.cobblemon.mod.common.net.messages.server.pokemon.update.evolution.AcceptEvolutionPacket
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
@@ -223,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
@@ -236,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
@@ -254,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
@@ -263,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.*
@@ -342,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()))
@@ -377,7 +390,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(PlayerInteractOptionsPacket.ID, PlayerInteractOptionsPacket::decode, PlayerInteractOptionsHandler))
list.add(PacketRegisterInfo(PokedexUIPacket.ID, PokedexUIPacket::decode, PokedexUIPacketHandler))
list.add(PacketRegisterInfo(ExpGainedDataPacket.ID, ExpGainedDataPacket::decode, ExpGainedDataPacketHandler))
list.add(PacketRegisterInfo(SynchronizeActiveTMPacket.ID, SynchronizeActiveTMPacket::decode, SynchronizeActiveTMHandler))
// Starter packets
list.add(PacketRegisterInfo(OpenStarterUIPacket.ID, OpenStarterUIPacket::decode, StarterUIPacketHandler))
@@ -403,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))
@@ -443,7 +531,11 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(BehaviourSyncPacket.ID, BehaviourSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(MarkRegistrySyncPacket.ID, MarkRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(RideSettingsSyncPacket.ID, RideSettingsSyncPacket::decode, RideSettingsSyncHandler))
list.add(PacketRegisterInfo(TechnicalMachineRegistrySyncPacket.ID, TechnicalMachineRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
// 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))
@@ -474,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))
@@ -506,18 +613,16 @@ object CobblemonNetwork {
// Pokédex scanning
list.add(PacketRegisterInfo(ServerConfirmedRegisterPacket.ID, ServerConfirmedRegisterPacket::decode, ServerConfirmedRegisterHandler))
// 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
}
@@ -597,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))
@@ -630,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 ->
@@ -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)))
}
@@ -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,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
}
@@ -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)
@@ -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
}
}
@@ -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") {
@@ -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()
@@ -137,6 +139,7 @@ open class PokemonBattle(
var dispatchResult = GO
val dispatches = ConcurrentLinkedDeque<BattleDispatch>()
val afterDispatches = mutableListOf<() -> Unit>()
val captureActions = mutableListOf<BattleCaptureAction>()
@@ -399,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) {
@@ -453,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()
@@ -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
@@ -24,8 +26,9 @@ import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacke
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import java.util.UUID
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<*>) {}
@@ -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()
}
@@ -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()
}
}
@@ -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
}
@@ -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?
)
@@ -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)
}
}
@@ -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()
)
}
}
@@ -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) }
}
}
@@ -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]
}
}
@@ -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
}
@@ -0,0 +1,59 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.camera.model
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.camera.CameraRotation
import com.cobblemon.mod.common.api.molang.ExpressionLike
import net.minecraft.world.phys.Vec3
/**
* Type-safe track evaluation result.
*/
sealed class TrackOutput {
data class Position(val position: Vec3) : TrackOutput()
data class Focus(val target: CameraFocus) : TrackOutput()
data class Rotation(val rotation: CameraRotation) : TrackOutput()
data class Event(val expression: ExpressionLike, val eventId: String?) : TrackOutput()
}
/**
* Camera track interface - produces outputs over time.
*/
interface CameraTrack {
val trackType: String
val duration: Float
fun evaluate(
time: Float,
context: Map<String, MoValue>,
runtime: MoLangRuntime,
origin: Vec3
): List<TrackOutput>
}
enum class TrackCapability {
POSITION,
FOCUS,
ROTATION,
EVENTS
}
fun CameraTrack.capabilities(): Set<TrackCapability> {
val outputs = evaluate(0f, emptyMap(), MoLangRuntime(), Vec3.ZERO)
return outputs.mapTo(mutableSetOf()) { output ->
when (output) {
is TrackOutput.Position -> TrackCapability.POSITION
is TrackOutput.Focus -> TrackCapability.FOCUS
is TrackOutput.Rotation -> TrackCapability.ROTATION
is TrackOutput.Event -> TrackCapability.EVENTS
}
}
}
@@ -0,0 +1,222 @@
/*
* 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.registry
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.camera.CameraTrackRegistry
import com.cobblemon.mod.common.api.camera.model.*
import com.cobblemon.mod.common.util.asExpressionLike
import com.google.gson.*
import java.lang.reflect.Type
/**
* JSON deserializer for Timeline (V2 format).
*
* Deserializes directly into the immutable Timeline model without legacy conversion.
*/
object TimelineDeserializer : JsonDeserializer<Timeline> {
override fun deserialize(json: JsonElement, typeOfT: Type, ctx: JsonDeserializationContext): Timeline {
val obj = json.asJsonObject
val id = obj.get("timelineId")?.asString
?: throw JsonParseException("Timeline missing 'timelineId' field")
val startStateId = obj.get("startStateId")?.asString
?: throw JsonParseException("Timeline '$id' missing 'startStateId' field")
val statesObj = obj.get("states")?.asJsonObject
?: throw JsonParseException("Timeline '$id' missing 'states' object")
val transitionsArray = obj.get("transitions")?.asJsonArray ?: JsonArray()
// Parse parameters (optional)
val parametersObj = obj.get("parameters")?.asJsonObject
val parameters = parametersObj?.entrySet()?.associate { (paramName, paramValue) ->
try {
paramName to deserializeParameter(paramValue.asJsonObject, paramName, id)
} catch (e: Exception) {
Cobblemon.LOGGER.error("[Timeline] Error parsing parameter '$paramName' in timeline '$id': ${e.message}")
throw JsonParseException("Invalid parameter '$paramName' in timeline '$id'", e)
}
} ?: emptyMap()
val states = statesObj.entrySet().associate { (key, value) ->
try {
key to deserializeState(value.asJsonObject, id)
} catch (e: Exception) {
Cobblemon.LOGGER.error("[Timeline] Error parsing state '$key' in timeline '$id': ${e.message}")
throw JsonParseException("Invalid state '$key' in timeline '$id'", e)
}
}
val transitions = transitionsArray.mapIndexed { index, element ->
try {
deserializeTransition(element.asJsonObject, id)
} catch (e: Exception) {
Cobblemon.LOGGER.error("[Timeline] Error parsing transition $index in timeline '$id': ${e.message}")
throw JsonParseException("Invalid transition $index in timeline '$id'", e)
}
}
return Timeline(
id = id,
startStateId = startStateId,
parameters = parameters,
states = states,
transitions = transitions
)
}
private fun deserializeParameter(obj: JsonObject, paramName: String, timelineId: String): ParameterDefinition {
val typeStr = obj.get("type")?.asString
?: throw JsonParseException("Parameter '$paramName' missing 'type' field")
val type = when (typeStr.lowercase()) {
"number" -> ParameterType.NUMBER
"vec3" -> ParameterType.VEC3
else -> throw JsonParseException("Parameter '$paramName' has unknown type '$typeStr'. Valid types: number, vec3")
}
val description = obj.get("description")?.asString
val default: ParameterDefault? = when {
!obj.has("default") -> null
type == ParameterType.NUMBER -> {
val defaultValue = obj.get("default")
if (defaultValue.isJsonPrimitive && defaultValue.asJsonPrimitive.isNumber) {
ParameterDefault.NumberDefault(defaultValue.asDouble)
} else {
throw JsonParseException("Parameter '$paramName' default must be a number")
}
}
type == ParameterType.VEC3 -> {
val defaultObj = obj.get("default")
if (defaultObj.isJsonObject) {
val vec = defaultObj.asJsonObject
val x = vec.get("x")?.asDouble
?: throw JsonParseException("Parameter '$paramName' vec3 default missing 'x' field")
val y = vec.get("y")?.asDouble
?: throw JsonParseException("Parameter '$paramName' vec3 default missing 'y' field")
val z = vec.get("z")?.asDouble
?: throw JsonParseException("Parameter '$paramName' vec3 default missing 'z' field")
ParameterDefault.Vec3Default(x, y, z)
} else {
throw JsonParseException("Parameter '$paramName' vec3 default must be an object with x, y, z fields")
}
}
else -> null
}
return ParameterDefinition(
type = type,
description = description,
default = default
)
}
private fun deserializeState(obj: JsonObject, timelineId: String): State {
val stateId = obj.get("stateId")?.asString
?: throw JsonParseException("State missing 'stateId' field")
val tracksArray = obj.get("tracks")?.asJsonArray
?: throw JsonParseException("State '$stateId' missing 'tracks' array")
val tracks = tracksArray.mapIndexedNotNull { index, element ->
try {
deserializeTrack(element.asJsonObject)
} catch (e: Exception) {
Cobblemon.LOGGER.error("[Timeline] Error parsing track $index in state '$stateId': ${e.message}", e)
null
}
}
val duration = obj.get("duration")?.asFloat
val isLooping = obj.get("isLooping")?.asBoolean ?: false
val speed = obj.get("speed")?.asFloat ?: 1.0f
val rememberProgress = obj.get("rememberProgress")?.asBoolean ?: false
val renderPlayerBody = obj.get("renderPlayerBody")?.asBoolean ?: true
val hiddenHudElements = when (val hideHudElement = obj.get("hideHud")) {
null -> emptySet()
is JsonPrimitive -> {
if (hideHudElement.isBoolean) {
if (hideHudElement.asBoolean) HudElement.ALL else emptySet()
} else {
Cobblemon.LOGGER.warn("[Timeline] Invalid hideHud value in state '$stateId', expected boolean or array")
emptySet()
}
}
is JsonArray -> {
hideHudElement.mapNotNull { element ->
val id = element.asString
HudElement.fromId(id) ?: run {
Cobblemon.LOGGER.warn("[Timeline] Unknown HUD element '$id' in state '$stateId'")
null
}
}.toSet()
}
else -> {
Cobblemon.LOGGER.warn("[Timeline] Invalid hideHud value in state '$stateId', expected boolean or array")
emptySet()
}
}
return State(
id = stateId,
tracks = tracks,
duration = duration,
isLooping = isLooping,
speed = speed,
rememberProgress = rememberProgress,
renderPlayerBody = renderPlayerBody,
hiddenHudElements = hiddenHudElements
)
}
private fun deserializeTrack(obj: JsonObject): CameraTrack {
val trackType = obj.get("trackType")?.asString
?: throw JsonParseException("Track missing 'trackType' field")
return CameraTrackRegistry.deserializeTrack(trackType, obj)
?: throw JsonParseException("Unknown track type: $trackType")
}
private fun deserializeTransition(obj: JsonObject, timelineId: String): Transition {
val sourceStateId = obj.get("sourceStateId")?.asString
?: throw JsonParseException("Transition missing 'sourceStateId' field")
val targetStateId = obj.get("targetStateId")?.asString
?: throw JsonParseException("Transition missing 'targetStateId' field")
val conditionStr = obj.get("condition")?.asString
?: throw JsonParseException("Transition from '$sourceStateId' to '$targetStateId' missing 'condition' field")
val blendDuration = obj.get("blendDuration")?.asFloat ?: 0.4f
val transitionType = obj.get("transitionType")?.asString?.let { typeStr ->
when (typeStr.lowercase()) {
"blend" -> TransitionType.BLEND
"instant" -> TransitionType.INSTANT
else -> {
Cobblemon.LOGGER.warn("[Timeline] Unknown transition type '$typeStr' in timeline '$timelineId', using BLEND")
TransitionType.BLEND
}
}
} ?: TransitionType.BLEND
return Transition(
sourceStateId = sourceStateId,
targetStateId = targetStateId,
condition = conditionStr.asExpressionLike(),
type = transitionType,
blendDuration = blendDuration
)
}
}
@@ -0,0 +1,117 @@
/*
* 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.registry
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.camera.model.Timeline
import com.cobblemon.mod.common.api.camera.result.ValidationError
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.GsonBuilder
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.ResourceManager
import java.io.InputStreamReader
/**
* Registry for loading Timeline (V2 format) from JSON files.
*
* Loads timelines directly into the new immutable Timeline model
* without legacy conversion.
*/
object TimelineRegistry : DataRegistry {
override val id = cobblemonResource("camera_timelines")
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<TimelineRegistry>()
private val gson = GsonBuilder()
.registerTypeAdapter(Timeline::class.java, TimelineDeserializer)
.create()
private val timelines = mutableMapOf<String, Timeline>()
override fun reload(manager: ResourceManager) {
timelines.clear()
val resources = manager.listResources("camera_timelines") { path ->
path.path.endsWith(".json")
}
for ((identifier, resource) in resources) {
try {
resource.open().use { stream ->
val reader = InputStreamReader(stream)
val timeline = gson.fromJson(reader, Timeline::class.java)
val errors = timeline.validate()
val hasErrors = errors.any { it.severity == ValidationError.Severity.ERROR }
if (hasErrors) {
Cobblemon.LOGGER.error("[TimelineRegistry] Validation failed for '$identifier':")
errors.forEach { error ->
val level = if (error.severity == ValidationError.Severity.ERROR) "ERROR" else "WARN"
Cobblemon.LOGGER.error(" [$level] ${error.path}: ${error.message}")
}
continue
}
if (errors.isNotEmpty()) {
Cobblemon.LOGGER.warn("[TimelineRegistry] Warnings for timeline '${timeline.id}':")
errors.forEach { Cobblemon.LOGGER.warn(" ${it.path}: ${it.message}") }
}
if (timelines.containsKey(timeline.id)) {
Cobblemon.LOGGER.warn("[TimelineRegistry] Duplicate timeline ID '${timeline.id}' in $identifier, overwriting")
}
timelines[timeline.id] = timeline
Cobblemon.LOGGER.debug("[TimelineRegistry] Loaded timeline '${timeline.id}' from $identifier")
}
} catch (e: Exception) {
Cobblemon.LOGGER.error("[TimelineRegistry] Error loading timeline from $identifier", e)
}
}
Cobblemon.LOGGER.info("[TimelineRegistry] Loaded ${timelines.size} timelines")
observable.emit(this)
}
override fun sync(player: ServerPlayer) {
// Timelines are server-driven, no sync needed
}
fun getTimeline(id: String): Timeline? {
val timeline = timelines[id]
if (timeline == null) {
Cobblemon.LOGGER.error("[TimelineRegistry] Timeline '$id' not found")
}
return timeline
}
fun hasTimeline(id: String): Boolean = timelines.containsKey(id)
fun registerTimeline(timeline: Timeline) {
val errors = timeline.validate()
if (errors.any { it.severity == ValidationError.Severity.ERROR }) {
Cobblemon.LOGGER.error("[TimelineRegistry] Cannot register invalid timeline '${timeline.id}':")
errors.forEach { Cobblemon.LOGGER.error(" ${it.path}: ${it.message}") }
return
}
if (timelines.containsKey(timeline.id)) {
Cobblemon.LOGGER.warn("[TimelineRegistry] Timeline '${timeline.id}' already registered, overwriting")
}
timelines[timeline.id] = timeline
Cobblemon.LOGGER.debug("[TimelineRegistry] Registered timeline '${timeline.id}' programmatically")
}
fun getAllTimelines(): Collection<Timeline> = timelines.values
}
@@ -0,0 +1,22 @@
/*
* 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.result
/**
* Validation error with location context.
*/
data class ValidationError(
val path: String,
val message: String,
val severity: Severity = Severity.ERROR
) {
enum class Severity { WARNING, ERROR }
override fun toString(): String = "[$severity] $path: $message"
}
@@ -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.track
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.camera.CameraTrackRegistry
import com.cobblemon.mod.common.api.tween.InterpolationMethod
import com.cobblemon.mod.common.api.camera.model.CameraFocus
import com.cobblemon.mod.common.api.camera.model.PositionExpression
import com.cobblemon.mod.common.util.asExpressionLike
import com.google.gson.JsonObject
object CameraTrackDeserializers {
/** Should be called during mod initialization. */
fun registerBuiltinTracks() {
CameraTrackRegistry.registerTrackType("position", ::deserializePositionTrack)
CameraTrackRegistry.registerTrackType("focus", ::deserializeFocusTrack)
CameraTrackRegistry.registerTrackType("event", ::deserializeEventTrack)
CameraTrackRegistry.registerTrackType("reference", ::deserializeReferenceTrack)
CameraTrackRegistry.registerTrackType("gui", ::deserializeGuiTrack)
Cobblemon.LOGGER.debug("[CameraTrackDeserializers] Registered built-in track types: position, focus, event, reference, gui")
}
/**
* Deserializes a position track from JSON.
*
* Expected JSON format:
* ```json
* {
* "trackType": "position",
* "keyframes": [
* {
* "time": 0.0,
* "position": { "x": 10.0, "y": 64.0, "z": 10.0 },
* "easing": "ease_in_out_quad"
* }
* ],
* "interpolationType": "bezier",
* "easing": "linear",
* "isRelative": false
* }
* ```
*/
private fun deserializePositionTrack(json: JsonObject): PositionTrack {
val keyframesArray = json.getAsJsonArray("keyframes")
?: throw IllegalArgumentException("Position track missing 'keyframes' array")
val keyframes = keyframesArray.map { element ->
val kfObj = element.asJsonObject
val time = kfObj.get("time")?.asFloat
?: throw IllegalArgumentException("Position keyframe missing 'time' field")
val positionElement = kfObj.get("position")
?: throw IllegalArgumentException("Position keyframe missing 'position' field")
val position = PositionExpression.fromJson(positionElement)
val easing = kfObj.get("easing")?.asString
PositionKeyframe(time, position, easing)
}
val interpolationTypeStr = json.get("interpolationType")?.asString ?: "linear"
val interpolationType = when (interpolationTypeStr.lowercase()) {
"linear" -> InterpolationMethod.LINEAR
"bezier", "cubic_bezier" -> InterpolationMethod.BEZIER
"hermite", "catmull_rom" -> InterpolationMethod.BEZIER // Map legacy types to available ones
else -> {
Cobblemon.LOGGER.warn("[CameraTrackDeserializers] Unknown interpolation type '$interpolationTypeStr', using LINEAR")
InterpolationMethod.LINEAR
}
}
val defaultEasing = json.get("easing")?.asString ?: "linear"
val isRelative = json.get("isRelative")?.asBoolean ?: false
return PositionTrack(keyframes, interpolationType, defaultEasing, isRelative)
}
/**
* Deserializes a focus track from JSON.
*
* Expected JSON format:
* ```json
* {
* "trackType": "focus",
* "keyframes": [
* {
* "time": 0.0,
* "target": { "type": "position", "x": 20.0, "y": 60.0, "z": 20.0 },
* "easing": "linear"
* }
* ],
* "easing": "linear"
* }
* ```
*/
private fun deserializeFocusTrack(json: JsonObject): FocusTrack {
val keyframesArray = json.getAsJsonArray("keyframes")
?: throw IllegalArgumentException("Focus track missing 'keyframes' array")
val keyframes = keyframesArray.map { element ->
val kfObj = element.asJsonObject
val time = kfObj.get("time")?.asFloat
?: throw IllegalArgumentException("Focus keyframe missing 'time' field")
val targetObj = kfObj.getAsJsonObject("target")
?: throw IllegalArgumentException("Focus keyframe missing 'target' field")
val target = CameraFocus.fromJson(targetObj)
val easing = kfObj.get("easing")?.asString
FocusKeyframe(time, target, easing)
}
val defaultEasing = json.get("easing")?.asString ?: "linear"
return FocusTrack(keyframes, defaultEasing)
}
/**
* Deserializes an event track from JSON.
*
* Expected JSON format:
* ```json
* {
* "trackType": "event",
* "events": [
* {
* "time": 0.5,
* "expression": "q.play_sound('cobblemon:poke_ball.open')",
* "eventId": "ball_open"
* }
* ]
* }
* ```
*/
private fun deserializeEventTrack(json: JsonObject): EventTrack {
val eventsArray = json.getAsJsonArray("events")
?: throw IllegalArgumentException("Event track missing 'events' array")
val events = eventsArray.map { element ->
val eventObj = element.asJsonObject
val time = eventObj.get("time")?.asFloat
?: throw IllegalArgumentException("Event missing 'time' field")
val expressionStr = eventObj.get("expression")?.asString
?: throw IllegalArgumentException("Event missing 'expression' field")
val eventId = eventObj.get("eventId")?.asString
TimedEvent(time, expressionStr.asExpressionLike(), eventId)
}
return EventTrack(events)
}
/**
* Deserializes a reference track from JSON.
*
* Expected JSON format:
* ```json
* {
* "trackType": "reference",
* "trackId": "intro_camera_movement"
* }
* ```
*/
private fun deserializeReferenceTrack(json: JsonObject): ReferenceTrack {
val trackId = json.get("trackId")?.asString
?: throw IllegalArgumentException("Reference track missing 'trackId' field")
return ReferenceTrack(trackId)
}
/**
* Deserializes a GUI track from JSON.
*
* Expected JSON format:
* ```json
* {
* "trackType": "gui",
* "actions": [
* {
* "time": 0.0,
* "action": "OPEN",
* "guiId": "cobblemon:battle_intro",
* "args": { "title": "Stadium Battle" },
* "argsExpressions": {
* "player_party_count": "variable.player_party_count"
* }
* },
* {
* "time": 5.0,
* "action": "CLOSE",
* "guiId": "cobblemon:battle_intro"
* }
* ]
* }
* ```
*/
private fun deserializeGuiTrack(json: JsonObject): GuiTrack {
val actionsArray = json.getAsJsonArray("actions")
?: throw IllegalArgumentException("GUI track missing 'actions' array")
val actions = actionsArray.map { element ->
val actionObj = element.asJsonObject
val time = actionObj.get("time")?.asFloat
?: throw IllegalArgumentException("GUI action missing 'time' field")
val actionType = actionObj.get("action")?.asString
?: throw IllegalArgumentException("GUI action missing 'action' field")
val guiId = actionObj.get("guiId")?.asString
?: throw IllegalArgumentException("GUI action missing 'guiId' field")
val args = mutableMapOf<String, String>()
actionObj.getAsJsonObject("args")?.entrySet()?.forEach { (key, value) ->
args[key] = value.asString
}
val argsExpressions = mutableMapOf<String, String>()
actionObj.getAsJsonObject("argsExpressions")?.entrySet()?.forEach { (key, value) ->
argsExpressions[key] = value.asString
}
TimedGuiAction(
time = time,
action = GuiActionType.valueOf(actionType.uppercase()),
guiId = guiId,
args = args,
argsExpressions = argsExpressions
)
}
return GuiTrack(actions)
}
}
@@ -0,0 +1,56 @@
/*
* 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.track
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.camera.model.CameraTrack
import com.cobblemon.mod.common.api.camera.model.TrackOutput
import com.cobblemon.mod.common.api.molang.ExpressionLike
import net.minecraft.world.phys.Vec3
/**
* Track that executes MoLang expressions at specific times.
*/
data class EventTrack(
val events: List<TimedEvent>
) : CameraTrack {
override val trackType = "event"
override val duration: Float
get() = events.maxOfOrNull { it.time } ?: 0f
override fun evaluate(
time: Float,
context: Map<String, MoValue>,
runtime: MoLangRuntime,
origin: Vec3
): List<TrackOutput> {
return emptyList()
}
init {
require(events.zipWithNext().all { (a, b) -> a.time <= b.time }) {
"EventTrack events must be sorted by time"
}
}
}
/**
* Timed event - execute expression at specific time.
*/
data class TimedEvent(
val time: Float,
val expression: ExpressionLike,
val eventId: String? = null
) {
init {
require(time >= 0f) { "Event time must be non-negative, got $time" }
}
}
@@ -0,0 +1,126 @@
/*
* 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.track
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.tween.EasingRegistry
import com.cobblemon.mod.common.api.tween.Interpolator
import com.cobblemon.mod.common.api.tween.Keyframe
import com.cobblemon.mod.common.api.camera.model.*
import net.minecraft.util.Mth
import net.minecraft.world.phys.Vec3
/**
* Track that animates camera look-at target over time.
* Point targets interpolate smoothly; entity targets switch instantly.
*/
data class FocusTrack(
val keyframes: List<FocusKeyframe>,
val defaultEasing: String = "linear"
) : CameraTrack {
override val trackType = "focus"
override val duration: Float
get() = keyframes.maxOfOrNull { it.time } ?: 0f
override fun evaluate(
time: Float,
context: Map<String, MoValue>,
runtime: MoLangRuntime,
origin: Vec3
): List<TrackOutput> {
if (keyframes.isEmpty()) return emptyList()
val segment = findFocusSegment(time) ?: return emptyList()
val focus = when {
// Both are Point targets - interpolate
segment.start is CameraFocus.Point && segment.end is CameraFocus.Point -> {
val startPos = segment.start.position.toVec3(context, runtime)
val endPos = segment.end.position.toVec3(context, runtime)
// Interpolate positions
val easedProgress = EasingRegistry.getEasingFunction(segment.easing)(segment.progress)
val interpolatedPos = Vec3(
Mth.lerp(easedProgress.toDouble(), startPos.x, endPos.x),
Mth.lerp(easedProgress.toDouble(), startPos.y, endPos.y),
Mth.lerp(easedProgress.toDouble(), startPos.z, endPos.z)
)
CameraFocus.Point(
PositionExpression(
CoordinateValue.Static(interpolatedPos.x),
CoordinateValue.Static(interpolatedPos.y),
CoordinateValue.Static(interpolatedPos.z)
)
)
}
// Entity targets or mixed - use instant switching
else -> {
if (segment.progress < 0.5f) segment.start else segment.end
}
}
return listOf(TrackOutput.Focus(focus))
}
private fun findFocusSegment(time: Float): FocusSegment? {
if (keyframes.isEmpty()) return null
if (keyframes.size == 1) {
val kf = keyframes[0]
return FocusSegment(kf.target, kf.target, kf.time, kf.time, 1f, defaultEasing)
}
for (i in 0 until keyframes.size - 1) {
val start = keyframes[i]
val end = keyframes[i + 1]
if (time <= end.time) {
val duration = end.time - start.time
val progress = if (duration > 0f) {
((time - start.time) / duration).coerceIn(0f, 1f)
} else 1f
val easing = end.easing ?: defaultEasing
return FocusSegment(start.target, end.target, start.time, end.time, progress, easing)
}
}
val last = keyframes.last()
return FocusSegment(last.target, last.target, last.time, last.time, 1f, defaultEasing)
}
init {
require(keyframes.isNotEmpty()) { "FocusTrack must have at least one keyframe" }
require(keyframes.zipWithNext().all { (a, b) -> a.time <= b.time }) {
"FocusTrack keyframes must be sorted by time"
}
}
private data class FocusSegment(
val start: CameraFocus,
val end: CameraFocus,
val startTime: Float,
val endTime: Float,
val progress: Float,
val easing: String
)
}
data class FocusKeyframe(
val time: Float,
val target: CameraFocus,
val easing: String? = null
) {
init {
require(time >= 0f) { "Keyframe time must be non-negative, got $time" }
}
}
@@ -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.api.camera.track
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.camera.model.CameraTrack
import com.cobblemon.mod.common.api.camera.model.TrackOutput
import net.minecraft.world.phys.Vec3
/**
* Track that fires timed GUI actions (open, close, update) during camera timelines.
*
* This track produces no camera outputs itself (no position/focus/rotation).
* The actions are executed by the CameraExecutor when their time is crossed.
*/
data class GuiTrack(
val actions: List<TimedGuiAction>
) : CameraTrack {
override val trackType = "gui"
override val duration: Float
get() = actions.maxOfOrNull { it.time } ?: 0f
override fun evaluate(
time: Float,
context: Map<String, MoValue>,
runtime: MoLangRuntime,
origin: Vec3
): List<TrackOutput> {
// GUI actions are handled by the engine/executor, not via TrackOutput
return emptyList()
}
init {
require(actions.zipWithNext().all { (a, b) -> a.time <= b.time }) {
"GuiTrack actions must be sorted by time"
}
}
}
/**
* Determines the type of GUI action to fire.
*/
enum class GuiActionType {
OPEN,
CLOSE,
UPDATE_ARGS
}
/**
* A timed GUI action within a timeline.
*
* @param time The time in seconds when this action should fire
* @param action The type of action
* @param guiId The scripted GUI resource location (e.g., "cobblemon:battle_intro")
* @param args Static argument values
* @param argsExpressions MoLang expressions to evaluate at fire time, merged with static args
*/
data class TimedGuiAction(
val time: Float,
val action: GuiActionType,
val guiId: String,
val args: Map<String, String> = emptyMap(),
val argsExpressions: Map<String, String> = emptyMap()
) {
init {
require(time >= 0f) { "GUI action time must be non-negative, got $time" }
}
}
/**
* Represents a GUI action that has fired (time crossed).
*/
data class FiredGuiAction(
val action: GuiActionType,
val guiId: String,
val args: Map<String, String>,
val argsExpressions: Map<String, String>
)
@@ -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.track
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.tween.*
import com.cobblemon.mod.common.api.camera.model.*
import net.minecraft.world.phys.Vec3
/**
* Track that animates camera position over time using keyframes.
*/
data class PositionTrack(
val keyframes: List<PositionKeyframe>,
val interpolation: InterpolationMethod = InterpolationMethod.LINEAR,
val defaultEasing: String = "linear",
val isRelative: Boolean = false
) : CameraTrack {
override val trackType = "position"
override val duration: Float
get() = keyframes.maxOfOrNull { it.time } ?: 0f
override fun evaluate(
time: Float,
context: Map<String, MoValue>,
runtime: MoLangRuntime,
origin: Vec3
): List<TrackOutput> {
if (keyframes.isEmpty()) return emptyList()
val interpolatableKeyframes = keyframes.map { kf ->
val vec = kf.position.toVec3(context, runtime, origin, isRelative)
Keyframe(kf.time, vec.asInterpolatable())
}
val interpolated = Interpolator.interpolate(
keyframes = interpolatableKeyframes,
time = time,
method = interpolation,
defaultEasing = defaultEasing,
getEasing = { value ->
// Find original keyframe to get easing
val index = interpolatableKeyframes.indexOfFirst { it.value == value }
keyframes.getOrNull(index)?.easing
}
)
return if (interpolated != null) {
listOf(TrackOutput.Position(interpolated.vec))
} else {
emptyList()
}
}
init {
require(keyframes.isNotEmpty()) { "PositionTrack must have at least one keyframe" }
require(keyframes.zipWithNext().all { (a, b) -> a.time <= b.time }) {
"PositionTrack keyframes must be sorted by time"
}
}
}
data class PositionKeyframe(
val time: Float,
val position: PositionExpression,
val easing: String? = null
) {
init {
require(time >= 0f) { "Keyframe time must be non-negative, got $time" }
}
}
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.camera.track
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.CameraTrack
import com.cobblemon.mod.common.api.camera.model.TrackOutput
import com.cobblemon.mod.common.client.camera.tracks.CameraTrackDefinitionRegistry
import net.minecraft.world.phys.Vec3
/**
* Track that references another track definition by ID.
*/
data class ReferenceTrack(
val trackId: String
) : CameraTrack {
override val trackType = "reference"
override val duration: Float
get() {
val referencedTrack = CameraTrackDefinitionRegistry.getTrack(trackId)
return referencedTrack?.duration ?: 0f
}
override fun evaluate(
time: Float,
context: Map<String, MoValue>,
runtime: MoLangRuntime,
origin: Vec3
): List<TrackOutput> {
val referencedTrack = CameraTrackDefinitionRegistry.getTrack(trackId)
if (referencedTrack == null) {
// Log warning but don't crash
Cobblemon.LOGGER.warn("[ReferenceTrack] Track '$trackId' not found in registry")
return emptyList()
}
// Delegate to referenced track
return referencedTrack.evaluate(time, context, runtime, origin)
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.cooking.effects
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.effect.MobEffectCategory
import net.minecraft.world.entity.Entity
@@ -19,13 +18,5 @@ class CleanseAllEffect : MobEffect(MobEffectCategory.BENEFICIAL, 0xFFFFFF) {
override fun applyInstantenousEffect(source: Entity?, indirect: Entity?, target: LivingEntity, amplifier: Int, proximity: Double) {
target.removeAllEffects()
}
override fun applyEffectTick(livingEntity: LivingEntity, amplifier: Int): Boolean {
if (livingEntity is ServerPlayer){
livingEntity.removeAllEffects()
}
return true
}
override fun shouldApplyEffectTickThisTick(duration: Int, amplifier: Int) = true
override fun shouldApplyEffectTickThisTick(duration: Int, amplifier: Int) = false
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.cooking.effects
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.effect.MobEffectCategory
import net.minecraft.world.entity.Entity
@@ -24,18 +23,5 @@ class CleanseNegativeEffect : MobEffect(MobEffectCategory.BENEFICIAL, 0xFFFFFF)
}
}
}
override fun applyEffectTick(livingEntity: LivingEntity, amplifier: Int): Boolean {
if (livingEntity is ServerPlayer){
val snapshot = livingEntity.activeEffects.toList()
for (ei in snapshot) {
if (ei.effect.value().category == MobEffectCategory.HARMFUL) {
livingEntity.removeEffect(ei.effect)
}
}
}
return true
}
override fun shouldApplyEffectTickThisTick(duration: Int, amplifier: Int) = true
override fun shouldApplyEffectTickThisTick(duration: Int, amplifier: Int) = false
}
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.data
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
// TODO this is implemented in multiple places but unused. Can we scratch this?
/**
* Represents an object that will possibly be synchronized to the client during datapack synchronization.
*
* @param T The type of the object being synchronized.
*
* @author Licious
* @since October 14th, 2022
*/
interface ClientDataSynchronizer<T> : Decodable, Encodable {
/**
* Compares an object of type [T] with this instance to see if synchronization is necessary.
*
* @param other The other instance of type [T].
* @return If synchronization is necessary.
*/
fun shouldSynchronize(other: T): Boolean
}
@@ -14,11 +14,9 @@ import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.toBlockPos
import com.google.gson.JsonElement
import com.mojang.serialization.JsonOps
import net.minecraft.core.component.DataComponentMap
import net.minecraft.core.component.DataComponentPatch
import net.minecraft.core.registries.Registries
import net.minecraft.resources.RegistryOps
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
@@ -42,7 +40,7 @@ open class ItemDropEntry : DropEntry {
override var maxSelectableTimes = 1
open val dropMethod: ItemDropMethod? = null
open var item = ResourceLocation.parse("minecraft:fish")
open val components: JsonElement? = null
open val components: DataComponentMap? = null
override fun drop(entity: LivingEntity?, world: ServerLevel, pos: Vec3, player: ServerPlayer?) {
val item = world.registryAccess().registryOrThrow(Registries.ITEM).get(item) ?: return LOGGER.error("Unable to load drop item: $item")
@@ -55,15 +53,11 @@ open class ItemDropEntry : DropEntry {
it
}
}
if (components != null) {
val registryOps = RegistryOps.create(JsonOps.INSTANCE, world.registryAccess())
val decodeResult = DataComponentPatch.CODEC.parse(registryOps, components)
decodeResult.ifSuccess { patch ->
stack.applyComponentsAndValidate(patch)
}.ifError { error ->
LOGGER.error("Unable to parse components for drop item $item: ${error.message()}")
}
val builder = DataComponentPatch.builder()
components?.forEach {
builder.set(it)
}
stack.applyComponentsAndValidate(builder.build())
if (dropMethod == ItemDropMethod.ON_PLAYER && player != null) {
world.addFreshEntity(ItemEntity(player.level(), player.x, player.y, player.z, stack))
@@ -22,22 +22,11 @@ import com.cobblemon.mod.common.api.events.berry.BerryMutationOfferEvent
import com.cobblemon.mod.common.api.events.berry.BerryMutationResultEvent
import com.cobblemon.mod.common.api.events.berry.BerryYieldCalculationEvent
import com.cobblemon.mod.common.api.events.cooking.PokeSnackSpawnPokemonEvent
import com.cobblemon.mod.common.api.events.camera.*
import com.cobblemon.mod.common.api.events.drops.LootDroppedEvent
import com.cobblemon.mod.common.api.events.entity.PokemonEntityLoadEvent
import com.cobblemon.mod.common.api.events.entity.PokemonEntitySaveEvent
import com.cobblemon.mod.common.api.events.entity.PokemonEntitySaveToWorldEvent
import com.cobblemon.mod.common.api.events.entity.SpawnBucketChosenEvent
import com.cobblemon.mod.common.api.events.entity.SpawnEvent
import com.cobblemon.mod.common.api.events.entity.*
import com.cobblemon.mod.common.api.events.farming.ApricornHarvestEvent
import com.cobblemon.mod.common.api.events.fishing.BaitConsumedEvent
import com.cobblemon.mod.common.api.events.fishing.BaitEffectFunctionRegistryEvent
import com.cobblemon.mod.common.api.events.fishing.BaitSetEvent
import com.cobblemon.mod.common.api.events.fishing.BaitSpawnPokemonEvent
import com.cobblemon.mod.common.api.events.fishing.BobberSpawnPokemonEvent
import com.cobblemon.mod.common.api.events.fishing.PokerodCastEvent
import com.cobblemon.mod.common.api.events.fishing.PokerodReelEvent
import com.cobblemon.mod.common.api.events.habitats.HabitatSpawnActivatedEvent
import com.cobblemon.mod.common.api.events.habitats.SaveHabitatBlockSettingsEvent
import com.cobblemon.mod.common.api.events.fishing.*
import com.cobblemon.mod.common.api.events.item.LeftoversCreatedEvent
import com.cobblemon.mod.common.api.events.pokeball.PokeBallCaptureCalculatedEvent
import com.cobblemon.mod.common.api.events.pokeball.PokemonCatchRateEvent
@@ -52,11 +41,7 @@ import com.cobblemon.mod.common.api.events.pokemon.healing.PokemonHealedEvent
import com.cobblemon.mod.common.api.events.pokemon.interaction.ExperienceCandyUseEvent
import com.cobblemon.mod.common.api.events.pokemon.interaction.PokemonInteractionGUICreationEvent
import com.cobblemon.mod.common.api.events.starter.StarterChosenEvent
import com.cobblemon.mod.common.api.events.storage.ChangePCBoxWallpaperEvent
import com.cobblemon.mod.common.api.events.storage.ReleasePokemonEvent
import com.cobblemon.mod.common.api.events.storage.RenamePCBoxEvent
import com.cobblemon.mod.common.api.events.storage.WallpaperCollectionEvent
import com.cobblemon.mod.common.api.events.storage.WallpaperUnlockedEvent
import com.cobblemon.mod.common.api.events.storage.*
import com.cobblemon.mod.common.api.events.world.BigRootPropagatedEvent
import com.cobblemon.mod.common.api.reactive.CancelableObservable
import com.cobblemon.mod.common.api.reactive.EventObservable
@@ -382,11 +367,32 @@ object CobblemonEvents {
@JvmField
val SELECT_DRIVER = EventObservable<SelectDriverEvent>()
// Habitats
// -------------------------------------------------------------------------------------
//
// Camera System
//
// -------------------------------------------------------------------------------------
@JvmField
val HABITAT_SPAWN_ACTIVATED = CancelableObservable<HabitatSpawnActivatedEvent>()
val CAMERA_STARTED_PRE = CancelableObservable<CameraStartedEvent.Pre>()
@JvmField
val HABITAT_SETTINGS_SAVED_PRE = CancelableObservable<SaveHabitatBlockSettingsEvent.Pre>()
val CAMERA_STARTED_POST = EventObservable<CameraStartedEvent.Post>()
@JvmField
val HABITAT_SETTINGS_SAVED_POST = EventObservable<SaveHabitatBlockSettingsEvent.Post>()
val CAMERA_STOPPED_PRE = EventObservable<CameraStoppedEvent.Pre>()
@JvmField
val CAMERA_STOPPED_POST = EventObservable<CameraStoppedEvent.Post>()
@JvmField
val CAMERA_STATE_TRANSITION = EventObservable<CameraStateTransitionEvent>()
@JvmField
val CAMERA_VIEWER_ADDED = EventObservable<CameraViewerAddedEvent>()
@JvmField
val CAMERA_VIEWER_REMOVED = EventObservable<CameraViewerRemovedEvent>()
@JvmField
val CAMERA_TICK = EventObservable<CameraTickEvent>()
}
@@ -0,0 +1,100 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.camera
import com.cobblemon.mod.common.api.camera.ICameraController
import com.cobblemon.mod.common.api.events.Cancelable
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.phys.Vec3
import java.util.UUID
/** Base interface for all camera events. */
interface CameraEvent {
val cameraId: UUID
val controller: ICameraController
}
/** Events fired when a camera starts. Pre is cancelable. */
interface CameraStartedEvent : CameraEvent {
val timelineId: String
val viewers: List<ServerPlayer>
/** Pre event - fired before camera starts. */
data class Pre(
override val cameraId: UUID,
override val controller: ICameraController,
override val timelineId: String,
override val viewers: List<ServerPlayer>
) : CameraStartedEvent, Cancelable()
/** Post event - fired after camera starts. */
data class Post(
override val cameraId: UUID,
override val controller: ICameraController,
override val timelineId: String,
override val viewers: List<ServerPlayer>
) : CameraStartedEvent
}
/** Events fired when a camera stops. */
interface CameraStoppedEvent : CameraEvent {
val reason: StopReason
enum class StopReason {
MANUAL,
COMPLETED,
VIEWER_LEFT,
ERROR
}
/** Pre event - fired before camera stops. */
data class Pre(
override val cameraId: UUID,
override val controller: ICameraController,
override val reason: StopReason
) : CameraStoppedEvent
/** Post event - fired after camera stops. */
data class Post(
override val cameraId: UUID,
override val controller: ICameraController,
override val reason: StopReason
) : CameraStoppedEvent
}
/** Fired when a camera transitions between states. */
data class CameraStateTransitionEvent(
override val cameraId: UUID,
override val controller: ICameraController,
val fromStateId: String,
val toStateId: String,
val transitionType: String
) : CameraEvent
/** Fired when a viewer is added to a camera. */
data class CameraViewerAddedEvent(
override val cameraId: UUID,
override val controller: ICameraController,
val viewer: ServerPlayer
) : CameraEvent
/** Fired when a viewer is removed from a camera. */
data class CameraViewerRemovedEvent(
override val cameraId: UUID,
override val controller: ICameraController,
val viewer: ServerPlayer
) : CameraEvent
/** Fired every tick while a camera is active. Fires 20 times per second. */
data class CameraTickEvent(
override val cameraId: UUID,
override val controller: ICameraController,
val currentPosition: Vec3,
val currentFocus: Vec3?
) : CameraEvent
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.events.entity
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
@@ -20,7 +21,7 @@ class SpawnBucketChosenEvent(
/** The cause of the spawn. */
val spawnCause: SpawnCause,
/** The bucket that was chosen. You can alter this. */
var bucket: String,
var bucket: SpawnBucket,
/** The final weights that were used to make the choice. */
val bucketWeights: Map<String, Float>
val bucketWeights: Map<SpawnBucket, Float>
)
@@ -10,9 +10,7 @@ package com.cobblemon.mod.common.api.events.entity
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import net.minecraft.world.entity.Entity
/**
@@ -23,9 +21,4 @@ import net.minecraft.world.entity.Entity
* @author Hiroku
* @since April 22nd, 2023
*/
class SpawnEvent<T : Entity>(val entity: T, val spawnablePosition: SpawnablePosition) : Cancelable() {
val cause: SpawnCause
get() = spawnablePosition.cause
val spawner: Spawner
get() = spawnablePosition.spawner
}
class SpawnEvent<T : Entity>(val entity: T, val spawnablePosition: SpawnablePosition) : Cancelable()
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.events.fishing
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.item.ItemStack
@@ -26,7 +27,7 @@ interface BaitSpawnPokemonEvent {
*/
data class Pre(
val bobber: PokeRodFishingBobberEntity, // todo replace this with the bait entity
val chosenBucket: String,
val chosenBucket: SpawnBucket,
val rod: ItemStack
) : Cancelable(), BobberSpawnPokemonEvent
@@ -37,7 +38,7 @@ interface BaitSpawnPokemonEvent {
* @param pokemon The Pokemon that is modified.
*/
data class Modify(
val chosenBucket: String,
val chosenBucket: SpawnBucket,
val rod: ItemStack,
val pokemon: PokemonEntity
) : BobberSpawnPokemonEvent
@@ -51,7 +52,7 @@ interface BaitSpawnPokemonEvent {
*/
data class Post(
val bobber: PokeRodFishingBobberEntity,
val chosenBucket: String,
val chosenBucket: SpawnBucket,
val bait: ItemStack,
val pokemon: PokemonEntity
) : BobberSpawnPokemonEvent
@@ -8,10 +8,12 @@
package com.cobblemon.mod.common.api.events.fishing
import com.cobblemon.mod.common.api.spawning.SpawnBucket
/**
* Event that is fired when a bucket is chosen by the bobber.
* @param chosenBucket The bucket that is chosen.
* @param buckets The list of buckets that the bobber can choose from.
* @param luckOfTheSeaLevel The level of the Luck of the Sea enchantment on the rod.
*/
class BobberBucketChosenEvent(var chosenBucket: String, val buckets: MutableList<String>, val luckOfTheSeaLevel: Int)
class BobberBucketChosenEvent(var chosenBucket: SpawnBucket, val buckets: MutableList<SpawnBucket>, val luckOfTheSeaLevel: Int)
@@ -12,7 +12,10 @@ import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -70,8 +73,8 @@ interface BobberSpawnPokemonEvent {
val bait: ItemStack,
val pokemon: PokemonEntity
) : BobberSpawnPokemonEvent {
val context = mutableMapOf(
"chosen_bucket" to StringValue(spawnAction.bucket),
val context = mutableMapOf<String, MoValue>(
"chosen_bucket" to ObjectValue(spawnAction.bucket, { it.name }),
"bait" to pokemon.level().itemRegistry.wrapAsHolder(bait.item).asMoLangValue(Registries.ITEM),
"pokemon_entity" to pokemon.asMoLangValue()
)
@@ -1,37 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.habitats
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.habitats.spawningstyle.ActivatedHabitatSpawning
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.spawner.FixedAreaSpawner
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
/**
* Event that fires when a habitat is about to attempt to spawn Pokémon. Cancelling this event will prevent
* the habitat block from attempting to spawn anything for this activation.
*
* @author Hiroku
* @since February 15th,2026
*/
class HabitatSpawnActivatedEvent(
/** The habitat block entity that is responsible for the spawn. */
val habitatBlockEntity: HabitatBlockEntity,
/** The activation settings. You could have got it from the block entity but this is easier. */
val activatedHabitatSpawning: ActivatedHabitatSpawning,
/** The [SpawnCause] that will be used in the spawn action. This may contain a player that is nearest to the block. */
var cause: SpawnCause,
/** The maximum number of Pokémon the block will consider spawning (provided there is space). */
var maxSpawns: Int
) : Cancelable() {
/** The spawner representing the habitat. */
val spawner: FixedAreaSpawner
get() = activatedHabitatSpawning.spawner
}
@@ -1,40 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.habitats
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.habitats.dto.HabitatSettingsDTO
import com.cobblemon.mod.common.block.habitat.HabitatBlockEntity
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
interface SaveHabitatBlockSettingsEvent {
val player: ServerPlayer
val world: ServerLevel
val blockPos: BlockPos
val habitatBlockEntity: HabitatBlockEntity
val settings: HabitatSettingsDTO
class Pre(
override val player: ServerPlayer,
override val world: ServerLevel,
override val blockPos: BlockPos,
override val habitatBlockEntity: HabitatBlockEntity,
override val settings: HabitatSettingsDTO
) : SaveHabitatBlockSettingsEvent, Cancelable()
class Post(
override val player: ServerPlayer,
override val world: ServerLevel,
override val blockPos: BlockPos,
override val habitatBlockEntity: HabitatBlockEntity,
override val settings: HabitatSettingsDTO
) : SaveHabitatBlockSettingsEvent
}
@@ -68,14 +68,11 @@ class SpawnBait(
val TYPING = cobblemonResource("typing")
val EGG_GROUP = cobblemonResource("egg_group")
val SHINY_REROLL = cobblemonResource("shiny_reroll")
val MARK_CHANCE = cobblemonResource("mark_chance")
val DROPS_REROLL = cobblemonResource("drops_reroll")
val HIDDEN_ABILITY_CHANCE = cobblemonResource("ha_chance")
val ALPHA_CHANCE = cobblemonResource("alpha_chance")
val POKEMON_CHANCE = cobblemonResource("pokemon_chance")
val FRIENDSHIP = cobblemonResource("friendship")
val RARITY_BUCKET = cobblemonResource("rarity_bucket")
val SIZE = cobblemonResource("size")
fun registerEffect(type: ResourceLocation, effect: (PokemonEntity, Effect) -> Unit) {
EFFECT_FUNCTIONS[type] = effect
@@ -89,14 +86,11 @@ class SpawnBait(
registerEffect(NATURE) { entity, effect -> FishingSpawnCause.alterNatureAttempt(entity, effect) }
registerEffect(IV) { entity, effect -> FishingSpawnCause.alterIVAttempt(entity, effect) }
registerEffect(SHINY_REROLL) { entity, effect -> FishingSpawnCause.shinyReroll(entity, effect) }
registerEffect(MARK_CHANCE) { entity, effect -> FishingSpawnCause.alterMarksAttempt(entity, effect) }
registerEffect(DROPS_REROLL) { entity, effect -> FishingSpawnCause.saveDropsReroll(entity, effect) }
registerEffect(GENDER_CHANCE) { entity, effect -> FishingSpawnCause.alterGenderAttempt(entity, effect) }
registerEffect(LEVEL_RAISE) { entity, effect -> FishingSpawnCause.alterLevelAttempt(entity, effect) }
registerEffect(HIDDEN_ABILITY_CHANCE) { entity, _ -> FishingSpawnCause.alterHAAttempt(entity) }
registerEffect(ALPHA_CHANCE) { entity, effect -> FishingSpawnCause.alterAlphaAttempt(entity) }
registerEffect(FRIENDSHIP) { entity, effect -> FishingSpawnCause.alterFriendshipAttempt(entity, effect) }
registerEffect(SIZE) { entity, effect -> FishingSpawnCause.alterSize(entity, effect) }
CobblemonEvents.BAIT_EFFECT_REGISTRATION.post(BaitEffectFunctionRegistryEvent()) { event ->
event.functions.forEach { (type, function) -> registerEffect(type, function) }
}
@@ -1,30 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.habitats
import com.cobblemon.mod.common.api.habitats.spawningstyle.ActivatedHabitatSpawning
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.entity.Entity
/**
* Influence added to the activated habitat spawner blocks so that they can track the entity and apply block modifiers.
*
* @author Hiroku
* @since February 15th, 2026
*/
class ActivatedHabitatSpawningInfluence(val activatedHabitatSpawning: ActivatedHabitatSpawning) : SpawningInfluence {
override fun affectSpawn(action: SpawnAction<*>, entity: Entity) {
if (entity is PokemonEntity) {
activatedHabitatSpawning.habitatBlockEntity.modifiers.apply(entity)
activatedHabitatSpawning.habitatBlockEntity.spawnedEntityIDs.add(entity.id)
}
}
}

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