Compare commits

..
4290 changed files with 91928 additions and 311042 deletions

No files matched your search

-3
View File
@@ -157,9 +157,6 @@ build:
paths:
- neoforge/build/libs
- fabric/build/libs
exclude:
- neoforge/build/libs/*-dev-shadow.jar
- fabric/build/libs/*-dev-shadow.jar
publish:
stage: deploy
+45 -12
View File
@@ -1,27 +1,60 @@
# Changelog
## [1.8.5 (MONTH xth, 2026)](#1-8-5)
## [1.8.1 (MONTH Xth, 2026)](#1-8-1)
### Additions
- Added the `healersSetSpawn` gamerule. When enabled, using a healing machine will set your spawnpoint.
- A Pokémon holding a Power item now gains 1 point in the corresponding riding stat every two minutes while being ridden. The interval is configurable, and can be disabled with `-1`.
- Added sounds to Gigaton Hammer and Pyro Ball
- Added stepsounds to Venusaur, Blastoise, Dewgong, Rhydon, Rhyhorn, Parasect and Mr. Mime.
### New rideable Pokémon
### Pokémon Added
### Model updates for the following Pokémon
### Animation updates for the following Pokémon
### Changes
- Amethyst now grows from Typegem core blocks like typegems do
### Fixes
- Fixed Ponigiri food values for AppleSkin integration
- Fixed errors emitted by typos in Parched Peat bog structure files
- Fixed the `stat_compare` and `stat_equal` evolution requirements crashing the game when given `special_attack` or `special_defence`, and made them treat any non-permanent stat as a failed requirement instead of crashing.
- Fixed the Beast Ball not applying its 0.1× penalty to Pokémon that are not Ultra Beasts.
- Fixed the Moon Ball reading the world's total elapsed time instead of the time of day, so its bonus applied during the day instead.
- Fixed the Love Ball never awarding its 2.5× opposite gender bonus, and matching species by display name instead of identifier.
- Fixed the Timer Ball being weaker than a regular Poké Ball on early turns and only reaching 4× on turn 14 instead of turn 10.
- Fixed the Luxury Ball only doubling Friendship gains of 1, leaving level up and berry gains unaffected.
- Fixed NPCs getting their own TM progress file.
- Fixed the TM Machine refusing move selections that the `unlockAllMoveDexMovesByDefault` config had made visible, leaving the move slot blank and the craft button unresponsive.
- Fixed Rufflet, Braviary, and Hisuian Braviary flooding logs with errors about missing sound events.
- Fixed Pokémon property typos in Pangoro and Dedenne herd spawn files.
- Fixed form typo in Rockruff dusk spawn entry.
- Fixed a potential world gen stall with the Type Gem clusters.
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait
- Fixed Ampharos' eyes not emitting particles
- Fixed Alolan Raticate's cry not playing
- Fixed unintentional watermelon cosmetic for Hisuian Electrode.
- Fixed Blastoise having an unintentional passenger seat
- Fixed odd NeoForge crash due to a dependency being outdated. It will now flag the dependency for being outdated correctly.
- Fixed form typo in Rockruff-Dusk spawn, preventing it from being able to spawn.
- Fixed a potential world gen stall caused by Type Gem clusters.
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait.
- Fixed Ampharos' eyes not emitting particles.
- Fixed Alolan Raticate's cry not playing.
- Fixed odd NeoForge crash due to a dependency being outdated. It will now correctly flag the dependency for being outdated.
- Fixed several incorrect light-based spawning conditions for ghosts and bugs.
- Removed unused, placeholder habitat pools.
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to a having an expression mistakenly assigned as a recoil animation.
- Fixed Pokémon on shoulders not animating if they are visibly holding a held item.
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to a having an expression mistakenly assigned as a recoil animation.
- Fixed Lycanroc's head movement
- Added head movement to Archaludon
- Fixed Duraludon line not having their sleep animations
- Fixed Diglett line not having their faint animations
- Fixed Kingambit moving its legs while walking
### Developer
### Molang & Datapacks
- `q.is_standing_on_blocks` now properly checks for non-full blocks like slabs, Mud or Soul Sand
- Added Molang functions to play footstep sounds based on the corresponding surface. These are intended to be used by Pokémon animations.
- Pokémon can now have their Minecraft footstep sounds silenced in their poser. This doesn't affect emitting game events.
### Particles
### Localization
- Updated translations for:
- Updated translations for:
+9
View File
@@ -0,0 +1,9 @@
- Some kind of particle effect for them? they're meant to have glowing eyes, so maybe some red particles on the eye locator
--- global posers? stacked posers?
--- red texture seems to be what some people wanted.
/- shoulder mounts part of it
- omni boost
- roar when player comes within reach, adjust levels
- Riding settings to control what the min scale is for a pokemon to be ridable.
+2 -5
View File
@@ -1,6 +1,3 @@
import utilities.gitBranch
import utilities.gitShortCommit
plugins {
id("cobblemon.root-conventions")
}
@@ -9,6 +6,6 @@ version = "${project.property("mod_version")}+${project.property("mc_version")}"
val isSnapshot = project.property("snapshot")?.equals("true") == true
if (isSnapshot) {
val fixedBranchName = gitBranch().substringAfter("/")
version = "$version-${fixedBranchName}-${gitShortCommit()}"
val fixedBranchName = versioning.info.branch.substringAfter("/")
version = "$version-${fixedBranchName}-${versioning.info.build}"
}
+1 -4
View File
@@ -36,7 +36,6 @@
- Added `cobblemon:catch_alpha_pokemon` advancement trigger and `totalAlphaCaptureCount` stat for captured Alpha Pokémon.
### New rideable Pokémon
- Pidgeot
- Crobat
- Skarmory
- Milotic
@@ -107,9 +106,7 @@
- Avalugg (Hisuian Form)
### Model updates for the following Pokémon
- Pidgey
- Pidgeotto
- Pidgeot
- Sandshrew
- Sandslash
- Nidoking
-41
View File
@@ -1,41 +0,0 @@
# Changelog
## [1.8.1 (September 13th, 2026)](#1-8-1)
### Animation updates for the following Pokémon
- Flapple, Appletun, Dipplin, Hydrapple (placeholder animations only).
### Fixes
- Fixed a potential world generation stall caused by Type Gem clusters.
- Fixed Pokémon on shoulders not animating if they are visibly holding a held item.
- Fixed NPCs getting their own TM progress file.
- Fixed odd NeoForge crash due to a dependency being outdated. It will now correctly flag the dependency for being outdated.
- Fixed the TM Machine refusing move selections that the `unlockAllMoveDexMovesByDefault` config had made visible, leaving the move slot blank and the craft button unresponsive.
- Fixed the Beast Ball not applying its 0.1× penalty to Pokémon that are not Ultra Beasts.
- Fixed the Moon Ball reading the world's total elapsed time instead of the time of day, so its bonus applied during the day instead.
- Fixed the Love Ball never awarding its 2.5× opposite gender bonus, and matching species by display name instead of identifier.
- Fixed the Timer Ball being weaker than a regular Poké Ball on early turns and only reaching 4× on turn 14 instead of turn 10.
- Fixed the Luxury Ball only doubling Friendship gains of 1, leaving level up and berry gains unaffected.
- Fixed Rufflet, Braviary, and Hisuian Braviary flooding logs with errors about missing sound events.
- Fixed Pokémon property typos in Pangoro and Dedenne herd spawn files.
- Fixed unintentional watermelon cosmetic for Hisuian Electrode.
- Fixed Blastoise having an unintentional passenger seat.
- Fixed form typo in Rockruff-Dusk spawn, preventing it from being able to spawn.
- Fixed the `stat_compare` and `stat_equal` evolution requirements crashing the game when given `special_attack` or `special_defence`, and made them treat any non-permanent stat as a failed requirement instead of crashing.
- Fixed Rowap and Jacoba berries not correctly altering Pokémon sizes when used as bait.
- Fixed Ampharos' eyes not emitting particles.
- Fixed Alolan Raticate's cry not playing.
- Fixed several incorrect light-based spawning conditions for ghosts and bugs.
- Removed unused, placeholder habitat pools.
- Fixed Mantyke, Mantine, and Quaxly getting stuck in a recoil 'expression' during battles due to having an expression assigned as a recoil animation.
- Fixed Lucario's aura particles not emitting.
- Fixed Lycanroc's head movement.
- Added head movement to Archaludon.
- Fixed Duraludon line not having their sleep animations.
- Fixed Diglett line not having their faint animations.
- Fixed Kingambit moving its legs while walking.
### Localization
- Updated translations for:
- Brazilian Portuguese
- Simplified Chinese
- Japanese
-211
View File
@@ -1,211 +0,0 @@
# Changelog
## [1.8.2 (October 11th, 2026)](#1-8-2)
### Additions
- The following items can now be placed on the ground as blocks:
- King's Rock, Galarica Cuff, Unremarkable Teacup, Masterpiece Teacup, Chipped Pot, Cracked Pot, Sachet, Metal Powder, Quick Powder, Bright Powder, Soft Sand, Electirizer, Magmarizer, Icy Rock, Heat Rock, Smooth Rock, Damp Rock, Destiny Knot, Luminous Moss, Never-Melt Ice, Wide Lens, Scope Lens, and Zoom Lens.
- I wasn't finished. Iron Ball, Metal Coat, Throat Spray, Terrain Extender, Medicinal Brew, Moomoo Milk, HP Up, Protein, Iron, Carbos, Calcium, Zinc, PP Up, PP Max, Upgrade, Dubious Disc, and Loaded Dice.
- The following items can now be placed on various surfaces:
- Galarica Wreath, Magnet, Flame Orb, Life Orb, Toxic Orb, Light Ball, and Smoke Ball.
- The following items can now be placed as blocks and have additional functionality:
- Ring Target, Metronome, and Eject Button.
- The following items can now be worn by Players and Pokémon:
- Galarica Wreath, Cleanse Tag, Spell Tag, Sharp Beak, Covert Cloak, Shell Helmet, and Never-Melt Ice.
- Wearable items can now be equipped by a player by holding it and right-clicking (or whatever you've rebound use/interact to).
- Wearable items that can be placed down are exempt from this behaviour.
- Mint Leaves can now be placed on the ground as Leaf Litter, or in Flower Pots.
- The recall animation now renders the ball in hand with the player's arm raised.
- Added Oricorio Flowers.
- Added Terrain Plants, four plants grown from Electric, Grassy, Misty, and Psychic Seeds, which glow under specific environmental conditions and can be farmed for more of their seeds.
- Berries can now be placed in Flower Pots.
- The evolution tree for a Pokémon can now be viewed in the Pokédex, showing the Pokémon's full family line and what's needed to evolve into each stage.
- Added custom sounds to a bunch of items that can be placed as blocks: liquid bottles, pill bottles, powders, and dice.
- Added paper as a new cosmetic item for Pikachu.
- Burmy changes its cloak after a battle it took part in, based on the biome.
- Trash Cloak in the Deep Dark, Nether and End.
- Plant Cloak in lush biomes such as forests, jungles, plains and swamps.
- Sandy Cloak in sandy biomes, beaches and caves.
### Pokémon Added
#### Generation 4
- Snover
- Abomasnow
- Burmy
- Wormadam
- Mothim
#### Generation 5
- Tympole
- Palpitoad
- Seismitoad
#### Generation 6
- Helioptile
- Heliolisk
#### Generation 7
- Grubbin
- Charjabug
- Vikavolt
- Minior
#### Generation 8
- Eternatus
#### Generation 9
- Greavard
- Houndstone
### Model updates for the following Pokémon
- Sudowoodo
- Spheal
- Cacnea
- Cacturne
- Binacle
- Barbaracle
- Glimmet
- Glimmora
- Deerling
- Sawsbuck (All forms)
- Absol
- Poliwag
- Politoed
- Ekans
- Oddish
- Gloom
- Vileplume
- Bellossom
- Shroomish
- Chespin
- Quilladin
- Chesnaught
- Fennekin
- Braixen
- Delphox
- Froakie
- Frogadier
- Greninja
- Shinx
- Luxio
- Luxray
- Slakoth
- Riolu
- Lucario
- Corsola
- Mime Jr.
### Animation updates for the following Pokémon
- Treecko (New Shoulder Mount!)
- Bonsly (New Shoulder Mount!)
- Sudowoodo
- Cacnea
- Cacturne
- Ekans
- Arbok
- Oddish
- Gloom
- Vileplume
- Bellossom
- Pichu
- Pikachu
- Raichu (+ Alolan)
- Greninja
- Slakoth
### Changes
- Slowed the speed of Type Gem growth, and revamped their generation to grow in a more crystal-like shape.
- Terrain Seeds can now be planted.
- Terrain Seeds are now consumed during PvP and NPC battles.
- Glow Item Frames can be crafted using Bright Powder as an alternative to Glow Ink Sacs.
- Bright Powder can be used on Signs and Hanging Signs to make text glow, as an alternative to Glow Ink Sacs.
- Updated various recipes to display in Recipe Groups within the Recipe Book.
- Updated most recipes to display more logically and consistently within the Recipe Book's categories.
- Updated the following recipes
- Never-Melt Ice, Focus Sash, Blunder Policy, Weakness Policy, Blank TM.
- Made ride rotations less jittery in multiplayer.
- Changes Disc Shelf TMs to use textures instead of tinting and overlays to line up with item display.
- Blank TM can now be inserted into the Disc Shelf.
- Added a TM creative tab.
- Made regional and Vivillon split-evo locations mutually exclusive.
- Vivillon's patterns are now viewable as forms with form names and unique Pokédex entries displayed.
- Item evolutions now unlock **all** locked evolutions whose requirements are met instead of stopping after the first.
- Exceptions being "forced" evolutions which stop after the first one.
- Removed Mega Drain as a TM.
- PokeSnacks (and other area spawns) will now start the vertical offset checking from the center rather than base coordinate
- This will help PokeSnacks to work inside caves or tighter spaces better.
### Fixes
- Type Gem blocks now properly generate clusters off of the block.
- Correctly grant "They're Minerals!" advancement when planting a Type Gem.
- Shoulder mounted Pokémon now follow player body animations, improving their positioning with custom player animations and default actions.
- Shoulder mounted Pokémon now sit slightly higher with chest armor equipped for better alignment, with a configurable blacklist.
- Type Gem Blocks now have advancements to give you the recipe in the recipe book (the data for the advancements were inside the recipes somehow??).
- Some other recipes without advancements have also been corrected.
- Corrected the TM Recipes for Charge Beam and Payday.
- Starter Screen Categories will no longer stop rendering if they are bigger than the viewport and you are not viewing the top or bottom edges.
- The Random Starter option no longer runs off into other categories or becomes unaccessible due to scroll limits if it causes a new row.
- Mochi and Feathers no longer drop glass bottles on use.
- Fixed a rare set of concurrency bugs related to reloading packs on NeoForge; fixes a ModernFix compatibility issue.
- Removed unnecessary conditions for Mimikyu's standing pose. Hopefully fixes the rare instance of it using its shoulder animations during battle in multiplayer scenarios.
- Fixed Rockruff line not blinking.
- Fixed Flygon sometimes using its running animation instead of walking.
- Fixed Bramblin always sleeping outside of battle.
- Fixed Dragonair using its battle idle as a normal idle.
- Fixed Lycanroc's head movement.
- Added head movement to Archaludon.
- Fixed Duraludon line not having any sleep animations.
- Fixed Diglett line not having their faint animations.
- Fixed Kingambit moving its legs while walking.
- Fixed Alolan Diglett's cry not playing.
- Fixed shiny alpha Snorlax using the non-shiny texture.
- Fixed Crabominable's head breaking when looking around.
- Fixed Shiny Dipplin and Hydrapple's textures.
- Various structure fixes.
- Fixed shiny Pokémon under certain circumstances forgetting taught TMs.
- Fixed certain Pokémon sounds sometimes being doubled when played at specific times in their animations.
- Fixed ball capture beam origin coming from behind the ball.
- Fixed Safari Ball catch rate outside of battle.
- Fixed alpha eye (and other) particles not respecting the client particle density setting in video options.
- Fixed the TM Machine UI not closing when player is moving away from it.
- Moved the config category for alpha/shiny chance config fields for fossil machine and honey log slathering to Spawning.
- Fixed crashes for addon species with non-Cobblemon namespaces in evolution, fishing bait, and Pokédex lookups.
- Bees will now grow Apricorns and the Galarica Nut Bush, which were in the bee growables tag but never handled, and correctly spend their nectar when they do.
- Saccharine Leaves can now be bone mealed.
- Fixed equipping a stack of wearable items instead of one item of a stack.
- Fixed TM stack consumption in TM Machine screen when a player teaches a move to a Pokémon.
- Fixed every Miltank giving mushroom stew, not only mushroom variants.
- Medicinal Leeks can now be eaten when they aren't being planted, matching their food properties.
- Sweeping attacks no longer set owned Pokémon on fire (e.g. with Fire Aspect) or knock them back. Friendly Fire is now off.
- Fixed Pokémon not losing their previous form's signature moves when changing forms (e.g. Rotom keeping Overheat after leaving its Heat form).
- Form-specific moves gained from a form change are now only granted if the Pokémon is high enough level to learn them, and no longer create duplicates in benched moves.
- Spinda now learns Teeter Dance at level 32 instead of via TM.
- Fixed Oricorio Pa'u form description in the Pokédex.
- Fixed PC playing sound on close when opened via command.
### Developer
- Evolution requirements can now set `pokedexIcon` and `pokedexTooltip` directly in their JSON to control how they're shown on the Pokédex's evolution tree, for example:
```json
{
"variant": "attack_defence_ratio",
"pokedexIcon": "minecraft:diamond_sword",
"pokedexTooltip": "cobblemon.ui.pokedex.requirement.attack_defence_ratio.tooltip"
}
```
- Fixed the wrong order of operations in the code flow prior to the `FOSSIL_REVIVED` event causing Pokémon to appear without the entity link.
- Fixed TestPartySlot command not correctly comparing the provided spec with the target Pokémon.
### Molang & Datapacks
- A new `negate` variant for evolution requirements has been added, which will negate (invert) the provided inner condition.
- Added Molang functions for individual Pokédex ids, wherein a subdex is passed in as a string to `dex_caught_count` etc.
- Added a `battle_ended` callback, backed by the new `BATTLE_ENDED` event, which fires whenever a battle ends for any reason, alongside a multitude of flags not previously covered by `BATTLE_VICTORY`.
- Added `battle_pokemon` to battle actor structs and `has_faced_opponents` to battle Pokémon structs.
- Added the `cobblemon:form_change/bagworm_cloak/plant_cloak`, `sandy_cloak` and `trash_cloak` biome tags, which control Burmy's cloak after battle.
### Localization
- Updated translations for:
- Brazilian Portuguese
- Canadian French
- Japanese
- Korean
- Russian
- Ukrainian
-107
View File
@@ -1,107 +0,0 @@
# Changelog
## [1.8.3 (October 25th, 2026)](#1-8-3)
### Additions
- Added compatability with AppleSkin.
### New Rideable Pokémon
- Vikavolt
- Fearow
### Particles
## New Move Animations
- Water Shuriken
### Cries added/updated for the following Pokémon
- Hoppip
- Skiploom
- Jumpluff
- Houndour
- Houndoom
- Wurmple
- Silcoon
- Beautifly
- Cascoon
- Dustox
- Surskit
- Masquerain
- Torkoal
- Starly
- Staravia
- Staraptor
- Combee
- Vespiquen
- Dusknoir
- Helioptile
- Heliolisk
- Grubbin
- Charjabug
- Vikavolt
- Applin (updated)
- Flapple (updated)
- Appletun
- Runerigus
- Dipplin
### Animation updates for the following Pokémon
- Vikavolt
- Magby
- Magmar
- Ninetales (+ Alolan)
- Spearow
- Fearow
- Squawkabilly
- Mudkip
- Marshtomp
- Swampert
### Changes
- Changed the Misc icon in the Campfire Pot Recipe Book to Health Candy.
- Moved EXP Candies from the Medicine recipe book category to the Misc category.
- Removed Dynamic Lights data from the Magmarizer item for consistency with the block.
- Added `doPokemonPredation` game rule, which controls aggressiveness of Pokémon with the `hate_entity` brain task defined.
- Added `predation_chance` server config value, which controls the possibility of attacking the hated entity found in `hate_entity` brain tasks.
- Prehistoric Eroded Pillar generates slightly more frequently.
- Togekiss was changed from a jet-style mount to bird-style.
### Fixes
- Fixed datapacks with item interaction evolutions that don't specify an item crashing the world on load.
- Fixed the Pokédex evolution tree crashing when a datapack's dex entry is missing an evolution's form.
- Fixed the Pokédex evolution tree crashing with a stack overflow on evolutions that loop back on themselves.
- Fixed the Pokédex evolution tree crashing when looking at a dex that is not part of the "National Dex" aggregate.
- Fixed some display names in the Pokédex evolution tree having unintended font colours.
- Fixed evolution trees not building properly when a Pokémon has several Pokédex entries for different regions.
- Fixed form-changing evolutions sometimes resolving to the source form in the Pokédex evolution tree.
- Broken datapack evolutions are now logged and skipped in the Pokédex evolution tree instead of crashing.
- Fixed the `alpha` property having no tab completion in commands such as `/pokespawn`, `/pokegive` and `/pokeedit`, and added the missing `dmax_level`, `gmax_factor` and `tradable` aliases to tab completion.
- Fixed friendship booster items not being recognised as such.
- Fixed conflicts with other mods that would make use of recipe books in certain ways (Farmer's Delight Cooking Pot).
- Fixed Pokémon battles stalling when a sidemod cancels the `THROWN_POKEBALL_HIT` hit event.
- Fixed choice feature tab completion in `spawnpokemon/pokegive/pokeedit` commands.
- Fixed MoLang that's targeting players intermittedly failing when they have died, requiring them to relog.
- Fixed a NeoForge bug where none of the Cobblemon plants/berries could be placed into pots.
- Fixed party overlay not hiding when the player opens the Pokédex scanner.
- Fixed `hate_entity` task focusing on its own entity, therefore enabling deadly warfare between Zangoose and Seviper.
- Prehistoric Frozen Pond, Prehistoric Frozen Spike, and Prehistoric Lush Den now actually generate (oops).
- Fixed Frogadier floating in its `battle_idle` animation.
- Fixed absence or incorrect mergers in spawning conditions, causing the max height/depth of conditions to use the min value instead.
- Added stricter riding checks on server side to prevent possible packet forgery when a player tries to ride Pokémon.
- Fixed Pokémon cries and animation sounds played from GUIs, such as the starter selection screen and the Pokédex, being tied to the Master volume slider instead of Friendly Creatures.
- Fixed Archeops T-posing during its `air_cry` animation.
### Developer
- Bumped NeoForge to `21.1.195` to get access to MixinExtras `0.5` and the expressions annotation.
- New Platform Event `PLAYER_RESPAWN` which is fired when a player respawns (either through death or returning from the end).
- Platform Events `SERVER_PLAYER_TICK_PRE` and `SERVER_PLAYER_TICK_POST` no longer fire when the player is dead.
- It is now tied much closer to the actual entity rather than the player or their connection.
### Molang & Datapacks
- Fixed `q.pokemon.nature` returning class instance instead of proper string value (`cobblemon:adamant`).
- Fixed `q.entity.is_looking_at` result being always `1` for whatever entity passed as the target parameter.
- Added hate chance optional parameter to `hate_entity` task. `1.0` will make an entity always attack the target.
### Localization
- Updated translations for:
- Russian
- Japanese
- Brazilian Portuguese
-25
View File
@@ -1,25 +0,0 @@
# Changelog
## [1.8.4 (October 27th, 2026)](#1-8-4)
### Changes
- The Froakie line now blinks. The world will never be the same.
### Fixes
- Fixed heaps of spawns not working. This was caused by us missing something before release. RIP.
- Fixed a crash caused by Fearow blinking while you're riding one. Makes sense to me.
- Fixed Minior's "Shields Down" ability being very broken.
- Fixed Dusclops using Squirtle's cry sound (lol).
- Fixed blank Cooking Pot recipe book entries and missing tabs on Fabric.
- Fixed a hitbox issue with placeable Orb held items attached to a ceiling.
- Fixed something sneaky. I'll tell you about it later.
### Molang & Datapacks
- Added `q.target_fainted` function to move action effects that checks if any opponents fainted from the move.
### Particles
- Updated Water Shuriken effect to be timed better when the opponent faints.
### Localization
- Updated translations for:
- Korean
- Simplified Chinese
+13 -15
View File
@@ -1,8 +1,9 @@
import utilities.isSnapshot
import utilities.version
import utilities.gitBranch
import utilities.gitCommit
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
/*
*
@@ -31,6 +32,7 @@ repositories {
maven(url = "https://api.modrinth.com/maven")
maven(url = "https://maven.neoforged.net/releases")
maven(url = "https://maven.gegy.dev")
mavenCentral()
mavenLocal()
}
@@ -39,6 +41,7 @@ dependencies {
modImplementation(libs.fabric.loader)
modApi(libs.molang)
// Integrations
compileOnlyApi(libs.jei.api)
modCompileOnly(libs.bundles.common.integrations.compileOnly) {
@@ -61,24 +64,21 @@ dependencies {
// Unit Testing
testImplementation(libs.bundles.unitTesting)
testImplementation(libs.graal.sdk)
testRuntimeOnly(libs.bundles.graal)
}
tasks.withType<Test> {
useJUnitPlatform()
// Static GL mocks must not load a graphics driver on headless CI.
systemProperty("org.lwjgl.opengl.explicitInit", "true")
testLogging {
setEvents(listOf("failed"))
setExceptionFormat("full")
}
System.getProperty("camera.benchmark")?.let { systemProperty("camera.benchmark", it) }
}
// Git info lives in its own output dir (not processResources) so a new commit only rewrites this one file.
val writeGitInfo by tasks.registering(WriteProperties::class) {
destinationFile = layout.buildDirectory.file("generated/gitInfo/cobblemon-git.properties")
property("commit", gitCommit())
property("branch", gitBranch())
}
sourceSets.main { output.dir(mapOf("builtBy" to writeGitInfo), layout.buildDirectory.dir("generated/gitInfo")) }
tasks.processResources {
exclude("data/**/README.md")
}
@@ -105,13 +105,11 @@ sourceSets {
property("modid", "cobblemon")
property("version", project.version())
property("isSnapshot", if(rootProject.isSnapshot()) "true" else "false")
property("gitCommit", versioning.info.commit)
property("branch", versioning.info.branch)
System.getProperty("buildNumber")?.let { property("buildNumber", it) }
property("timestamp", OffsetDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm:ss")) + " UTC")
}
}
}
}
// The platform jars are built from :common:jar, so the remapped common jars are only needed by `publish`
tasks.assemble {
setDependsOn(dependsOn.filterNot { (it as? TaskProvider<*>)?.name in setOf("remapJar", "remapSourcesJar") })
}
@@ -0,0 +1,153 @@
/*
* 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.render.camera.CameraFeedHandler;
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 net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.SectionPos;
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.LightLayer;
import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.chunk.LevelChunk;
import java.util.ArrayList;
import java.util.function.Consumer;
public final class CameraClientChunkCacheExtension {
private static final Long2ObjectOpenHashMap<LevelChunk> CHUNKS = new Long2ObjectOpenHashMap<>();
private CameraClientChunkCacheExtension() {}
public static boolean shouldKeepChunk(ChunkPos chunkPos) {
int renderDistance = Minecraft.getInstance().options.renderDistance().get();
return CameraChunkVisibilityManager.shouldKeepChunk(chunkPos, renderDistance)
|| CameraFeedHandler.shouldAddChunk(chunkPos, renderDistance);
}
public static void retain(LevelChunk chunk) {
synchronized (CHUNKS) {
CHUNKS.put(chunk.getPos().toLong(), chunk);
}
}
public static LevelChunk take(ClientLevel level, int x, int z) {
synchronized (CHUNKS) {
long position = ChunkPos.asLong(x, z);
LevelChunk chunk = CHUNKS.get(position);
return chunk != null && chunk.getLevel() == level ? CHUNKS.remove(position) : null;
}
}
public static void drop(ClientLevel level, ChunkPos chunkPos) {
LevelChunk chunk = take(level, chunkPos.x, chunkPos.z);
if (chunk != null) {
unload(level, chunk);
}
}
public static LevelChunk getChunk(int x, int z) {
synchronized (CHUNKS) {
return CHUNKS.get(ChunkPos.asLong(x, z));
}
}
public static LevelChunk replaceWithPacketData(ClientLevel level, int x, int z, FriendlyByteBuf packetData, CompoundTag chunkTag, Consumer<ClientboundLevelChunkPacketData.BlockEntityTagOutput> tagOutput) {
if (!Minecraft.getInstance().isSameThread()) {
throw new IllegalStateException("Camera chunk packet applied off the client thread");
}
ChunkPos chunkPos = new ChunkPos(x, z);
LevelChunk chunk = getChunk(x, z);
if (chunk == null || chunk.getLevel() != level) {
chunk = new LevelChunk(level, chunkPos);
}
chunk.replaceWithPacketData(packetData, chunkTag, tagOutput);
retain(chunk);
level.onChunkLoaded(chunkPos);
SodiumCompat.getInstance().notifyChunkAdded(level, x, z);
PlatformEvents.CLIENT_CHUNK_LOAD.postEvents(new ClientChunkEvent.Load(chunk, level));
return chunk;
}
public static void prune(ClientLevel level) {
for (LevelChunk chunk : snapshot()) {
if (chunk.getLevel() == level && !shouldKeepChunk(chunk.getPos())) {
try {
drop(level, chunk.getPos());
} finally {
queueLightRemoval(level, chunk.getPos());
}
}
}
}
public static void clear() {
ArrayList<LevelChunk> removed;
synchronized (CHUNKS) {
removed = new ArrayList<>(CHUNKS.values());
CHUNKS.clear();
}
RuntimeException failure = null;
for (LevelChunk chunk : removed) {
ClientLevel level = (ClientLevel) chunk.getLevel();
try {
try {
unload(level, chunk);
} finally {
queueLightRemoval(level, chunk.getPos());
}
} catch (RuntimeException exception) {
if (failure == null) failure = exception;
else if (failure != exception) failure.addSuppressed(exception);
}
}
if (failure != null) throw failure;
}
public static ArrayList<LevelChunk> snapshot() {
synchronized (CHUNKS) {
return new ArrayList<>(CHUNKS.values());
}
}
private static void queueLightRemoval(ClientLevel level, ChunkPos position) {
level.queueLightUpdate(() -> {
LevelLightEngine lightEngine = level.getLightEngine();
lightEngine.setLightEnabled(position, false);
for (int y = lightEngine.getMinLightSection(); y < lightEngine.getMaxLightSection(); y++) {
SectionPos section = SectionPos.of(position, y);
lightEngine.queueSectionData(LightLayer.BLOCK, section, null);
lightEngine.queueSectionData(LightLayer.SKY, section, null);
}
for (int y = level.getMinSection(); y < level.getMaxSection(); y++) {
lightEngine.updateSectionStatus(SectionPos.of(position, y), true);
}
});
}
public static void unload(ClientLevel level, LevelChunk chunk) {
ChunkPos position = chunk.getPos();
try {
SodiumCompat.getInstance().notifyChunkRemoved(level, position.x, position.z);
} finally {
try {
PlatformEvents.CLIENT_CHUNK_UNLOAD.postEvents(new ClientChunkEvent.Unload(chunk, level));
} finally {
level.unload(chunk);
}
}
}
}
@@ -0,0 +1,155 @@
/*
* 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 it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.client.renderer.chunk.SectionRenderDispatcher;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.Level;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.UUID;
public final class CameraViewAreaExtension {
private static final Long2ObjectOpenHashMap<SectionRenderDispatcher.RenderSection> SECTIONS = new Long2ObjectOpenHashMap<>();
private static final Map<UUID, LongSet> OWNERS = new HashMap<>();
private static SectionRenderDispatcher sectionRenderDispatcher;
private static final AtomicLong REVISION = new AtomicLong();
private static final AtomicLong GRAPH_REVISION = new AtomicLong();
private static final Set<SectionRenderDispatcher.RenderSection> CAMERA_SECTIONS = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>()));
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 (sectionRenderDispatcher == null || 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) {
REVISION.incrementAndGet();
Collection<SectionRenderDispatcher.RenderSection> removed = new ArrayList<>();
for (int sectionY = minSectionY; sectionY < maxSectionY; sectionY++) {
long sectionPos = SectionPos.asLong(sectionX, sectionY, sectionZ);
SectionRenderDispatcher.RenderSection section = SECTIONS.remove(sectionPos);
if (section != null) {
removed.add(section);
}
}
releaseBuffers(removed);
}
public static void clear() {
REVISION.incrementAndGet();
sectionRenderDispatcher = null;
minSectionY = 0;
maxSectionY = 0;
OWNERS.clear();
Collection<SectionRenderDispatcher.RenderSection> removed = new ArrayList<>(SECTIONS.values());
SECTIONS.clear();
releaseBuffers(removed);
}
public static long revision() {
return REVISION.get();
}
public static long graphRevision() {
return GRAPH_REVISION.get();
}
public static boolean onSectionCompiled(SectionRenderDispatcher.RenderSection section) {
// Late camera uploads must not enter the player's graph after their buffers have been released.
if (!CAMERA_SECTIONS.contains(section)) return false;
GRAPH_REVISION.incrementAndGet();
return true;
}
public static void retainSections(UUID cameraId, Collection<SectionRenderDispatcher.RenderSection> sections) {
LongSet positions = new LongOpenHashSet();
for (SectionRenderDispatcher.RenderSection section : sections) {
positions.add(SectionPos.asLong(section.getOrigin()));
}
LongSet previous = OWNERS.put(cameraId, positions);
releaseUnownedSections(previous);
}
public static void releaseSections(UUID cameraId) {
releaseUnownedSections(OWNERS.remove(cameraId));
}
private static void releaseUnownedSections(LongSet positions) {
if (positions == null) return;
Collection<SectionRenderDispatcher.RenderSection> removed = new ArrayList<>();
for (long position : positions) {
if (OWNERS.values().stream().anyMatch(owner -> owner.contains(position))) continue;
SectionRenderDispatcher.RenderSection section = SECTIONS.remove(position);
if (section != null) removed.add(section);
}
releaseBuffers(removed);
}
private static void releaseBuffers(Collection<SectionRenderDispatcher.RenderSection> sections) {
RuntimeException failure = null;
for (SectionRenderDispatcher.RenderSection section : sections) {
try {
section.releaseBuffers();
} catch (RuntimeException exception) {
if (failure == null) failure = exception;
else if (failure != exception) failure.addSuppressed(exception);
}
}
if (failure != null) throw failure;
}
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();
SectionRenderDispatcher.RenderSection section = sectionRenderDispatcher.new RenderSection(0, origin.getX(), origin.getY(), origin.getZ());
CAMERA_SECTIONS.add(section);
return section;
}
}
@@ -0,0 +1,14 @@
/*
* 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;
public interface CameraFeedRenderState {
boolean cobblemonRenderHand();
boolean cobblemonRenderBlockOutline();
}
@@ -8,42 +8,54 @@
package com.cobblemon.mod.common.mixin;
import com.llamalad7.mixinextras.sugar.Local;
import com.cobblemon.mod.common.block.SaccharineLeafBlock;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.animal.Bee;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Final;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(targets = "net.minecraft.world.entity.animal.Bee$BeeGrowCropGoal")
public abstract class BeeEntityMixin {
@Shadow(aliases = {"this$0", "field_20373"}) @Final private Bee bee;
@Unique
private BlockState cobblemon$result = null;
@Inject(method = "tick()V", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/block/BonemealableBlock;performBonemeal(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/util/RandomSource;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)V",
shift = At.Shift.BY,
by = 2
),
locals = LocalCapture.CAPTURE_FAILHARD
)
private void injectCustomGrowth(CallbackInfo ci, int i, BlockPos blockPos, BlockState blockState, Block block) {
if (block instanceof SaccharineLeafBlock) {
int age = blockState.getValue(SaccharineLeafBlock.Companion.getAGE());
boolean waterlogged = blockState.getValue(BlockStateProperties.WATERLOGGED);
if (age < 2 && !waterlogged) {
this.cobblemon$result = blockState.setValue(SaccharineLeafBlock.Companion.getAGE(), age + 1);
}
}
}
// Grows bonemealable blocks vanilla's hardcoded checks miss, returning the state so it handles the rest.
@ModifyVariable(method = "tick", at = @At(value = "LOAD", ordinal = 0), index = 5)
private BlockState cobblemon$growBonemealableBlocks(BlockState grownState, @Local(ordinal = 0) BlockState blockState, @Local BlockPos pos) {
if (grownState != null || blockState.is(Blocks.CAVE_VINES) || blockState.is(Blocks.CAVE_VINES_PLANT)) {
return grownState;
private BlockState applyCustomBlockState(BlockState before) {
if(this.cobblemon$result != null && before == null) {
BlockState result = this.cobblemon$result;
this.cobblemon$result = null;
return result;
}
if (!(blockState.getBlock() instanceof BonemealableBlock bonemealable)) {
return null;
}
ServerLevel level = (ServerLevel) this.bee.level();
if (!bonemealable.isValidBonemealTarget(level, pos, blockState)) {
return null;
}
bonemealable.performBonemeal(level, this.bee.getRandom(), pos, blockState);
return level.getBlockState(pos);
return before;
}
}
@@ -15,7 +15,6 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.mojang.serialization.MapCodec;
import java.util.Optional;
import java.util.function.Supplier;
import net.minecraft.SharedConstants;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.IntTag;
import net.minecraft.resources.ResourceKey;
@@ -46,24 +45,4 @@ public class ChunkStorageMixin {
int curVersion = vanillaFixed.contains(VERSION_KEY) ? vanillaFixed.getInt(VERSION_KEY) : 0;
return DataFixTypes.CHUNK.update(CobblemonSchemas.getDATA_FIXER(), vanillaFixed, curVersion, CobblemonSchemas.DATA_VERSION);
}
// Our update on the same MC version still needs its own fixes so run them here in that case.
@Inject(
method = "upgradeChunkTag(Lnet/minecraft/resources/ResourceKey;Ljava/util/function/Supplier;Lnet/minecraft/nbt/CompoundTag;Ljava/util/Optional;)Lnet/minecraft/nbt/CompoundTag;",
at = @At("HEAD"),
cancellable = true
)
private void cobblemon$doChunkFixOnCurrentVersion(
ResourceKey<Level> levelKey,
Supplier<DimensionDataStorage> storage,
CompoundTag chunkData,
Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey,
CallbackInfoReturnable<CompoundTag> cir
) {
if (ChunkStorage.getVersion(chunkData) != SharedConstants.getCurrentVersion().getDataVersion().getVersion()) {
return;
}
int curVersion = chunkData.contains(VERSION_KEY) ? chunkData.getInt(VERSION_KEY) : 0;
cir.setReturnValue(DataFixTypes.CHUNK.update(CobblemonSchemas.getDATA_FIXER(), chunkData, curVersion, CobblemonSchemas.DATA_VERSION));
}
}
@@ -8,6 +8,8 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.api.cinematic.runtime.CinematicManager;
import net.minecraft.world.damagesource.DamageSource;
import com.cobblemon.mod.common.CobblemonNetwork;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
@@ -132,6 +134,16 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
}
}
@Inject(method = "hurt(Lnet/minecraft/world/damagesource/DamageSource;F)Z", at = @At("HEAD"), cancellable = true)
private void cobblemon$cancelDamageDuringCinematicTakeover(DamageSource source, float amount, CallbackInfoReturnable<Boolean> cir) {
final LivingEntity self = (LivingEntity) (Object) this;
if (self instanceof ServerPlayer serverPlayer) {
if (CinematicManager.INSTANCE.isPlayerUnderImmuneTakeover(serverPlayer.getUUID())) {
cir.setReturnValue(false);
}
}
}
@Inject(method = "decreaseAirSupply", at = @At("HEAD"), cancellable = true)
private void cobblemon$conditionalWaterBreathing(
int currentAir,
@@ -35,16 +35,13 @@ import com.cobblemon.mod.common.util.DataKeys;
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
@@ -53,7 +50,6 @@ import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
@@ -375,33 +371,4 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
this.cobblemon$rideEyePos = rideEyePos;
}
/**
* Sweeping attacks apply post-attack enchantment effects (e.g. Fire Aspect) to every entity caught
* in the sweep, even if the damage itself was blocked. Skip them for Pokémon immune to the attack.
*/
@WrapOperation(
method = "attack",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/enchantment/EnchantmentHelper;doPostAttackEffects(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;)V")
)
private void cobblemon$skipPostAttackEffectsOnImmunePokemon(ServerLevel level, Entity target, DamageSource source, Operation<Void> original) {
if (target instanceof PokemonEntity pokemonEntity && pokemonEntity.isInvulnerableTo(source)) {
return;
}
original.call(level, target, source);
}
/**
* Sweeping attacks also knock back every entity caught in the sweep regardless of whether it took damage.
*/
@WrapOperation(
method = "attack",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;knockback(DDD)V")
)
private void cobblemon$skipKnockbackOnImmunePokemon(LivingEntity target, double strength, double x, double z, Operation<Void> original, @Local DamageSource source) {
if (target instanceof PokemonEntity pokemonEntity && pokemonEntity.isInvulnerableTo(source)) {
return;
}
original.call(target, strength, x, z);
}
}
@@ -15,7 +15,6 @@ import com.cobblemon.mod.common.api.riding.RidingStyle;
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner;
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawnerFactory;
import com.cobblemon.mod.common.api.stats.CobblemonStats;
import com.cobblemon.mod.common.block.HealingMachineBlock;
import com.cobblemon.mod.common.duck.ServerLevelDuck;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules;
@@ -23,14 +22,10 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.core.BlockPos;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -38,9 +33,6 @@ import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Optional;
@Mixin(ServerPlayer.class)
public abstract class ServerPlayerMixin implements PlayerSpawnerAccessor {
@@ -150,17 +142,4 @@ public abstract class ServerPlayerMixin implements PlayerSpawnerAccessor {
((ServerLevelDuck) this.serverLevel()).cobblemon$getLevelResourceLocation()
);
}
@Inject(
method = "findRespawnAndUseSpawnBlock(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;FZZ)Ljava/util/Optional;",
at = @At(value = "HEAD"),
cancellable = true
)
private static void cobblemon$useHealthStationAsRespawn(ServerLevel level, BlockPos pos, float angle, boolean forced, boolean keepInventory, CallbackInfoReturnable<Optional<ServerPlayer.RespawnPosAngle>> cir) {
BlockState state = level.getBlockState(pos);
Block block = state.getBlock();
if (block instanceof HealingMachineBlock && HealingMachineBlock.Companion.canSetSpawn(level)) {
cir.setReturnValue(HealingMachineBlock.Companion.findStandUpPosition(EntityType.PLAYER, level, pos).map((vec3) -> ServerPlayer.RespawnPosAngle.of(vec3, pos)));
}
}
}
@@ -11,9 +11,12 @@ package com.cobblemon.mod.common.mixin.accessor;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(Entity.class)
public interface AccessorEntity {
@Invoker("updateFluidOnEyes")
void cobblemonUpdateFluidOnEyes();
@Accessor("eyeHeight")
void standingEyeHeight(float standingEyeHeight);
@@ -0,0 +1,19 @@
/*
* 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 org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(Camera.class)
public interface CameraAccessor {
@Accessor("detached")
void setDetached(boolean detached);
}
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.accessor;
import net.minecraft.client.multiplayer.ClientChunkCache;
import net.minecraft.world.level.chunk.LevelChunk;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import java.util.concurrent.atomic.AtomicReferenceArray;
@Mixin(ClientChunkCache.Storage.class)
public interface CameraChunkStorageAccessor {
@Accessor("chunks")
AtomicReferenceArray<LevelChunk> cobblemon$getChunks();
@Accessor("chunkRadius")
int cobblemon$getChunkRadius();
@Accessor("chunkCount")
int cobblemon$getChunkCount();
@Accessor("chunkCount")
void cobblemon$setChunkCount(int count);
@Accessor("viewCenterX")
int cobblemon$getViewCenterX();
@Accessor("viewCenterZ")
int cobblemon$getViewCenterZ();
@Invoker("getIndex")
int cobblemon$getIndex(int x, int z);
}
@@ -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.mixin.accessor;
import net.minecraft.server.level.ServerEntity;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(ServerEntity.class)
public interface ServerEntityEntityAccessor {
@Accessor("entity")
Entity getEntity();
}
@@ -9,10 +9,13 @@
package com.cobblemon.mod.common.mixin.accessor;
import net.minecraft.server.level.ServerEntity;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerPlayerConnection;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import java.util.List;
import java.util.Set;
@Mixin(targets = "net.minecraft.server.level.ChunkMap$TrackedEntity")
@@ -22,4 +25,7 @@ public interface TrackedEntityAccessor {
@Accessor("serverEntity")
ServerEntity getServerEntity();
}
@Invoker("updatePlayers")
void cobblemon$updatePlayers(List<ServerPlayer> players);
}
@@ -0,0 +1,47 @@
/*
* 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.cinematic;
import com.cobblemon.mod.common.api.cinematic.runtime.CinematicViewerScopedEntity;
import com.cobblemon.mod.common.mixin.accessor.ServerEntityEntityAccessor;
import com.cobblemon.mod.common.mixin.accessor.TrackedEntityAccessor;
import net.minecraft.server.level.ServerEntity;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Set;
import java.util.UUID;
@Mixin(targets = "net.minecraft.server.level.ChunkMap$TrackedEntity")
public abstract class TrackedEntityMixin {
// Cancellation must also unpair existing viewers, or they keep receiving updates after the scope narrows.
@Inject(method = "updatePlayer", at = @At("HEAD"), cancellable = true)
private void cobblemon$filterViewerScopedEntity(ServerPlayer player, CallbackInfo ci) {
final TrackedEntityAccessor self = (TrackedEntityAccessor) this;
final ServerEntity serverEntity = self.getServerEntity();
if (serverEntity == null) return;
final Entity entity = ((ServerEntityEntityAccessor) serverEntity).getEntity();
if (entity == null) return;
if (entity instanceof CinematicViewerScopedEntity scoped) {
final Set<UUID> allowed = scoped.getCobblemonSceneViewers();
if (allowed != null && !allowed.contains(player.getUUID())) {
if (self.getSeenBy().remove(player.connection)) {
serverEntity.removePairing(player);
}
ci.cancel();
}
}
}
}
@@ -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.mixin.client;
import com.cobblemon.mod.common.client.camera.CameraClientChunkCacheExtension;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.protocol.game.ClientboundForgetLevelChunkPacket;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.LevelChunk;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientPacketListener.class)
public abstract class CameraClientPacketListenerMixin {
@Shadow
private ClientLevel level;
@Inject(method = "queueLightRemoval", at = @At("HEAD"), cancellable = true)
private void cobblemon$keepCameraLight(ClientboundForgetLevelChunkPacket packet, CallbackInfo ci) {
ChunkPos position = packet.pos();
LevelChunk retained = CameraClientChunkCacheExtension.getChunk(position.x, position.z);
if (retained != null && retained.getLevel() == level) {
ci.cancel();
}
}
}
@@ -0,0 +1,127 @@
/*
* 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.render.camera.CameraFeedHandler;
import com.cobblemon.mod.common.client.render.camera.CameraViewAreaExtension;
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.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 {
private static final int CINEMATIC_RENDER_DISTANCE_CHUNKS = 32;
private static final float CINEMATIC_FOG_DISTANCE = CINEMATIC_RENDER_DISTANCE_CHUNKS * 16;
@Shadow
private SectionRenderDispatcher sectionRenderDispatcher;
@Shadow
private ClientLevel level;
@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();
}
// Sodium owns its setup pipeline even while vanilla feed sections are substituted.
@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()) {
CameraFeedHandler.prepareVisibleSections(frustum);
ci.cancel();
}
}
@Inject(method = "setLevel", at = @At("HEAD"))
private void cobblemon$onSetLevel(ClientLevel level, CallbackInfo ci) {
try {
if (this.level != level) {
CameraClientChunkCacheExtension.clear();
}
} finally {
CameraViewAreaExtension.clear();
}
}
@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 = "addRecentlyCompiledSection", at = @At("HEAD"), cancellable = true)
private void cobblemon$onSectionCompiled(SectionRenderDispatcher.RenderSection section, CallbackInfo ci) {
if (CameraViewAreaExtension.onSectionCompiled(section)) {
ci.cancel();
}
}
@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;
}
// A Marker camera otherwise excludes the local player from vanilla entity rendering.
@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);
}
}
@@ -8,8 +8,12 @@
package com.cobblemon.mod.common.mixin.client;
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;
@@ -19,12 +23,15 @@ import net.minecraft.client.Camera;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.phys.Vec3;
import java.util.UUID;
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.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Camera.class)
public abstract class CameraMixin implements CameraDuck {
@@ -32,6 +39,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 +51,7 @@ public abstract class CameraMixin implements CameraDuck {
@Override public BlockGetter cobblemon$getLevel() {
return level;
}
@Shadow protected abstract void setRotation(float yRot, float xRot);
@Override public Entity cobblemon$getEntity() {
return entity;
@@ -86,4 +95,29 @@ public abstract class CameraMixin implements CameraDuck {
MountedCameraRenderer.INSTANCE.setSmoothRotation(null);
original.call(instance, x, y, z);
}
// Apply after vanilla third-person and sleeping offsets so authored transforms remain exact.
@Inject(method = "setup", at = @At("RETURN"))
private void cobblemon$applyCinematicTransform(BlockGetter level, Entity entity, boolean detached, boolean thirdPersonReverse, float partialTick, CallbackInfo ci) {
UUID capturedId = CameraFeedHandler.getCurrentlyCapturedCamera();
CameraFeedHandler.FeedContext context = capturedId != null ? CameraFeedHandler.getFeedContext(capturedId) : null;
float roll;
if (context != null) {
setPosition(context.getPosition());
setRotation(context.getYaw(), context.getPitch());
roll = context.getRoll();
} else {
if (!ClientCameraController.shouldUseCinematicCamera()) return;
ClientCameraController.ActiveTransform transform = ClientCameraController.getCameraTransform();
if (transform == null) return;
CameraRotation rotation = transform.getRotation();
setPosition(transform.getPosition());
setRotation(rotation.getYaw(), rotation.getPitch());
roll = transform.getRoll();
}
this.rotation.rotateZ((float) Math.toRadians(roll));
OrientationController.Companion.getFORWARDS().rotate(this.rotation, this.forwards);
OrientationController.Companion.getUP().rotate(this.rotation, this.up);
OrientationController.Companion.getLEFT().rotate(this.rotation, this.left);
}
}
@@ -0,0 +1,178 @@
/*
* 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.mixin.accessor.CameraChunkStorageAccessor;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
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.Level;
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.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
@Mixin(value = ClientChunkCache.class, priority = 1100)
public abstract class ClientChunkCacheMixin {
@Shadow
volatile ClientChunkCache.Storage storage;
@Shadow
@Final
ClientLevel level;
@Shadow
@Final
private LevelChunk emptyChunk;
@Inject(method = "drop", at = @At("HEAD"), cancellable = true)
private void cobblemon$onDrop(ChunkPos chunkPos, CallbackInfo ci) {
if (CameraClientChunkCacheExtension.shouldKeepChunk(chunkPos)) {
CameraChunkStorageAccessor access = (CameraChunkStorageAccessor) (Object) storage;
int index = access.cobblemon$getIndex(chunkPos.x, chunkPos.z);
LevelChunk chunk = access.cobblemon$getChunks().get(index);
if (chunk != null && chunk.getPos().equals(chunkPos)) {
cobblemon$retain(access, index, chunk);
}
ci.cancel();
} else {
CameraClientChunkCacheExtension.drop(level, chunkPos);
}
}
@Inject(method = "updateViewCenter", at = @At("HEAD"))
private void cobblemon$onViewCenter(int x, int z, CallbackInfo ci) {
CameraChunkStorageAccessor access = (CameraChunkStorageAccessor) (Object) storage;
cobblemon$retainOutsideRange(access, x, z, access.cobblemon$getChunkRadius());
}
@Inject(method = "updateViewRadius", at = @At("HEAD"))
private void cobblemon$onViewRadius(int radius, CallbackInfo ci) {
CameraChunkStorageAccessor access = (CameraChunkStorageAccessor) (Object) storage;
cobblemon$retainOutsideRange(access, access.cobblemon$getViewCenterX(), access.cobblemon$getViewCenterZ(), Math.max(2, radius) + 3);
}
@Inject(method = "updateViewCenter", at = @At("TAIL"))
private void cobblemon$afterViewCenter(int x, int z, CallbackInfo ci) {
cobblemon$promoteCameraChunks((CameraChunkStorageAccessor) (Object) storage);
}
@Inject(method = "updateViewRadius", at = @At("TAIL"))
private void cobblemon$afterViewRadius(int radius, CallbackInfo ci) {
cobblemon$promoteCameraChunks((CameraChunkStorageAccessor) (Object) storage);
}
@Inject(method = "tick", at = @At("HEAD"))
private void cobblemon$onTick(BooleanSupplier hasTimeLeft, boolean tickChunks, CallbackInfo ci) {
CameraClientChunkCacheExtension.prune(level);
}
@Inject(method = "replaceWithPacketData", at = @At("HEAD"), cancellable = true)
private void cobblemon$onReplaceWithPacketData(int x, int z, FriendlyByteBuf buffer, CompoundTag chunkTag, Consumer<ClientboundLevelChunkPacketData.BlockEntityTagOutput> tagOutputConsumer, CallbackInfoReturnable<LevelChunk> cir) {
if (!storage.inRange(x, z)) {
if (CameraClientChunkCacheExtension.shouldKeepChunk(new ChunkPos(x, z))) {
cir.setReturnValue(CameraClientChunkCacheExtension.replaceWithPacketData(level, x, z, buffer, chunkTag, tagOutputConsumer));
}
return;
}
CameraChunkStorageAccessor access = (CameraChunkStorageAccessor) (Object) storage;
int index = access.cobblemon$getIndex(x, z);
LevelChunk previous = access.cobblemon$getChunks().get(index);
if (previous != null && (previous.getPos().x != x || previous.getPos().z != z)
&& CameraClientChunkCacheExtension.shouldKeepChunk(previous.getPos())) {
cobblemon$retain(access, index, previous);
}
}
@WrapOperation(method = "replaceWithPacketData", at = @At(value = "NEW", target = "(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/level/ChunkPos;)Lnet/minecraft/world/level/chunk/LevelChunk;"))
private LevelChunk cobblemon$reuseCameraChunk(Level level, ChunkPos position, Operation<LevelChunk> original) {
LevelChunk retained = CameraClientChunkCacheExtension.take(this.level, position.x, position.z);
return retained != null ? retained : original.call(level, position);
}
@Inject(method = "replaceBiomes", at = @At("HEAD"), cancellable = true)
private void cobblemon$onReplaceBiomes(int x, int z, FriendlyByteBuf buffer, CallbackInfo ci) {
LevelChunk chunk = CameraClientChunkCacheExtension.getChunk(x, z);
if (chunk != null && chunk.getLevel() == level) {
chunk.replaceBiomes(buffer);
ci.cancel();
}
}
@Inject(method = "getChunk(IILnet/minecraft/world/level/chunk/status/ChunkStatus;Z)Lnet/minecraft/world/level/chunk/LevelChunk;", at = @At("RETURN"), cancellable = true)
private void cobblemon$onGetChunk(int x, int z, ChunkStatus chunkStatus, boolean requireChunk, CallbackInfoReturnable<LevelChunk> cir) {
if (cir.getReturnValue() == null || cir.getReturnValue() == emptyChunk) {
LevelChunk chunk = CameraClientChunkCacheExtension.getChunk(x, z);
if (chunk != null && chunk.getLevel() == level) {
cir.setReturnValue(chunk);
}
}
}
@Unique
private void cobblemon$promoteCameraChunks(CameraChunkStorageAccessor access) {
AtomicReferenceArray<LevelChunk> chunks = access.cobblemon$getChunks();
for (LevelChunk chunk : CameraClientChunkCacheExtension.snapshot()) {
ChunkPos position = chunk.getPos();
if (chunk.getLevel() != level
|| Math.abs(position.x - access.cobblemon$getViewCenterX()) > access.cobblemon$getChunkRadius()
|| Math.abs(position.z - access.cobblemon$getViewCenterZ()) > access.cobblemon$getChunkRadius()) continue;
int index = access.cobblemon$getIndex(position.x, position.z);
LevelChunk previous = chunks.get(index);
if (previous != null && CameraClientChunkCacheExtension.shouldKeepChunk(previous.getPos())) {
cobblemon$retain(access, index, previous);
previous = null;
}
CameraClientChunkCacheExtension.take(level, position.x, position.z);
chunks.set(index, chunk);
if (previous == null) {
access.cobblemon$setChunkCount(access.cobblemon$getChunkCount() + 1);
} else {
CameraClientChunkCacheExtension.unload(level, previous);
}
}
}
@Unique
private void cobblemon$retainOutsideRange(CameraChunkStorageAccessor access, int centerX, int centerZ, int radius) {
AtomicReferenceArray<LevelChunk> chunks = access.cobblemon$getChunks();
for (int index = 0; index < chunks.length(); index++) {
LevelChunk chunk = chunks.get(index);
if (chunk == null) continue;
ChunkPos position = chunk.getPos();
if ((Math.abs(position.x - centerX) > radius || Math.abs(position.z - centerZ) > radius)
&& CameraClientChunkCacheExtension.shouldKeepChunk(position)) {
cobblemon$retain(access, index, chunk);
}
}
}
@Unique
private void cobblemon$retain(CameraChunkStorageAccessor access, int index, LevelChunk chunk) {
// Detach before loader unload callbacks: lighting and block entities belong to this same LevelChunk.
if (access.cobblemon$getChunks().compareAndSet(index, chunk, null)) {
access.cobblemon$setChunkCount(access.cobblemon$getChunkCount() - 1);
CameraClientChunkCacheExtension.retain(chunk);
}
}
}
@@ -16,7 +16,6 @@ import com.cobblemon.mod.common.item.crafting.CookingPotShapelessRecipe;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.ClientRecipeBook;
import net.minecraft.client.RecipeBookCategories;
import net.minecraft.client.gui.screens.recipebook.RecipeCollection;
@@ -64,12 +63,23 @@ public abstract class ClientRecipeBookMixin {
}
}
@Inject(method = "setupCollections",
at = @At(
value = "INVOKE",
target = "Lcom/google/common/collect/ImmutableMap;copyOf(Ljava/util/Map;)Lcom/google/common/collect/ImmutableMap;"
))
private void addCustomAggregateCategories(Iterable<RecipeHolder<?>> recipes, RegistryAccess registryAccess, CallbackInfo ci, @Local(ordinal = 1) Map<RecipeBookCategories, List<RecipeCollection>> map2) {
@Inject(method = "setupCollections", at = @At("RETURN"))
private void addCustomAggregateCategories(Iterable<RecipeHolder<?>> recipes, RegistryAccess registryAccess, CallbackInfo ci) {
Map<RecipeBookCategories, List<List<RecipeHolder<?>>>> map = categorizeAndGroupRecipes(recipes);
Map<RecipeBookCategories, List<RecipeCollection>> map2 = Maps.newHashMap();
ImmutableList.Builder<RecipeCollection> builder = ImmutableList.builder();
map.forEach((recipeBookCategories, list) -> {
List<RecipeCollection> collections = list.stream()
.map(listx -> new RecipeCollection(registryAccess, listx))
.peek(builder::add)
.collect(ImmutableList.toImmutableList());
map2.put(recipeBookCategories, collections);
});
RecipeBookCategories.AGGREGATE_CATEGORIES.forEach((recipeBookCategories, list) -> map2.put(recipeBookCategories, (List) list.stream().flatMap((recipeBookCategoriesx) -> ((List) map2.getOrDefault(recipeBookCategoriesx, ImmutableList.of())).stream()).collect(ImmutableList.toImmutableList())));
CobblemonRecipeCategories.Companion.getCustomAggregateCategories().forEach((recipeBookCategories, list) -> map2.put(recipeBookCategories, (List) list.stream().flatMap((recipeBookCategoriesx) -> ((List) map2.getOrDefault(recipeBookCategoriesx, ImmutableList.of())).stream()).collect(ImmutableList.toImmutableList())));
this.collectionsByTab = ImmutableMap.copyOf(map2);
this.allCollections = builder.build();
}
}
@@ -9,43 +9,142 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker;
import com.cobblemon.mod.common.client.camera.GameCamera;
import com.cobblemon.mod.common.client.camera.ClientCameraController;
import com.cobblemon.mod.common.client.cinematic.ClientSceneSessionManager;
import com.cobblemon.mod.common.client.render.camera.accessor.CameraFeedRenderState;
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.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
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 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.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.function.Predicate;
import java.util.UUID;
@Mixin(GameRenderer.class)
public class GameRendererMixin {
@Unique
long lastTime = -1;
public abstract class GameRendererMixin implements CameraFeedRenderState {
@Inject(
method = "render",
at = @At(value = "TAIL")
)
public void render(DeltaTracker counter, boolean tick, CallbackInfo ci) {
@Shadow private boolean renderHand;
@Shadow private boolean renderBlockOutline;
@Override public boolean cobblemonRenderHand() { return renderHand; }
@Override public boolean cobblemonRenderBlockOutline() { return renderBlockOutline; }
@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);
ClientCameraController.advanceFrame(client.isPaused() ? 0f : counter.getRealtimeDeltaTicks() / 20f);
ClientSceneSessionManager.INSTANCE.getAllSessions().forEach(session -> session.refreshCompositor());
if (ClientCameraController.shouldUseCinematicCamera()) {
if (!cobblemon$usingCinematic) {
cobblemon$playerCamera = mainCamera;
mainCamera = cobblemon$gameCamera;
cobblemon$usingCinematic = true;
}
cobblemon$gameCamera.prepare(client, counter.getGameTimeDeltaPartialTick(tick));
} else if (cobblemon$usingCinematic) {
cobblemon$restoreCamera();
}
lastTime = newTime;
}
// WrapOperation allows Iris/Sodium to share this call site; Redirect claims it exclusively.
@WrapOperation(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, Operation<Void> original) {
if (!CompositorManager.isFullscreenActive()) {
original.call(instance, 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) {
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 = "getFov", at = @At("HEAD"), cancellable = true)
private void cobblemon$overrideCinematicFov(Camera camera, float partialTicks, boolean useFovSetting, CallbackInfoReturnable<Double> cir) {
UUID capturedId = CameraFeedHandler.getCurrentlyCapturedCamera();
Float fov;
if (capturedId != null) {
CameraFeedHandler.FeedContext context = CameraFeedHandler.getFeedContext(capturedId);
fov = context != null ? context.getFov() : null;
} else {
if (camera != cobblemon$gameCamera || !ClientCameraController.shouldUseCinematicCamera()) return;
fov = ClientCameraController.getPrimaryFovOverride();
}
if (fov != null) {
cir.setReturnValue(fov.doubleValue());
}
}
@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);
}
cobblemon$lastTime = newTime;
}
@ModifyArg(
@@ -60,4 +159,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;
}
}
}
@@ -10,13 +10,11 @@ package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker;
import com.cobblemon.mod.common.duck.RidePassenger;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import org.spongepowered.asm.mixin.Final;
@@ -98,14 +96,4 @@ public class HumanoidModelMixin {
MountedPlayerRenderer.INSTANCE.animate((HumanoidModel<AbstractClientPlayer>)(Object)this, pokemonEntity, player, netHeadYaw, headPitch, ageInTicks, limbSwing, limbSwingAmount);
}
}
@Inject(method = "setupAnim*", at = @At("TAIL"))
public void cobblemon$poseRecallArm(LivingEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch, CallbackInfo ci) {
if (!(entity instanceof AbstractClientPlayer) || entity.getVehicle() instanceof PokemonEntity) return;
float raise = RecallArmAnimationTracker.INSTANCE.raiseFactor(entity.getUUID());
if (raise <= 0f) return;
this.rightArm.xRot = Mth.lerp(raise, this.rightArm.xRot, (float) (-Math.PI / 2.0));
this.rightArm.yRot = Mth.lerp(raise, this.rightArm.yRot, 0f);
this.rightArm.zRot = Mth.lerp(raise, this.rightArm.zRot, 0f);
}
}
@@ -1,32 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.client.render.player.RecallArmAnimationTracker;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.layers.ItemInHandLayer;
import net.minecraft.world.entity.HumanoidArm;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ItemInHandLayer.class)
public class ItemInHandLayerMixin {
@Inject(method = "renderArmWithItem", at = @At("HEAD"), cancellable = true)
private void cobblemon$hideRecallHandItem(LivingEntity entity, ItemStack itemStack, ItemDisplayContext itemDisplayContext, HumanoidArm humanoidArm, PoseStack poseStack, MultiBufferSource multiBufferSource, int light, CallbackInfo ci) {
if (humanoidArm == HumanoidArm.RIGHT && RecallArmAnimationTracker.INSTANCE.isRecalling(entity.getUUID())) {
ci.cancel();
}
}
}
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.client.render.player.FirstPersonRecallBallRenderer;
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;
@@ -29,10 +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) {
if (abstractClientPlayer.isUsingItem() && abstractClientPlayer.getUseItem().getItem() instanceof PokedexItem) {
if (ClientCameraController.shouldUseCinematicCamera()) {
ci.cancel();
return;
}
else if (interactionHand == InteractionHand.MAIN_HAND && FirstPersonRecallBallRenderer.INSTANCE.render(poseStack, multiBufferSource, j, f)) {
if (abstractClientPlayer.isUsingItem() && abstractClientPlayer.getUseItem().getItem() instanceof PokedexItem) {
ci.cancel();
}
else if (abstractClientPlayer instanceof OrientationControllable controllable && controllable.getOrientationController().isActive()) {
@@ -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;
@@ -31,11 +34,34 @@ public abstract class PlayerRendererMixin extends LivingEntityRenderer<AbstractC
super(context, model, shadowRadius);
}
@WrapOperation(
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, Operation<Boolean> original) {
if (ClientCameraController.shouldUseCinematicCamera() && ClientCameraController.shouldRenderPlayerBody()) {
return false;
}
return original.call(instance);
}
@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);
}
@Inject(method = "render(Lnet/minecraft/client/player/AbstractClientPlayer;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At("HEAD"), cancellable = true)
private void cobblemon$hideCinematicPlayer(AbstractClientPlayer player, float yaw, float partialTick, PoseStack poseStack, MultiBufferSource buffers, int light, CallbackInfo ci) {
if (player != Minecraft.getInstance().player) return;
if (ClientCameraController.shouldUseCinematicCamera() && !ClientCameraController.shouldRenderPlayerBody()) {
ci.cancel();
return;
}
}
@WrapOperation(method = "getRenderOffset(Lnet/minecraft/client/player/AbstractClientPlayer;F)Lnet/minecraft/world/phys/Vec3;", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/AbstractClientPlayer;isCrouching()Z"))
private boolean cobblemon$disableCrouchOffset(AbstractClientPlayer instance, Operation<Boolean> original) {
if (instance.isPassenger() && instance.getVehicle() instanceof PokemonEntity) {
@@ -9,25 +9,55 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.CobblemonRecipeCategories;
import com.llamalad7.mixinextras.expression.Definition;
import com.llamalad7.mixinextras.expression.Expression;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.ClientRecipeBook;
import net.minecraft.client.Minecraft;
import net.minecraft.client.RecipeBookCategories;
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
import net.minecraft.client.gui.screens.recipebook.RecipeBookPage;
import net.minecraft.client.gui.screens.recipebook.RecipeBookTabButton;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(RecipeBookComponent.class)
public class RecipeBookComponentMixin {
@Definition(id = "recipeBookCategories", local = @Local(type = RecipeBookCategories.class))
@Definition(id = "FURNACE_SEARCH", field = "Lnet/minecraft/client/RecipeBookCategories;FURNACE_SEARCH:Lnet/minecraft/client/RecipeBookCategories;")
@Expression("recipeBookCategories != FURNACE_SEARCH")
@ModifyExpressionValue(
method = "updateTabs",
at = @At(value = "MIXINEXTRAS:EXPRESSION")
)
private boolean notCookingPotSearch(boolean original, @Local RecipeBookCategories recipeBookCategories) {
return original && recipeBookCategories != CobblemonRecipeCategories.COOKING_POT_SEARCH.toVanillaCategory();
@Shadow private ClientRecipeBook book;
@Shadow protected Minecraft minecraft;
@Shadow private int width;
@Shadow private int xOffset;
@Shadow private int height;
@Shadow @Final private RecipeBookPage recipeBookPage;
@Inject(method = "updateTabs", at = @At("HEAD"), cancellable = true)
private void customUpdateTabs(CallbackInfo ci) {
RecipeBookComponent instance = (RecipeBookComponent) (Object) this;
int i = (width - 147) / 2 - xOffset - 30;
int j = (height - 166) / 2 + 3;
int k = 27;
int l = 0;
for (RecipeBookTabButton recipeBookTabButton : instance.tabButtons) {
RecipeBookCategories recipeBookCategories = recipeBookTabButton.getCategory();
if (recipeBookCategories != RecipeBookCategories.CRAFTING_SEARCH && recipeBookCategories != RecipeBookCategories.FURNACE_SEARCH && recipeBookCategories != CobblemonRecipeCategories.COOKING_POT_SEARCH.toVanillaCategory()) {
if (recipeBookTabButton.updateVisibility(book)) {
recipeBookTabButton.setPosition(i, j + 27 * l++);
recipeBookTabButton.startAnimation(minecraft);
}
} else {
recipeBookTabButton.visible = true;
recipeBookTabButton.setPosition(i, j + k * l++);
}
}
ci.cancel();
}
}
@@ -0,0 +1,30 @@
/*
* 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.RenderAlphaScope;
import net.minecraft.client.renderer.RenderStateShard;
import net.minecraft.client.renderer.RenderType;
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(RenderStateShard.class)
public abstract class RenderStateShardMixin {
@Inject(method = "setupRenderState", at = @At("RETURN"))
private void cobblemon$applyScopedAlpha(CallbackInfo ci) {
if ((Object) this instanceof RenderType) RenderAlphaScope.afterMaterialSetup();
}
@Inject(method = "clearRenderState", at = @At("RETURN"))
private void cobblemon$restoreScopedAlpha(CallbackInfo ci) {
if ((Object) this instanceof RenderType) RenderAlphaScope.afterMaterialClear();
}
}
@@ -0,0 +1,24 @@
/*
* 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);
}
}
@@ -9,6 +9,8 @@
package com.cobblemon.mod.common.mixin.entity;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.cinematic.runtime.CinematicViewerScopedEntity;
import com.cobblemon.mod.common.api.cinematic.runtime.pathfinding.NativeMotionEntity;
import com.cobblemon.mod.common.duck.RidePassenger;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.pokemon.Pokemon;
@@ -25,6 +27,8 @@ import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
@@ -35,11 +39,35 @@ 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.Set;
import java.util.UUID;
@Mixin(Entity.class)
public abstract class EntityMixin {
public abstract class EntityMixin implements CinematicViewerScopedEntity {
@Unique
@Nullable
private Set<UUID> cobblemon$sceneViewers = null;
@Nullable
@Override
public Set<UUID> getCobblemonSceneViewers() {
return cobblemon$sceneViewers;
}
@Override
public void setCobblemonSceneViewers(@Nullable Set<UUID> viewers) {
this.cobblemon$sceneViewers = viewers;
}
@Shadow @Nullable public abstract Entity getVehicle();
@WrapOperation(method = "move", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/Block;stepOn(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/entity/Entity;)V"))
private void cobblemon$skipSimulatedFootsteps(Block block, Level level, BlockPos pos, BlockState state,
Entity entity, Operation<Void> original) {
if (!(entity instanceof NativeMotionEntity)) original.call(block, level, pos, state, entity);
}
@Inject(method = "updateInWaterStateAndDoWaterCurrentPushing", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/Entity;wasTouchingWater:Z", ordinal = 1), cancellable = true)
public void cobblemon$verifyActuallyTouchingWater(CallbackInfo ci) {
if(this.getVehicle() instanceof PokemonEntity) {
@@ -8,12 +8,8 @@ object CobblemonBuildDetails {
const val VERSION = "{{ version }}"
const val SNAPSHOT = {{ isSnapshot }}
// Read at runtime rather than templated in, so a new commit doesn't invalidate compiled code.
private val git = java.util.Properties().apply {
CobblemonBuildDetails::class.java.getResourceAsStream("/cobblemon-git.properties")?.use(::load)
}
@JvmField val GIT_COMMIT: String = git.getProperty("commit", "Not Specified")
@JvmField val BRANCH: String = git.getProperty("branch", "Not Specified")
const val GIT_COMMIT = "{{ gitCommit | default("Not Specified") }}"
const val BRANCH = "{{ branch | default("Not Specified") }}"
fun smallCommitHash(): String {
return this.GIT_COMMIT.substring(0, 7)
@@ -8,6 +8,11 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.cinematic.registry.SceneBuiltinSources
import com.cobblemon.mod.common.net.serverhandling.editor.EditorPreviewSessionRegistry
import com.cobblemon.mod.common.api.cinematic.runtime.CinematicManager
import com.cobblemon.mod.common.api.molangui.MolangUiService
import com.cobblemon.mod.common.CobblemonBuildDetails.smallCommitHash
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.SeasonResolver
@@ -76,6 +81,8 @@ 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.battles.stadium.StadiumBattleRegistry
import com.cobblemon.mod.common.battles.stadium.StadiumSpectating
import com.cobblemon.mod.common.block.dispenser.DispenserBehaviorRegistry
import com.cobblemon.mod.common.client.CobblemonPack
import com.cobblemon.mod.common.command.argument.*
@@ -259,9 +266,9 @@ object Cobblemon {
playerDataManager.onPlayerDisconnect(it.player)
RequestManager.onLogoff(it.player)
serverPlayerStructs.remove(it.player.uuid)
}
PlatformEvents.PLAYER_RESPAWN.subscribe {
serverPlayerStructs.remove(it.player.uuid)
MolangUiService.closeAllForPlayer(it.player.uuid)
CinematicManager.onPlayerDisconnect(it.player.uuid)
EditorPreviewSessionRegistry.onPlayerDisconnect(it.player.uuid)
}
PlatformEvents.PLAYER_DEATH.subscribe {
PCLinkManager.removeLink(it.player.uuid)
@@ -304,6 +311,7 @@ object Cobblemon {
HeldItemProvider.register(CobblemonHeldItemManager, Priority.LOWEST)
}
fun sendServerSettingsPacketToPlayer(player: ServerPlayer) {
ServerSettingsPacket(
this.config.preventCompletePartyDeposit,
@@ -356,6 +364,8 @@ object Cobblemon {
CustomPokemonProperty.register(NoAIProperty)
CustomPokemonProperty.register(HeldItemVisibleProperty)
SceneBuiltinSources.register()
CobblemonEvents.POKEMON_PROPERTY_INITIALISED.emit(Unit)
CallbackHandler.setup()
@@ -452,7 +462,14 @@ object Cobblemon {
)
}
PlatformEvents.SERVER_STOPPING.subscribe {
StadiumSpectating.clear()
// Release temporary actor AI/gravity settings before the final world save.
CinematicManager.clear()
}
PlatformEvents.SERVER_STOPPED.subscribe {
StadiumBattleRegistry.clearDynamic()
MolangUiService.closeAll()
storage.unregisterAll(it.server.registryAccess())
playerDataManager.saveAllStores()
playerDataManager.saveExecutor.shutdown()
@@ -111,19 +111,8 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
)
@JvmField
val TERRAIN_PLANT: BlockEntityType<TerrainPlantBlockEntity> = this.create(
"terrain_plant",
BlockEntityType.Builder.of(::TerrainPlantBlockEntity,
CobblemonBlocks.VOLTACONIA,
CobblemonBlocks.LUCIBELL,
CobblemonBlocks.VERDROOT
).build(null)
)
@JvmField
val MISTLEAF_FLOWER: BlockEntityType<MistleafFlowerBlockEntity> = this.create(
"mistleaf",
BlockEntityType.Builder.of(::MistleafFlowerBlockEntity,
CobblemonBlocks.MISTLEAF
).build(null)
val STADIUM_BLOCK: BlockEntityType<StadiumBlockEntity> = this.create(
"stadium_block",
BlockEntityType.Builder.of(::StadiumBlockEntity, CobblemonBlocks.STADIUM_BLOCK).build(null)
)
}
@@ -16,7 +16,6 @@ import com.cobblemon.mod.common.block.MintBlock.MintType
import com.cobblemon.mod.common.block.campfirepot.CampfireBlock
import com.cobblemon.mod.common.block.campfirepot.CampfirePotBlock
import com.cobblemon.mod.common.block.chest.GildedChestBlock
import com.cobblemon.mod.common.block.decor.*
import com.cobblemon.mod.common.block.general.BaleBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
@@ -27,7 +26,6 @@ import com.cobblemon.mod.common.block.sign.CobblemonSignBlock
import com.cobblemon.mod.common.block.sign.CobblemonWallHangingSignBlock
import com.cobblemon.mod.common.block.sign.CobblemonWallSignBlock
import com.cobblemon.mod.common.block.tmmachine.TMMachineBlock
import com.cobblemon.mod.common.block.general.MintLitterBlock
import com.cobblemon.mod.common.mixin.invoker.BlocksInvoker
import com.cobblemon.mod.common.mixin.invoker.DoorBlockInvoker
import com.cobblemon.mod.common.mixin.invoker.FireBlockInvoker
@@ -623,131 +621,37 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val WEAKNESS_POLICY = create("weakness_policy", WallAttachedDirectionalShapeBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_PAPER_LARGE_SOUNDS).mapColor(MapColor.NONE).noOcclusion().noCollission(), 14, 12, 1))
@JvmField
val RING_TARGET = create("ring_target", RingTargetBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).mapColor(MapColor.NONE).noOcclusion().strength(0.125f)))
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val MAGNET = create("magnet", MagnetBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion().strength(0.075f)))
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val GALARICA_WREATH = create("galarica_wreath", GalaricaWreathBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noOcclusion().noCollission()))
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val FLAME_ORB = create("flame_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{15}))
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val TOXIC_ORB = create("toxic_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val LIFE_ORB = create("life_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{7}))
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val LIGHT_BALL = create("light_ball", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{13}))
@JvmField
val SMOKE_BALL = create("smoke_ball", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val KINGS_ROCK = create("kings_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.CENTERED_6x))
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val GALARICA_CUFF = create("galarica_cuff", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noCollission().noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_10x4))
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val UNREMARKABLE_TEACUP = create("unremarkable_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val MASTERPIECE_TEACUP = create("masterpiece_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
@JvmField
val CHIPPED_POT = create("chipped_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
@JvmField
val CRACKED_POT = create("cracked_pot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEAPOT))
@JvmField
val ELECTIRIZER = create("electirizer", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.IZER))
@JvmField
val MAGMARIZER = create("magmarizer", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.IZER))
@JvmField
val SACHET = create("sachet", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val METAL_POWDER = create("metal_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val QUICK_POWDER = create("quick_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val BRIGHT_POWDER = create("bright_powder", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion().lightLevel{3}, DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val SOFT_SAND = create("soft_sand", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_POWDER_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.POUCH))
@JvmField
val NEVER_MELT_ICE = create("never_melt_ice", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().friction(0.989F), DecorativeItemBlock.DecorativeItemBlockType.NEVERMELT))
@JvmField
val SCOPE_LENS = create("scope_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.SCOPE))
@JvmField
val WIDE_LENS = create("wide_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.WIDE))
@JvmField
val ZOOM_LENS = create("zoom_lens", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.COPPER).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.ZOOM))
@JvmField
val ICY_ROCK = create("icy_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
@JvmField
val HEAT_ROCK = create("heat_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
@JvmField
val SMOOTH_ROCK = create("smooth_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x4))
@JvmField
val DAMP_ROCK = create("damp_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DEEPSLATE).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_8x6))
@JvmField
val DESTINY_KNOT = create("destiny_knot", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOL).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.KNOT))
@JvmField
val LUMINOUS_MOSS = create("luminous_moss", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.MOSS).mapColor(MapColor.NONE).noOcclusion().lightLevel{5}, DecorativeItemBlock.DecorativeItemBlockType.CENTERED_8x))
@JvmField
val METRONOME = create("metronome", ActivatableDecorationBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion(), ActivatableDecorationBlock.ActivatableDecorationBlockType.METRONOME))
@JvmField
val EJECT_BUTTON = create("eject_button", EjectButtonBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).pushReaction(PushReaction.DESTROY).noOcclusion().strength(0.125f), 40))
@JvmField
val LOADED_DICE = create("loaded_dice", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_DICE_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.DICE))
@JvmField
val IRON_BALL = create("iron_ball", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.IRON_BALL))
@JvmField
val METAL_COAT = create("metal_coat", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.METAL_COAT))
@JvmField
val UPGRADE = create("upgrade", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.UPGRADE))
@JvmField
val DUBIOUS_DISC = create("dubious_disc", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.UPGRADE))
@JvmField
val THROAT_SPRAY = create("throat_spray", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.THROAT_SPRAY))
@JvmField
val TERRAIN_EXTENDER = create("terrain_extender", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_METAL_CAN_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.EXTENDER))
@JvmField
val MOOMOO_MILK = create("moomoo_milk", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_JAR_GLASS_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.GLASS_BOTTLE))
@JvmField
val MEDICINAL_BREW = create("medicinal_brew", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_JAR_GLASS_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.GLASS_BOTTLE))
@JvmField
val FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val AWAKENING = create("awakening", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val BURN_HEAL = create("burn_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ICE_HEAL = create("ice_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val PARALYZE_HEAL = create("paralyze_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
@JvmField
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
@JvmField
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
@JvmField
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
@JvmField
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
@JvmField
val DIRE_HIT = create("dire_hit", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@@ -766,23 +670,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val X_SPEED = create("x_${Stats.SPEED.identifier.path}", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@JvmField
val HP_UP = create("hp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val PROTEIN = create("protein", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val IRON = create("iron", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val CARBOS = create("carbos", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val CALCIUM = create("calcium", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val ZINC = create("zinc", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val PP_UP = create("pp_up", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val PP_MAX = create("pp_max", StackableItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.ITEM_BLOCK_BOTTLE_PILLS_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.VITAMIN))
@JvmField
val MONITOR = create("monitor", MonitorBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).lightLevel { if (it.getValue(MonitorBlock.SCREEN) != MonitorBlock.MonitorScreen.OFF) 13 else 0 }))
@JvmField
@@ -796,9 +683,11 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
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 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 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(
@@ -827,7 +716,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val PASTURE = create("pasture", PastureBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BROWN).sound(SoundType.WOOD).strength(2F).noOcclusion().pushReaction(PushReaction.BLOCK).lightLevel { if ((it.getValue(PastureBlock.ON) as Boolean) && (it.getValue(PastureBlock.PART) == PastureBlock.PasturePart.TOP)) 13 else 0 }))
@JvmField
val RED_MINT = create("red_mint", MintBlock(MintType.RED, BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_RED).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MINT_SOUNDS).pushReaction(PushReaction.DESTROY)))
@JvmField
val BLUE_MINT = create("blue_mint", MintBlock(MintType.BLUE, BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BLUE).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MINT_SOUNDS).pushReaction(PushReaction.DESTROY)))
@@ -845,68 +734,29 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val PEP_UP_FLOWER = this.create("pep_up_flower", FlowerBlock(MobEffects.LEVITATION, 10F, BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().instabreak().sound(SoundType.GRASS).offsetType(BlockBehaviour.OffsetType.XZ).pushReaction(PushReaction.DESTROY)))
@JvmField
val POTTED_PEP_UP_FLOWER = this.create("potted_pep_up_flower", BlocksInvoker.createFlowerPotBlock(PEP_UP_FLOWER))
@JvmField
val HEARTY_GRAINS = this.create("hearty_grains", HeartyGrainsBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.PLANT).noCollission().randomTicks().instabreak()))
@JvmField
val GALARICA_NUT_BUSH = this.create("galarica_nut_bush", NutBushBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).randomTicks().noCollission().sound(SoundType.SWEET_BERRY_BUSH).pushReaction(PushReaction.DESTROY)))
@JvmField
val RED_MINT_LEAF_LITTER = this.mintLitter("red_mint_leaf_litter", MapColor.COLOR_RED)
val POTTED_RED_APRICORN_SAPLING = this.create("potted_red_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(RED_APRICORN_SAPLING))
@JvmField
val POTTED_RED_MINT = this.flowerPot("potted_red_mint", RED_MINT_LEAF_LITTER)
val POTTED_YELLOW_APRICORN_SAPLING = this.create("potted_yellow_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(YELLOW_APRICORN_SAPLING))
@JvmField
val BLUE_MINT_LEAF_LITTER = this.mintLitter("blue_mint_leaf_litter", MapColor.COLOR_BLUE)
val POTTED_GREEN_APRICORN_SAPLING = this.create("potted_green_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(GREEN_APRICORN_SAPLING))
@JvmField
val POTTED_BLUE_MINT = this.flowerPot("potted_blue_mint", BLUE_MINT_LEAF_LITTER)
val POTTED_BLUE_APRICORN_SAPLING = this.create("potted_blue_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(BLUE_APRICORN_SAPLING))
@JvmField
val CYAN_MINT_LEAF_LITTER = this.mintLitter("cyan_mint_leaf_litter", MapColor.COLOR_CYAN)
val POTTED_PINK_APRICORN_SAPLING = this.create("potted_pink_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(PINK_APRICORN_SAPLING))
@JvmField
val POTTED_CYAN_MINT = this.flowerPot("potted_cyan_mint", CYAN_MINT_LEAF_LITTER)
val POTTED_BLACK_APRICORN_SAPLING = this.create("potted_black_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(BLACK_APRICORN_SAPLING))
@JvmField
val PINK_MINT_LEAF_LITTER = this.mintLitter("pink_mint_leaf_litter", MapColor.COLOR_PINK)
val POTTED_WHITE_APRICORN_SAPLING = this.create("potted_white_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(WHITE_APRICORN_SAPLING))
@JvmField
val POTTED_PINK_MINT = this.flowerPot("potted_pink_mint", PINK_MINT_LEAF_LITTER)
@JvmField
val GREEN_MINT_LEAF_LITTER = this.mintLitter("green_mint_leaf_litter", MapColor.COLOR_GREEN)
@JvmField
val POTTED_GREEN_MINT = this.flowerPot("potted_green_mint", GREEN_MINT_LEAF_LITTER)
@JvmField
val WHITE_MINT_LEAF_LITTER = this.mintLitter("white_mint_leaf_litter", MapColor.SNOW)
@JvmField
val POTTED_WHITE_MINT = this.flowerPot("potted_white_mint", WHITE_MINT_LEAF_LITTER)
@JvmField
val LOBAILA = this.create("lobaila", LobailaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().instabreak().sound(SoundType.GRASS).offsetType(BlockBehaviour.OffsetType.XZ).ignitedByLava().pushReaction(PushReaction.DESTROY)))
@JvmField
val POMOHANA = this.create("pomohana", PomohanaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().instabreak().sound(SoundType.GRASS).ignitedByLava().pushReaction(PushReaction.DESTROY)))
@JvmField
val PAUVILLEA = this.create("pauvillea",PauvilleaFlowerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY)))
@JvmField
val WISTENSU = this.create("wistensu", WistensuFlowerHeadBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY).lightLevel{11}))
@JvmField
val WISTENSU_VINE = this.create("wistensu_vine", WistensuFlowerVineBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.GRASS).instabreak().ignitedByLava().pushReaction(PushReaction.DESTROY)))
@JvmField
val ELECTRIC_SEED = this.terrainSeed("electric_seed", TerrainSeedBlock.TerrainSeedType.ELECTRIC)
@JvmField
val PSYCHIC_SEED = this.terrainSeed("psychic_seed", TerrainSeedBlock.TerrainSeedType.PSYCHIC)
@JvmField
val MISTY_SEED = this.terrainSeed("misty_seed", TerrainSeedBlock.TerrainSeedType.MISTY)
@JvmField
val GRASSY_SEED = this.terrainSeed("grassy_seed", TerrainSeedBlock.TerrainSeedType.GRASSY)
@JvmField
val VOLTACONIA = this.terrainPlant("voltaconia") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 8 else 0 }
@JvmField
val VERDROOT = this.terrainPlant("verdroot") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 5 else 0 }
@JvmField
val LUCIBELL = this.terrainPlant("lucibell") { state -> if (state.getValue(TerrainPlantBlock.GLOWING)) 8 else 0 }
@JvmField
val MISTLEAF = this.create(
"mistleaf",
MistleafFlowerBlock {
if ((it.getValue(MistleafFlowerBlock.GLOWING) >= 1)) it.getValue(MistleafFlowerBlock.GLOWING) + 2 else 0
}
)
val POTTED_SACCHARINE_SAPLING = this.create("potted_saccharine_sapling", BlocksInvoker.createFlowerPotBlock(SACCHARINE_SAPLING))
@JvmField
val HEARTY_GRAIN_BALE = this.create("hearty_grain_bale", BaleBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).instrument(NoteBlockInstrument.BANJO).strength(0.5F).sound(CobblemonSounds.HEARTY_GRAIN_BALE_SOUNDS)))
@@ -1084,165 +934,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val YACHE_BERRY = this.berryBlock("yache")
//val BINDING_SOIL = this.create("binding_soil", BindingSoilBlock(BlockBehaviour.Properties.of()))
@JvmField
val POTTED_AGUAV_BERRY = this.flowerPot("potted_aguav_berry", AGUAV_BERRY)
@JvmField
val POTTED_APICOT_BERRY = this.flowerPot("potted_apicot_berry", APICOT_BERRY)
@JvmField
val POTTED_ASPEAR_BERRY = this.flowerPot("potted_aspear_berry", ASPEAR_BERRY)
@JvmField
val POTTED_BABIRI_BERRY = this.flowerPot("potted_babiri_berry", BABIRI_BERRY)
@JvmField
val POTTED_BELUE_BERRY = this.flowerPot("potted_belue_berry", BELUE_BERRY)
@JvmField
val POTTED_BLUK_BERRY = this.flowerPot("potted_bluk_berry", BLUK_BERRY)
@JvmField
val POTTED_CHARTI_BERRY = this.flowerPot("potted_charti_berry", CHARTI_BERRY)
@JvmField
val POTTED_CHERI_BERRY = this.flowerPot("potted_cheri_berry", CHERI_BERRY)
@JvmField
val POTTED_CHESTO_BERRY = this.flowerPot("potted_chesto_berry", CHESTO_BERRY)
@JvmField
val POTTED_CHILAN_BERRY = this.flowerPot("potted_chilan_berry", CHILAN_BERRY)
@JvmField
val POTTED_CHOPLE_BERRY = this.flowerPot("potted_chople_berry", CHOPLE_BERRY)
@JvmField
val POTTED_COBA_BERRY = this.flowerPot("potted_coba_berry", COBA_BERRY)
@JvmField
val POTTED_COLBUR_BERRY = this.flowerPot("potted_colbur_berry", COLBUR_BERRY)
@JvmField
val POTTED_CORNN_BERRY = this.flowerPot("potted_cornn_berry", CORNN_BERRY)
@JvmField
val POTTED_CUSTAP_BERRY = this.flowerPot("potted_custap_berry", CUSTAP_BERRY)
@JvmField
val POTTED_DURIN_BERRY = this.flowerPot("potted_durin_berry", DURIN_BERRY)
@JvmField
val POTTED_EGGANT_BERRY = this.flowerPot("potted_eggant_berry", EGGANT_BERRY)
@JvmField
val POTTED_ENIGMA_BERRY = this.flowerPot("potted_enigma_berry", ENIGMA_BERRY)
@JvmField
val POTTED_FIGY_BERRY = this.flowerPot("potted_figy_berry", FIGY_BERRY)
@JvmField
val POTTED_GANLON_BERRY = this.flowerPot("potted_ganlon_berry", GANLON_BERRY)
@JvmField
val POTTED_GREPA_BERRY = this.flowerPot("potted_grepa_berry", GREPA_BERRY)
@JvmField
val POTTED_HABAN_BERRY = this.flowerPot("potted_haban_berry", HABAN_BERRY)
@JvmField
val POTTED_HONDEW_BERRY = this.flowerPot("potted_hondew_berry", HONDEW_BERRY)
@JvmField
val POTTED_HOPO_BERRY = this.flowerPot("potted_hopo_berry", HOPO_BERRY)
@JvmField
val POTTED_IAPAPA_BERRY = this.flowerPot("potted_iapapa_berry", IAPAPA_BERRY)
@JvmField
val POTTED_JABOCA_BERRY = this.flowerPot("potted_jaboca_berry", JABOCA_BERRY)
@JvmField
val POTTED_KASIB_BERRY = this.flowerPot("potted_kasib_berry", KASIB_BERRY)
@JvmField
val POTTED_KEBIA_BERRY = this.flowerPot("potted_kebia_berry", KEBIA_BERRY)
@JvmField
val POTTED_KEE_BERRY = this.flowerPot("potted_kee_berry", KEE_BERRY)
@JvmField
val POTTED_KELPSY_BERRY = this.flowerPot("potted_kelpsy_berry", KELPSY_BERRY)
@JvmField
val POTTED_LANSAT_BERRY = this.flowerPot("potted_lansat_berry", LANSAT_BERRY)
@JvmField
val POTTED_LEPPA_BERRY = this.flowerPot("potted_leppa_berry", LEPPA_BERRY)
@JvmField
val POTTED_LIECHI_BERRY = this.flowerPot("potted_liechi_berry", LIECHI_BERRY)
@JvmField
val POTTED_LUM_BERRY = this.flowerPot("potted_lum_berry", LUM_BERRY)
@JvmField
val POTTED_MAGO_BERRY = this.flowerPot("potted_mago_berry", MAGO_BERRY)
@JvmField
val POTTED_MAGOST_BERRY = this.flowerPot("potted_magost_berry", MAGOST_BERRY)
@JvmField
val POTTED_MARANGA_BERRY = this.flowerPot("potted_maranga_berry", MARANGA_BERRY)
@JvmField
val POTTED_MICLE_BERRY = this.flowerPot("potted_micle_berry", MICLE_BERRY)
@JvmField
val POTTED_NANAB_BERRY = this.flowerPot("potted_nanab_berry", NANAB_BERRY)
@JvmField
val POTTED_NOMEL_BERRY = this.flowerPot("potted_nomel_berry", NOMEL_BERRY)
@JvmField
val POTTED_OCCA_BERRY = this.flowerPot("potted_occa_berry", OCCA_BERRY)
@JvmField
val POTTED_ORAN_BERRY = this.flowerPot("potted_oran_berry", ORAN_BERRY)
@JvmField
val POTTED_PAMTRE_BERRY = this.flowerPot("potted_pamtre_berry", PAMTRE_BERRY)
@JvmField
val POTTED_PASSHO_BERRY = this.flowerPot("potted_passho_berry", PASSHO_BERRY)
@JvmField
val POTTED_PAYAPA_BERRY = this.flowerPot("potted_payapa_berry", PAYAPA_BERRY)
@JvmField
val POTTED_PECHA_BERRY = this.flowerPot("potted_pecha_berry", PECHA_BERRY)
@JvmField
val POTTED_PERSIM_BERRY = this.flowerPot("potted_persim_berry", PERSIM_BERRY)
@JvmField
val POTTED_PETAYA_BERRY = this.flowerPot("potted_petaya_berry", PETAYA_BERRY)
@JvmField
val POTTED_PINAP_BERRY = this.flowerPot("potted_pinap_berry", PINAP_BERRY)
@JvmField
val POTTED_POMEG_BERRY = this.flowerPot("potted_pomeg_berry", POMEG_BERRY)
@JvmField
val POTTED_QUALOT_BERRY = this.flowerPot("potted_qualot_berry", QUALOT_BERRY)
@JvmField
val POTTED_RABUTA_BERRY = this.flowerPot("potted_rabuta_berry", RABUTA_BERRY)
@JvmField
val POTTED_RAWST_BERRY = this.flowerPot("potted_rawst_berry", RAWST_BERRY)
@JvmField
val POTTED_RAZZ_BERRY = this.flowerPot("potted_razz_berry", RAZZ_BERRY)
@JvmField
val POTTED_RINDO_BERRY = this.flowerPot("potted_rindo_berry", RINDO_BERRY)
@JvmField
val POTTED_ROSELI_BERRY = this.flowerPot("potted_roseli_berry", ROSELI_BERRY)
@JvmField
val POTTED_ROWAP_BERRY = this.flowerPot("potted_rowap_berry", ROWAP_BERRY)
@JvmField
val POTTED_SALAC_BERRY = this.flowerPot("potted_salac_berry", SALAC_BERRY)
@JvmField
val POTTED_SHUCA_BERRY = this.flowerPot("potted_shuca_berry", SHUCA_BERRY)
@JvmField
val POTTED_SITRUS_BERRY = this.flowerPot("potted_sitrus_berry", SITRUS_BERRY)
@JvmField
val POTTED_SPELON_BERRY = this.flowerPot("potted_spelon_berry", SPELON_BERRY)
@JvmField
val POTTED_STARF_BERRY = this.flowerPot("potted_starf_berry", STARF_BERRY)
@JvmField
val POTTED_TAMATO_BERRY = this.flowerPot("potted_tamato_berry", TAMATO_BERRY)
@JvmField
val POTTED_TANGA_BERRY = this.flowerPot("potted_tanga_berry", TANGA_BERRY)
@JvmField
val POTTED_TOUGA_BERRY = this.flowerPot("potted_touga_berry", TOUGA_BERRY)
@JvmField
val POTTED_WACAN_BERRY = this.flowerPot("potted_wacan_berry", WACAN_BERRY)
@JvmField
val POTTED_WATMEL_BERRY = this.flowerPot("potted_watmel_berry", WATMEL_BERRY)
@JvmField
val POTTED_WEPEAR_BERRY = this.flowerPot("potted_wepear_berry", WEPEAR_BERRY)
@JvmField
val POTTED_WIKI_BERRY = this.flowerPot("potted_wiki_berry", WIKI_BERRY)
@JvmField
val POTTED_YACHE_BERRY = this.flowerPot("potted_yache_berry", YACHE_BERRY)
@JvmField
val POTTED_PEP_UP_FLOWER = this.flowerPot("potted_pep_up_flower", PEP_UP_FLOWER)
@JvmField
val POTTED_RED_APRICORN_SAPLING = this.flowerPot("potted_red_apricorn_sapling", RED_APRICORN_SAPLING)
@JvmField
val POTTED_YELLOW_APRICORN_SAPLING = this.flowerPot("potted_yellow_apricorn_sapling", YELLOW_APRICORN_SAPLING)
@JvmField
val POTTED_GREEN_APRICORN_SAPLING = this.flowerPot("potted_green_apricorn_sapling", GREEN_APRICORN_SAPLING)
@JvmField
val POTTED_BLUE_APRICORN_SAPLING = this.flowerPot("potted_blue_apricorn_sapling", BLUE_APRICORN_SAPLING)
@JvmField
val POTTED_PINK_APRICORN_SAPLING = this.flowerPot("potted_pink_apricorn_sapling", PINK_APRICORN_SAPLING)
@JvmField
val POTTED_BLACK_APRICORN_SAPLING = this.flowerPot("potted_black_apricorn_sapling", BLACK_APRICORN_SAPLING)
@JvmField
val POTTED_WHITE_APRICORN_SAPLING = this.flowerPot("potted_white_apricorn_sapling", WHITE_APRICORN_SAPLING)
@JvmField
val POTTED_SACCHARINE_SAPLING = this.flowerPot("potted_saccharine_sapling", SACCHARINE_SAPLING)
init {
/**
* Makes all blocks in array flammable by adding them to FireBlock's flammableRegistry.
@@ -1344,57 +1035,4 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
}
fun typeGemClusters() = typeGemClusters.toMap()
private fun flowerPot(name: String, flower: Block) = this.create(
name,
FlowerPotBlock(
flower,
BlockBehaviour.Properties.of()
.instabreak()
.noOcclusion()
.pushReaction(PushReaction.DESTROY)
)
)
private fun mintLitter(name: String, color: MapColor) = this.create(
name,
MintLitterBlock(
BlockBehaviour.Properties.of()
.sound(CobblemonSounds.MINT_SOUNDS)
.mapColor(color)
.noOcclusion()
.noCollission()
.pushReaction(PushReaction.DESTROY)
)
)
private fun terrainSeed(name: String, type: TerrainSeedBlock.TerrainSeedType) = this.create(
name,
TerrainSeedBlock(
type,
BlockBehaviour.Properties.of()
.pushReaction(PushReaction.DESTROY)
.ignitedByLava()
.mapColor(MapColor.PLANT)
.offsetType(BlockBehaviour.OffsetType.XZ)
.noCollission()
.randomTicks()
.instabreak()
)
)
private fun terrainPlant(name: String, lightLevel: (BlockState) -> Int) = this.create(
name,
TerrainPlantBlock(
BlockBehaviour.Properties.of()
.mapColor(MapColor.PLANT)
.noCollission()
.instabreak()
.sound(SoundType.GRASS)
.offsetType(BlockBehaviour.OffsetType.XZ)
.ignitedByLava()
.pushReaction(PushReaction.DESTROY)
.lightLevel(lightLevel)
)
)
}
@@ -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)
@@ -61,7 +65,6 @@ object CobblemonCommands {
ChangeBoxWallpaperCommand.register(dispatcher)
RunMolangCommand.register(dispatcher)
RunMolangScriptCommand.register(dispatcher)
SpectateBattleCommand.register(dispatcher)
CobblemonConfigCommand.register(dispatcher)
UnlockPCBoxWallpaperCommand.register(dispatcher)
BehaviourEditCommand.register(dispatcher)
@@ -77,5 +80,8 @@ object CobblemonCommands {
// 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)
CinematicCommand.register(dispatcher)
StadiumBattleCommand.register(dispatcher)
MolangUiCommand.register(dispatcher)
}
}
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.entity.boat.CobblemonBoatEntity
import com.cobblemon.mod.common.entity.boat.CobblemonChestBoatEntity
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.entity.generic.GenericBedrockEntity
import com.cobblemon.mod.common.entity.stadium.StadiumCameraDroneEntity
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -81,6 +82,12 @@ object CobblemonEntities : PlatformRegistry<Registry<EntityType<*>>, ResourceKey
@JvmField
val GENERIC_BEDROCK_ENTITY_KEY = cobblemonResource("generic_bedrock")
@JvmField
val STADIUM_CAMERA_DRONE: EntityType<StadiumCameraDroneEntity> = this.create(
"stadium_camera_drone",
EntityType.Builder.of(::StadiumCameraDroneEntity, MobCategory.MISC)
.build(cobblemonResource("stadium_camera_drone").toString())
)
@JvmField
val GENERIC_BEDROCK_ENTITY: EntityType<GenericBedrockEntity> = this.create(
GENERIC_BEDROCK_ENTITY_KEY.path,
EntityType.Builder.of({ _, world -> GenericBedrockEntity(world) }, MobCategory.MISC)
@@ -99,4 +106,4 @@ object CobblemonEntities : PlatformRegistry<Registry<EntityType<*>>, ResourceKey
consumer(POKEMON, PokemonEntity.createAttributes())
consumer(NPC, NPCEntity.createAttributes())
}
}
}
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.api.net.NetworkPacket
import com.mojang.brigadier.arguments.ArgumentType
import kotlin.reflect.KClass
import net.minecraft.commands.synchronization.ArgumentTypeInfo
import net.minecraft.core.HolderLookup
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.MinecraftServer
@@ -169,16 +168,12 @@ interface CobblemonImplementation {
* TODO
*
* @param identifier
* @param reloaderFactory
* @param reloader
* @param type
* @param dependencies
*/
fun registerResourceReloader(
identifier: ResourceLocation,
type: PackType,
dependencies: Collection<ResourceLocation>,
reloaderFactory: (HolderLookup.Provider) -> PreparableReloadListener
)
fun registerResourceReloader(identifier: ResourceLocation, reloader: PreparableReloadListener, type: PackType, dependencies: Collection<ResourceLocation>)
/**
* TODO
*
@@ -38,6 +38,7 @@ import com.cobblemon.mod.common.item.berry.StatusCuringBerryItem
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 +75,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
@@ -465,6 +469,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
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
@@ -472,18 +478,18 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
// Evolution items
@JvmField val LINK_CABLE = create("link_cable", LinkCableItem())
@JvmField val DRAGON_SCALE = noSettingsItem("dragon_scale")
@JvmField val METAL_COAT = itemNameBlockItem("metal_coat", CobblemonBlocks.METAL_COAT)
@JvmField val UPGRADE = itemNameBlockItem("upgrade", CobblemonBlocks.UPGRADE)
@JvmField val DUBIOUS_DISC = itemNameBlockItem("dubious_disc", CobblemonBlocks.DUBIOUS_DISC)
@JvmField val METAL_COAT = noSettingsItem("metal_coat")
@JvmField val UPGRADE = noSettingsItem("upgrade")
@JvmField val DUBIOUS_DISC = noSettingsItem("dubious_disc")
@JvmField val DEEP_SEA_SCALE = noSettingsItem("deep_sea_scale")
@JvmField val DEEP_SEA_TOOTH = noSettingsItem("deep_sea_tooth")
@JvmField val ELECTIRIZER = itemNameBlockItem("electirizer", CobblemonBlocks.ELECTIRIZER)
@JvmField val MAGMARIZER = itemNameBlockItem("magmarizer", CobblemonBlocks.MAGMARIZER)
@JvmField val ELECTIRIZER = noSettingsItem("electirizer")
@JvmField val MAGMARIZER = noSettingsItem("magmarizer")
@JvmField val OVAL_STONE = noSettingsItem("oval_stone")
@JvmField val PROTECTOR = noSettingsItem("protector")
@JvmField val REAPER_CLOTH = noSettingsItem("reaper_cloth")
@JvmField val PRISM_SCALE = noSettingsItem("prism_scale")
@JvmField val SACHET = itemNameBlockItem("sachet", CobblemonBlocks.SACHET)
@JvmField val SACHET = noSettingsItem("sachet")
@JvmField val WHIPPED_DREAM = create("whipped_dream", foodItem(8, 0.3f))
@JvmField val STRAWBERRY_SWEET = create("strawberry_sweet", foodItem(6, 0.125f))
@JvmField val LOVE_SWEET = create("love_sweet", foodItem(6, 0.125f))
@@ -492,20 +498,22 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val FLOWER_SWEET = create("flower_sweet", foodItem(6, 0.125f))
@JvmField val STAR_SWEET = create("star_sweet", foodItem(6, 0.125f))
@JvmField val RIBBON_SWEET = create("ribbon_sweet", foodItem(6, 0.125f))
@JvmField val CHIPPED_POT = itemNameBlockItem("chipped_pot", CobblemonBlocks.CHIPPED_POT)
@JvmField val CRACKED_POT = itemNameBlockItem("cracked_pot", CobblemonBlocks.CRACKED_POT)
@JvmField val MASTERPIECE_TEACUP = itemNameBlockItem("masterpiece_teacup", CobblemonBlocks.MASTERPIECE_TEACUP)
@JvmField val UNREMARKABLE_TEACUP = itemNameBlockItem("unremarkable_teacup", CobblemonBlocks.UNREMARKABLE_TEACUP)
@JvmField val CHIPPED_POT = noSettingsItem("chipped_pot")
@JvmField val CRACKED_POT = noSettingsItem("cracked_pot")
@JvmField val MASTERPIECE_TEACUP = noSettingsItem("masterpiece_teacup")
@JvmField val UNREMARKABLE_TEACUP = noSettingsItem("unremarkable_teacup")
@JvmField val SWEET_APPLE = compostableItem("sweet_apple", foodItem(4, 0.3f), 0.65f)
@JvmField val TART_APPLE = compostableItem("tart_apple", foodItem(4, 0.3f), 0.65f)
@JvmField val SYRUPY_APPLE = compostableItem("syrupy_apple", foodItem(4, 0.3f), 0.65f)
@JvmField val GALARICA_CUFF = itemNameBlockItem("galarica_cuff", CobblemonBlocks.GALARICA_CUFF)
@JvmField val GALARICA_CUFF = noSettingsItem("galarica_cuff")
@JvmField val GALARICA_WREATH = noSettingsItem("galarica_wreath")
@JvmField val BLACK_AUGURITE = noSettingsItem("black_augurite")
@JvmField val PEAT_BLOCK = noSettingsItem("peat_block")
@JvmField val RAZOR_CLAW = noSettingsItem("razor_claw")
@JvmField val RAZOR_FANG = noSettingsItem("razor_fang")
@JvmField val AUSPICIOUS_ARMOR = heldItem("auspicious_armor")
@JvmField val MALICIOUS_ARMOR = heldItem("malicious_armor")
@JvmField val SHELL_HELMET = heldItem("shell_helmet")
@JvmField val METAL_ALLOY = noSettingsItem("metal_alloy")
@JvmField val SCROLL_OF_DARKNESS = noSettingsItem("scroll_of_darkness")
@JvmField val SCROLL_OF_WATERS = noSettingsItem("scroll_of_waters")
@@ -649,17 +657,17 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val EXPERIENCE_CANDY_XL = candyItem("exp_candy_xl") { _, _ -> CandyItem.DEFAULT_XL_CANDY_YIELD }
@JvmField
val CALCIUM = create("calcium", VitaminItem(Stats.SPECIAL_ATTACK, CobblemonBlocks.CALCIUM))
val CALCIUM = create("calcium", VitaminItem(Stats.SPECIAL_ATTACK))
@JvmField
val CARBOS = create("carbos", VitaminItem(Stats.SPEED, CobblemonBlocks.CARBOS))
val CARBOS = create("carbos", VitaminItem(Stats.SPEED))
@JvmField
val HP_UP = create("hp_up", VitaminItem(Stats.HP, CobblemonBlocks.HP_UP))
val HP_UP = create("hp_up", VitaminItem(Stats.HP))
@JvmField
val IRON = create("iron", VitaminItem(Stats.DEFENCE, CobblemonBlocks.IRON))
val IRON = create("iron", VitaminItem(Stats.DEFENCE))
@JvmField
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK, CobblemonBlocks.PROTEIN))
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK))
@JvmField
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE, CobblemonBlocks.ZINC))
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE))
@JvmField
val HEALTH_MOCHI = create("health_mochi", MochiItem(Stats.HP))
@JvmField
@@ -711,7 +719,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val PEP_UP_FLOWER = compostableBlockItem("pep_up_flower", CobblemonBlocks.PEP_UP_FLOWER)
@JvmField
val MEDICINAL_BREW = itemNameBlockItem("medicinal_brew", CobblemonBlocks.MEDICINAL_BREW)
val MEDICINAL_BREW = noSettingsItem("medicinal_brew")
@JvmField
val REMEDY = compostableItem("remedy", RemedyItem(RemedyItem.NORMAL), 0.65f)
@JvmField
@@ -719,7 +727,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SUPERB_REMEDY = compostableItem("superb_remedy", RemedyItem(RemedyItem.SUPERB), 1f)
@JvmField
val MOOMOO_MILK = create("moomoo_milk", MoomooMilk(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16)), CobblemonBlocks.MOOMOO_MILK))
val MOOMOO_MILK = create("moomoo_milk", MoomooMilk(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16))))
@JvmField
val POTION = create("potion", PotionItem(PotionType.POTION, CobblemonBlocks.POTION))
@@ -741,34 +749,34 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val MAX_REVIVE = create("max_revive", ReviveItem(max = true))
@JvmField
val PP_UP = create("pp_up", PPUpItem(1, CobblemonBlocks.PP_UP))
val PP_UP = create("pp_up", PPUpItem(1))
@JvmField
val PP_MAX = create("pp_max", PPUpItem(3, CobblemonBlocks.PP_MAX))
val PP_MAX = create("pp_max", PPUpItem(3))
@JvmField
val RED_MINT_SEEDS = mintSeed("red", MintType.RED.getCropBlock())
@JvmField
val RED_MINT_LEAF = compostableItemNameBlockItem("red_mint_leaf", CobblemonBlocks.RED_MINT_LEAF_LITTER, 0.50f)
val RED_MINT_LEAF = mintLeaf("red", MintLeafItem(MintType.RED))
@JvmField
val BLUE_MINT_SEEDS = mintSeed("blue", MintType.BLUE.getCropBlock())
@JvmField
val BLUE_MINT_LEAF = compostableItemNameBlockItem("blue_mint_leaf", CobblemonBlocks.BLUE_MINT_LEAF_LITTER, 0.50f)
val BLUE_MINT_LEAF = mintLeaf("blue", MintLeafItem(MintType.BLUE))
@JvmField
val CYAN_MINT_SEEDS = mintSeed("cyan", MintType.CYAN.getCropBlock())
@JvmField
val CYAN_MINT_LEAF = compostableItemNameBlockItem("cyan_mint_leaf", CobblemonBlocks.CYAN_MINT_LEAF_LITTER, 0.50f)
val CYAN_MINT_LEAF = mintLeaf("cyan", MintLeafItem(MintType.CYAN))
@JvmField
val PINK_MINT_SEEDS = mintSeed("pink", MintType.PINK.getCropBlock())
@JvmField
val PINK_MINT_LEAF = compostableItemNameBlockItem("pink_mint_leaf", CobblemonBlocks.PINK_MINT_LEAF_LITTER, 0.50f)
val PINK_MINT_LEAF = mintLeaf("pink", MintLeafItem(MintType.PINK))
@JvmField
val GREEN_MINT_SEEDS = mintSeed("green", MintType.GREEN.getCropBlock())
@JvmField
val GREEN_MINT_LEAF = compostableItemNameBlockItem("green_mint_leaf", CobblemonBlocks.GREEN_MINT_LEAF_LITTER, 0.50f)
val GREEN_MINT_LEAF = mintLeaf("green", MintLeafItem(MintType.GREEN))
@JvmField
val WHITE_MINT_SEEDS = mintSeed("white", MintType.WHITE.getCropBlock())
@JvmField
val WHITE_MINT_LEAF = compostableItemNameBlockItem("white_mint_leaf", CobblemonBlocks.WHITE_MINT_LEAF_LITTER, 0.50f)
val WHITE_MINT_LEAF = mintLeaf("white", MintLeafItem(MintType.WHITE))
val mints = mutableMapOf<String, MintItem>()
@@ -920,18 +928,11 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val CHOICE_SPECS = wearableItem("choice_specs")
@JvmField val EXP_SHARE = wearableItem("exp_share")
@JvmField val FOCUS_BAND = wearableItem("focus_band")
@JvmField val KINGS_ROCK = wearableBlockItem("kings_rock", CobblemonBlocks.KINGS_ROCK)
@JvmField val NEVER_MELT_ICE = wearableBlockItem("never_melt_ice", CobblemonBlocks.NEVER_MELT_ICE)
@JvmField val KINGS_ROCK = wearableItem("kings_rock")
@JvmField val MUSCLE_BAND = wearableItem("muscle_band")
@JvmField val ROCKY_HELMET = wearableItem("rocky_helmet")
@JvmField val SAFETY_GOGGLES = wearableItem("safety_goggles")
@JvmField val WISE_GLASSES = wearableItem("wise_glasses")
@JvmField val SHARP_BEAK = wearableItem("sharp_beak")
@JvmField val COVERT_CLOAK = wearableItem("covert_cloak")
@JvmField val SHELL_HELMET = wearableItem("shell_helmet")
@JvmField val GALARICA_WREATH = wearableBlockItem("galarica_wreath", CobblemonBlocks.GALARICA_WREATH)
@JvmField val CLEANSE_TAG = wearableBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
@JvmField val SPELL_TAG = wearableBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
// Held Items
@JvmField
@@ -959,17 +960,21 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val CHOICE_SCARF = heldItem("choice_scarf")
@JvmField
val CLEANSE_TAG = itemNameBlockItem("cleanse_tag", CobblemonBlocks.CLEANSE_TAG)
@JvmField
val CLEAR_AMULET = heldItem("clear_amulet")
@JvmField
val DESTINY_KNOT = itemNameBlockItem("destiny_knot", CobblemonBlocks.DESTINY_KNOT)
val COVERT_CLOAK = heldItem("covert_cloak")
@JvmField
val DESTINY_KNOT = heldItem("destiny_knot")
@JvmField
val DRAGON_FANG = heldItem("dragon_fang")
@JvmField
val EJECT_BUTTON = itemNameBlockItem("eject_button", CobblemonBlocks.EJECT_BUTTON)
val EJECT_BUTTON = heldItem("eject_button")
@JvmField
val EJECT_PACK = heldItem("eject_pack")
@JvmField
val ELECTRIC_SEED = itemNameBlockItem("electric_seed", CobblemonBlocks.ELECTRIC_SEED)
val ELECTRIC_SEED = heldItem("electric_seed")
@JvmField
val EVERSTONE = heldItem("everstone")
@JvmField
@@ -979,13 +984,13 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val FAIRY_FEATHER = heldItem("fairy_feather")
@JvmField
val FLAME_ORB = itemNameBlockItem("flame_orb", CobblemonBlocks.FLAME_ORB)
val FLAME_ORB = heldItem("flame_orb")
@JvmField
val FLOAT_STONE = heldItem("float_stone")
@JvmField
val FOCUS_SASH = heldItem("focus_sash")
@JvmField
val GRASSY_SEED = itemNameBlockItem("grassy_seed", CobblemonBlocks.GRASSY_SEED)
val GRASSY_SEED = heldItem("grassy_seed")
@JvmField
val GRIP_CLAW = heldItem("grip_claw")
@JvmField
@@ -993,32 +998,34 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val HEAVY_DUTY_BOOTS = heldItem("heavy_duty_boots")
@JvmField
val IRON_BALL = itemNameBlockItem("iron_ball", CobblemonBlocks.IRON_BALL)
val IRON_BALL = heldItem("iron_ball")
@JvmField
val LAGGING_TAIL = heldItem("lagging_tail")
@JvmField
val LEFTOVERS = compostableHeldItem("leftovers", null, 0.50f)
@JvmField
val LIFE_ORB = itemNameBlockItem("life_orb", CobblemonBlocks.LIFE_ORB)
val LIFE_ORB = heldItem("life_orb")
@JvmField
val LIGHT_BALL = itemNameBlockItem("light_ball", CobblemonBlocks.LIGHT_BALL)
val LIGHT_BALL = heldItem("light_ball")
@JvmField
val LIGHT_CLAY = heldItem("light_clay")
@JvmField
val LOADED_DICE = itemNameBlockItem("loaded_dice", CobblemonBlocks.LOADED_DICE)
val LOADED_DICE = heldItem("loaded_dice")
@JvmField
val LUCKY_EGG = heldItem("lucky_egg")
@JvmField
val LUMINOUS_MOSS = itemNameBlockItem("luminous_moss", CobblemonBlocks.LUMINOUS_MOSS)
val LUMINOUS_MOSS = heldItem("luminous_moss")
@JvmField
val MAGNET = itemNameBlockItem("magnet", CobblemonBlocks.MAGNET)
val MAGNET = heldItem("magnet")
@JvmField
val METRONOME = itemNameBlockItem("metronome", CobblemonBlocks.METRONOME)
val METRONOME = heldItem("metronome")
@JvmField
val MIRACLE_SEED = heldItem("miracle_seed")
@JvmField
val MYSTIC_WATER = heldItem("mystic_water")
@JvmField
val NEVER_MELT_ICE = heldItem("never_melt_ice")
@JvmField
val POISON_BARB = heldItem("poison_barb")
@JvmField
val POWER_ANKLET = heldItem("power_anklet")
@@ -1033,7 +1040,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val POWER_WEIGHT = heldItem("power_weight")
@JvmField
val PSYCHIC_SEED = itemNameBlockItem("psychic_seed", CobblemonBlocks.PSYCHIC_SEED)
val PSYCHIC_SEED = heldItem("psychic_seed")
@JvmField
val PROTECTIVE_PADS = heldItem("protective_pads")
@JvmField
@@ -1043,11 +1050,13 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val RED_CARD = heldItem("red_card")
@JvmField
val RING_TARGET = itemNameBlockItem("ring_target", CobblemonBlocks.RING_TARGET)
val RING_TARGET = heldItem("ring_target")
@JvmField
val ROOM_SERVICE = heldItem("room_service")
@JvmField
val SCOPE_LENS = itemNameBlockItem("scope_lens", CobblemonBlocks.SCOPE_LENS)
val SCOPE_LENS = heldItem("scope_lens")
@JvmField
val SHARP_BEAK = heldItem("sharp_beak")
@JvmField
val SHED_SHELL = heldItem("shed_shell")
@JvmField
@@ -1057,19 +1066,21 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SILVER_POWDER = heldItem("silver_powder")
@JvmField
val SOFT_SAND = itemNameBlockItem("soft_sand", CobblemonBlocks.SOFT_SAND)
val SOFT_SAND = heldItem("soft_sand")
@JvmField
val SMOKE_BALL = itemNameBlockItem("smoke_ball", CobblemonBlocks.SMOKE_BALL)
val SPELL_TAG = itemNameBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
@JvmField
val SMOKE_BALL = heldItem("smoke_ball")
@JvmField
val SOOTHE_BELL = heldItem("soothe_bell")
@JvmField
val STICKY_BARB = heldItem("sticky_barb")
@JvmField
val TERRAIN_EXTENDER = itemNameBlockItem("terrain_extender", CobblemonBlocks.TERRAIN_EXTENDER)
val TERRAIN_EXTENDER = heldItem("terrain_extender")
@JvmField
val THROAT_SPRAY = itemNameBlockItem("throat_spray", CobblemonBlocks.THROAT_SPRAY)
val THROAT_SPRAY = heldItem("throat_spray")
@JvmField
val TOXIC_ORB = itemNameBlockItem("toxic_orb", CobblemonBlocks.TOXIC_ORB)
val TOXIC_ORB = heldItem("toxic_orb")
@JvmField
val TWISTED_SPOON = heldItem("twisted_spoon")
@JvmField
@@ -1077,33 +1088,33 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val WEAKNESS_POLICY = itemNameBlockItem("weakness_policy", CobblemonBlocks.WEAKNESS_POLICY)
@JvmField
val WIDE_LENS = itemNameBlockItem("wide_lens", CobblemonBlocks.WIDE_LENS)
val WIDE_LENS = heldItem("wide_lens")
@JvmField
val ZOOM_LENS = itemNameBlockItem("zoom_lens", CobblemonBlocks.ZOOM_LENS)
val ZOOM_LENS = heldItem("zoom_lens")
@JvmField
val MENTAL_HERB = compostableHeldItem("mental_herb", null, 0.85F)
@JvmField
val MIRROR_HERB = compostableHeldItem("mirror_herb", null, 0.85F)
@JvmField
val MISTY_SEED = itemNameBlockItem("misty_seed", CobblemonBlocks.MISTY_SEED)
val MISTY_SEED = heldItem("misty_seed")
@JvmField
val POWER_HERB = compostableHeldItem("power_herb", null, 0.85F)
@JvmField
val WHITE_HERB = compostableHeldItem("white_herb", null, 0.85F)
@JvmField
val BRIGHT_POWDER = glowItem("bright_powder", CobblemonBlocks.BRIGHT_POWDER)
val BRIGHT_POWDER = heldItem("bright_powder")
@JvmField
val METAL_POWDER = itemNameBlockItem("metal_powder", CobblemonBlocks.METAL_POWDER)
val METAL_POWDER = heldItem("metal_powder")
@JvmField
val QUICK_POWDER = itemNameBlockItem("quick_powder", CobblemonBlocks.QUICK_POWDER)
val QUICK_POWDER = heldItem("quick_powder")
@JvmField
val DAMP_ROCK = itemNameBlockItem("damp_rock", CobblemonBlocks.DAMP_ROCK)
val DAMP_ROCK = heldItem("damp_rock")
@JvmField
val HEAT_ROCK = itemNameBlockItem("heat_rock", CobblemonBlocks.HEAT_ROCK)
val HEAT_ROCK = heldItem("heat_rock")
@JvmField
val SMOOTH_ROCK = itemNameBlockItem("smooth_rock", CobblemonBlocks.SMOOTH_ROCK)
val SMOOTH_ROCK = heldItem("smooth_rock")
@JvmField
val ICY_ROCK = itemNameBlockItem("icy_rock", CobblemonBlocks.ICY_ROCK)
val ICY_ROCK = heldItem("icy_rock")
// Mulch
@JvmField
@@ -1558,25 +1569,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
//@JvmField
//val BINDING_SOIL = blockItem("binding_soil", CobblemonBlocks.BINDING_SOIL)
// Plants, Flora, and Flowers
@JvmField
val LOBAILA = compostableBlockItem("lobaila", CobblemonBlocks.LOBAILA, 0.65f)
@JvmField
val POMOHANA = compostableBlockItem("pomohana", CobblemonBlocks.POMOHANA, 0.3f)
@JvmField
val PAUVILLEA = compostableBlockItem("pauvillea", CobblemonBlocks.PAUVILLEA, 0.65f)
@JvmField
val WISTENSU = compostableBlockItem("wistensu", CobblemonBlocks.WISTENSU, 0.5f)
@JvmField
val VOLTACONIA = compostableBlockItem("voltaconia", CobblemonBlocks.VOLTACONIA, 0.85f)
@JvmField
val VERDROOT = compostableBlockItem("verdroot", CobblemonBlocks.VERDROOT, 0.85f)
@JvmField
val MISTLEAF = compostableBlockItem("mistleaf", CobblemonBlocks.MISTLEAF, 0.85f)
@JvmField
val LUCIBELL = compostableBlockItem("lucibell", CobblemonBlocks.LUCIBELL, 0.85f)
private fun blockItem(name: String, block: Block, rarity: Rarity = Rarity.COMMON): BlockItem = this.create(name, BlockItem(block, Item.Properties().rarity(rarity)))
private fun itemNameBlockItem(name: String, block: Block, rarity: Rarity = Rarity.COMMON): ItemNameBlockItem = this.create(name, ItemNameBlockItem(block, Item.Properties().rarity(rarity)))
@@ -1613,16 +1605,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
private fun wearableItem(name: String, heldItemRemappedName: String? = null): CobblemonItem = create(
name,
WearableHatItem(name).also {
wearables.add(it)
if (heldItemRemappedName != null) {
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
}
}
)
private fun wearableBlockItem(name: String, block: Block, heldItemRemappedName: String? = null): WearableBlockItem = create(
name,
WearableBlockItem(name, block).also {
WearableItem(name).also {
wearables.add(it)
if (heldItemRemappedName != null) {
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
@@ -1779,6 +1762,4 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
private fun foodItem(nutrition: Int, saturationModifier: Float): Item {
return CobblemonItem(Item.Properties().food(FoodProperties.Builder().nutrition(nutrition).saturationModifier(saturationModifier).build()))
}
private fun glowItem(name: String, block: Block): GlowItem = this.create(name, GlowItem(block, Item.Properties()))
}
@@ -9,6 +9,7 @@
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
@@ -16,15 +17,17 @@ 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.flag.FeatureFlags
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 MOLANG_UI = create("molang_ui", MenuType(::MolangContainerMenu, FeatureFlags.DEFAULT_FLAGS))
override val registry: Registry<MenuType<*>>
get() = BuiltInRegistries.MENU
override val resourceKey: ResourceKey<Registry<MenuType<*>>>
get() = Registries.MENU
}
}
@@ -17,6 +17,20 @@ 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.cinematic.BeatChangeHandler
import com.cobblemon.mod.common.client.net.cinematic.FeedAssignmentHandler
import com.cobblemon.mod.common.client.net.cinematic.FeedTransformHandler
import com.cobblemon.mod.common.client.net.cinematic.SceneEndHandler
import com.cobblemon.mod.common.client.net.cinematic.SceneStartHandler
import com.cobblemon.mod.common.client.net.editor.EditorPreviewAckHandler
import com.cobblemon.mod.common.client.net.editor.EditorPreviewStateHandler
import com.cobblemon.mod.common.client.net.editor.EditorRouteResultHandler
import com.cobblemon.mod.common.net.messages.client.editor.EditorRouteResultS2CPacket
import com.cobblemon.mod.common.net.messages.server.editor.EditorRouteRequestC2SPacket
import com.cobblemon.mod.common.net.serverhandling.editor.EditorRouteRequestHandler
import com.cobblemon.mod.common.net.messages.client.editor.EditorPreviewStateS2CPacket
import com.cobblemon.mod.common.net.messages.server.editor.EditorPreviewControlC2SPacket
import com.cobblemon.mod.common.net.serverhandling.editor.EditorPreviewControlHandler
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,6 +46,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.molangui.MolangUiCloseHandler
import com.cobblemon.mod.common.client.net.molangui.MolangUiCompatibilityRequestHandler
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.SetActiveTMPacketHandler
import com.cobblemon.mod.common.client.net.gui.SetTMMachineContainerDataPacketHandler
import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
@@ -53,6 +71,16 @@ import com.cobblemon.mod.common.client.net.riding.ClientboundUpdateDriverInputHa
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.stadium.StadiumConfigResultHandler
import com.cobblemon.mod.common.client.net.stadium.StadiumSpectateStateHandler
import com.cobblemon.mod.common.net.messages.client.stadium.StadiumSpectateStatePacket
import com.cobblemon.mod.common.net.messages.server.stadium.StadiumSpectatePacket
import com.cobblemon.mod.common.net.serverhandling.stadium.StadiumSpectateHandler
import com.cobblemon.mod.common.client.net.stadium.StadiumCinematicHandler
import com.cobblemon.mod.common.net.messages.client.stadium.StadiumCinematicPacket
import com.cobblemon.mod.common.net.messages.server.stadium.EditStadiumCinematicPacket
import com.cobblemon.mod.common.net.serverhandling.stadium.EditStadiumCinematicHandler
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,17 +100,30 @@ 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.serverhandling.stadium.RequestStadiumPlacementWandHandler
import com.cobblemon.mod.common.net.serverhandling.stadium.UpdateStadiumBlockHandler
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.cinematic.BeatChangePacket
import com.cobblemon.mod.common.net.messages.client.cinematic.FeedAssignmentPacket
import com.cobblemon.mod.common.net.messages.client.cinematic.FeedTransformPacket
import com.cobblemon.mod.common.net.messages.client.cinematic.SceneEndPacket
import com.cobblemon.mod.common.net.messages.client.cinematic.SceneStartPacket
import com.cobblemon.mod.common.net.messages.client.editor.EditorPreviewAckS2CPacket
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
import com.cobblemon.mod.common.net.messages.client.cooking.SeasoningRegistrySyncPacket
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.molangui.MolangUiClosePacket
import com.cobblemon.mod.common.net.messages.client.molangui.MolangUiCompatibilityRequestPacket
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.debug.RequestOpenRidingStatsDebugGUIPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
@@ -108,6 +149,10 @@ import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.Cle
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.stadium.StadiumConfigResultPacket
import com.cobblemon.mod.common.net.messages.server.stadium.RequestStadiumPlacementWandPacket
import com.cobblemon.mod.common.net.messages.server.stadium.UpdateStadiumBlockPacket
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
@@ -157,6 +202,8 @@ import com.cobblemon.mod.common.net.messages.server.RequestPlayerInteractionsPac
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.battle.BattleSelectActionsPacket
import com.cobblemon.mod.common.net.messages.server.battle.StadiumPreviewChoicePacket
import com.cobblemon.mod.common.net.serverhandling.battle.StadiumPreviewChoiceHandler
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamLeavePacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamRequestPacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamResponsePacket
@@ -176,10 +223,15 @@ import com.cobblemon.mod.common.net.messages.server.debug.OpenRidingStatsDebugGU
import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidingSettingsPacket
import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidingStatRangePacket
import com.cobblemon.mod.common.net.messages.server.debug.ServerboundUpdateRidingStatsPacket
import com.cobblemon.mod.common.net.messages.server.editor.EditorPreviewStartC2SPacket
import com.cobblemon.mod.common.net.messages.server.editor.EditorPreviewStopC2SPacket
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.molangui.MolangUiActionPacket
import com.cobblemon.mod.common.net.messages.server.molangui.MolangUiClosedPacket
import com.cobblemon.mod.common.net.messages.server.habitat.SaveHabitatBlockSettingsPacket
import com.cobblemon.mod.common.net.messages.server.npc.SaveNPCPacket
import com.cobblemon.mod.common.net.messages.server.molangui.MolangUiCompatibilityResponsePacket
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
@@ -234,11 +286,16 @@ import com.cobblemon.mod.common.net.serverhandling.debug.RequestOpenRidingStatsD
import com.cobblemon.mod.common.net.serverhandling.debug.ServerboundUpdateRidingSettingsHandler
import com.cobblemon.mod.common.net.serverhandling.debug.ServerboundUpdateRidingStatRangeHandler
import com.cobblemon.mod.common.net.serverhandling.debug.ServerboundUpdateRidingStatsHandler
import com.cobblemon.mod.common.net.serverhandling.editor.EditorPreviewStartHandler
import com.cobblemon.mod.common.net.serverhandling.editor.EditorPreviewStopHandler
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.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.molangui.MolangUiCompatibilityResponseHandler
import com.cobblemon.mod.common.net.serverhandling.orientation.ServerboundUpdateOrientationHandler
import com.cobblemon.mod.common.net.serverhandling.pasture.PasturePokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pasture.SetPastureConflictHandler
@@ -387,6 +444,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(BattleEndPacket.ID, BattleEndPacket::decode, BattleEndHandler))
list.add(PacketRegisterInfo(BattleInitializePacket.ID, BattleInitializePacket::decode, BattleInitializeHandler))
list.add(PacketRegisterInfo(BattleQueueRequestPacket.ID, BattleQueueRequestPacket::decode, BattleQueueRequestHandler))
list.add(PacketRegisterInfo(StadiumBattleStatePacket.ID, StadiumBattleStatePacket::decode, StadiumBattleStateHandler))
list.add(PacketRegisterInfo(BattleFaintPacket.ID, BattleFaintPacket::decode, BattleFaintHandler))
list.add(PacketRegisterInfo(BattleMakeChoicePacket.ID, BattleMakeChoicePacket::decode, BattleMakeChoiceHandler))
list.add(PacketRegisterInfo(BattleHealthChangePacket.ID, BattleHealthChangePacket::decode, BattleHealthChangeHandler))
@@ -403,9 +461,37 @@ 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
)
)
list.add(PacketRegisterInfo(SceneStartPacket.ID, SceneStartPacket::decode, SceneStartHandler))
list.add(PacketRegisterInfo(SceneEndPacket.ID, SceneEndPacket::decode, SceneEndHandler))
list.add(PacketRegisterInfo(FeedTransformPacket.ID, FeedTransformPacket::decode, FeedTransformHandler))
list.add(PacketRegisterInfo(FeedAssignmentPacket.ID, FeedAssignmentPacket::decode, FeedAssignmentHandler))
list.add(PacketRegisterInfo(BeatChangePacket.ID, BeatChangePacket::decode, BeatChangeHandler))
list.add(PacketRegisterInfo(EditorPreviewAckS2CPacket.ID, EditorPreviewAckS2CPacket::decode, EditorPreviewAckHandler))
list.add(PacketRegisterInfo(EditorPreviewStateS2CPacket.ID, EditorPreviewStateS2CPacket::decode, EditorPreviewStateHandler))
list.add(PacketRegisterInfo(EditorRouteResultS2CPacket.ID, EditorRouteResultS2CPacket::decode, EditorRouteResultHandler))
// MultiBattleTeam Packets
list.add(PacketRegisterInfo(TeamRequestNotificationPacket.ID, TeamRequestNotificationPacket::decode, TeamRequestNotificationHandler))
@@ -425,7 +511,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(MovesRegistrySyncPacket.ID, MovesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BerryRegistrySyncPacket.ID, BerryRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SpeciesRegistrySyncPacket.ID, SpeciesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(DexEvolutionDisplaySyncPacket.ID, DexEvolutionDisplaySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(PropertiesCompletionRegistrySyncPacket.ID, PropertiesCompletionRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(StandardSpeciesFeatureSyncPacket.ID, StandardSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(GlobalSpeciesFeatureSyncPacket.ID, GlobalSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
@@ -446,6 +531,11 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(RideSettingsSyncPacket.ID, RideSettingsSyncPacket::decode, RideSettingsSyncHandler))
list.add(PacketRegisterInfo(TechnicalMachineRegistrySyncPacket.ID, TechnicalMachineRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(MolangUiOpenPacket.ID, MolangUiOpenPacket::decode, MolangUiOpenHandler))
list.add(PacketRegisterInfo(MolangUiClosePacket.ID, MolangUiClosePacket::decode, MolangUiCloseHandler))
list.add(PacketRegisterInfo(MolangUiUpdatePacket.ID, MolangUiUpdatePacket::decode, MolangUiUpdateHandler))
list.add(PacketRegisterInfo(MolangUiCompatibilityRequestPacket.ID, MolangUiCompatibilityRequestPacket::decode, MolangUiCompatibilityRequestHandler))
// Effects
list.add(PacketRegisterInfo(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler))
list.add(PacketRegisterInfo(SpawnSnowstormEntityParticlePacket.ID, SpawnSnowstormEntityParticlePacket::decode, SpawnSnowstormEntityParticleHandler))
@@ -475,8 +565,25 @@ 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
)
)
list.add(PacketRegisterInfo(OpenStadiumConfigPacket.ID, OpenStadiumConfigPacket::decode, OpenStadiumConfigHandler))
list.add(PacketRegisterInfo(StadiumConfigResultPacket.ID, StadiumConfigResultPacket::decode, StadiumConfigResultHandler))
list.add(PacketRegisterInfo(StadiumSpectateStatePacket.ID, StadiumSpectateStatePacket::decode, StadiumSpectateStateHandler))
list.add(PacketRegisterInfo(StadiumCinematicPacket.ID, StadiumCinematicPacket::decode, StadiumCinematicHandler))
// Orientation
list.add(PacketRegisterInfo(ClientboundUpdateOrientationPacket.ID, ClientboundUpdateOrientationPacket::decode, ClientboundUpdateOrientationHandler))
@@ -574,6 +681,7 @@ object CobblemonNetwork {
// Battle packets
list.add(PacketRegisterInfo(BattleSelectActionsPacket.ID, BattleSelectActionsPacket::decode, BattleSelectActionsHandler))
list.add(PacketRegisterInfo(StadiumPreviewChoicePacket.ID, StadiumPreviewChoicePacket::decode, StadiumPreviewChoiceHandler))
list.add(PacketRegisterInfo(SpectateBattlePacket.ID, SpectateBattlePacket::decode, SpectateBattleHandler))
list.add(PacketRegisterInfo(RemoveSpectatorPacket.ID, RemoveSpectatorPacket::decode, RemoveSpectatorHandler))
@@ -641,8 +749,29 @@ object CobblemonNetwork {
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
)
)
list.add(PacketRegisterInfo(UpdateStadiumBlockPacket.ID, UpdateStadiumBlockPacket::decode, UpdateStadiumBlockHandler))
list.add(PacketRegisterInfo(EditStadiumCinematicPacket.ID, EditStadiumCinematicPacket::decode, EditStadiumCinematicHandler))
list.add(PacketRegisterInfo(RequestStadiumPlacementWandPacket.ID, RequestStadiumPlacementWandPacket::decode, RequestStadiumPlacementWandHandler))
list.add(PacketRegisterInfo(StadiumSpectatePacket.ID, StadiumSpectatePacket::decode, StadiumSpectateHandler))
list.add(PacketRegisterInfo(MolangUiActionPacket.ID, MolangUiActionPacket::decode, MolangUiActionHandler))
list.add(PacketRegisterInfo(MolangUiClosedPacket.ID, MolangUiClosedPacket::decode, MolangUiClosedHandler))
list.add(PacketRegisterInfo(MolangUiCompatibilityResponsePacket.ID, MolangUiCompatibilityResponsePacket::decode, MolangUiCompatibilityResponseHandler))
// Habitat blocks
list.add(PacketRegisterInfo(SaveHabitatBlockSettingsPacket.ID, SaveHabitatBlockSettingsPacket::decode, SaveHabitatBlockSettingsHandler))
@@ -650,6 +779,10 @@ object CobblemonNetwork {
// Debug / cheats
list.add(PacketRegisterInfo(RequestOpenRidingStatsDebugGUIPacket.ID, RequestOpenRidingStatsDebugGUIPacket::decode, RequestOpenRidingStatsDebugGUIHandler))
list.add(PacketRegisterInfo(EditorPreviewStartC2SPacket.ID, EditorPreviewStartC2SPacket::decode, EditorPreviewStartHandler))
list.add(PacketRegisterInfo(EditorRouteRequestC2SPacket.ID, EditorRouteRequestC2SPacket::decode, EditorRouteRequestHandler))
list.add(PacketRegisterInfo(EditorPreviewStopC2SPacket.ID, EditorPreviewStopC2SPacket::decode, EditorPreviewStopHandler))
list.add(PacketRegisterInfo(EditorPreviewControlC2SPacket.ID, EditorPreviewControlC2SPacket::decode, EditorPreviewControlHandler))
return list
}
@@ -657,4 +790,4 @@ object CobblemonNetwork {
fun sendPacketToPlayer(player: ServerPlayer, packet: NetworkPacket<*>) {
Cobblemon.implementation.networkManager.sendPacketToPlayer(player, packet)
}
}
}
@@ -148,9 +148,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val MONITOR_GLITCHING = this.create("block.monitor.glitching")
@JvmField
val BRIGHT_POWDER_USE = this.create("block.bright_powder.use")
@JvmField
val FOSSIL_MACHINE_ACTIVATE = this.create("block.fossil_machine.activate")
@JvmField
@@ -660,95 +657,5 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
SoundEvents.WOOL_FALL
)
@JvmField
val ITEM_BLOCK_POWDER_PLACE = this.create("block.item_block.powder.place")
@JvmField
val ITEM_BLOCK_POWDER_BREAK = this.create("block.item_block.powder.break")
@JvmField
val ITEM_BLOCK_POWDER_STEP = this.create("block.item_block.powder.step")
@JvmField
val ITEM_BLOCK_POWDER_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_POWDER_BREAK,
ITEM_BLOCK_POWDER_STEP,
ITEM_BLOCK_POWDER_PLACE,
ITEM_BLOCK_POWDER_STEP,
ITEM_BLOCK_POWDER_STEP
)
@JvmField
val ITEM_BLOCK_BOTTLE_LIQUID_PLACE = this.create("block.item_block.bottle_liquid.place")
@JvmField
val ITEM_BLOCK_BOTTLE_LIQUID_BREAK = this.create("block.item_block.bottle_liquid.break")
@JvmField
val ITEM_BLOCK_BOTTLE_LIQUID_STEP = this.create("block.item_block.bottle_liquid.step")
@JvmField
val ITEM_BLOCK_BOTTLE_LIQUID_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_BOTTLE_LIQUID_BREAK,
ITEM_BLOCK_BOTTLE_LIQUID_STEP,
ITEM_BLOCK_BOTTLE_LIQUID_PLACE,
ITEM_BLOCK_BOTTLE_LIQUID_PLACE,
ITEM_BLOCK_BOTTLE_LIQUID_PLACE
)
@JvmField
val ITEM_BLOCK_BOTTLE_PILLS_PLACE = this.create("block.item_block.bottle_pills.place")
@JvmField
val ITEM_BLOCK_BOTTLE_PILLS_BREAK = this.create("block.item_block.bottle_pills.break")
@JvmField
val ITEM_BLOCK_BOTTLE_PILLS_STEP = this.create("block.item_block.bottle_pills.step")
@JvmField
val ITEM_BLOCK_BOTTLE_PILLS_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_BOTTLE_PILLS_BREAK,
ITEM_BLOCK_BOTTLE_PILLS_STEP,
ITEM_BLOCK_BOTTLE_PILLS_PLACE,
ITEM_BLOCK_BOTTLE_PILLS_PLACE,
ITEM_BLOCK_BOTTLE_PILLS_PLACE
)
@JvmField
val ITEM_BLOCK_DICE_PLACE = this.create("block.item_block.dice.place")
@JvmField
val ITEM_BLOCK_DICE_BREAK = this.create("block.item_block.dice.break")
@JvmField
val ITEM_BLOCK_DICE_STEP = this.create("block.item_block.dice.step")
@JvmField
val ITEM_BLOCK_DICE_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_DICE_BREAK,
ITEM_BLOCK_DICE_STEP,
ITEM_BLOCK_DICE_PLACE,
ITEM_BLOCK_DICE_STEP,
ITEM_BLOCK_DICE_STEP
)
@JvmField
val ITEM_BLOCK_METAL_CAN_PLACE = this.create("block.item_block.metal_can.place")
@JvmField
val ITEM_BLOCK_METAL_CAN_BREAK = this.create("block.item_block.metal_can.break")
@JvmField
val ITEM_BLOCK_METAL_CAN_STEP = this.create("block.item_block.metal_can.step")
@JvmField
val ITEM_BLOCK_METAL_CAN_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_METAL_CAN_BREAK,
ITEM_BLOCK_METAL_CAN_STEP,
ITEM_BLOCK_METAL_CAN_PLACE,
ITEM_BLOCK_METAL_CAN_STEP,
ITEM_BLOCK_METAL_CAN_STEP
)
@JvmField
val ITEM_BLOCK_JAR_GLASS_LIQUID_PLACE = this.create("block.item_block.jar_glass_liquid.place")
@JvmField
val ITEM_BLOCK_JAR_GLASS_LIQUID_BREAK = this.create("block.item_block.jar_glass_liquid.break")
@JvmField
val ITEM_BLOCK_JAR_GLASS_LIQUID_STEP = this.create("block.item_block.jar_glass_liquid.step")
@JvmField
val ITEM_BLOCK_JAR_GLASS_LIQUID_SOUNDS = SoundType(1f, 1.1f,
ITEM_BLOCK_JAR_GLASS_LIQUID_BREAK,
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP,
ITEM_BLOCK_JAR_GLASS_LIQUID_PLACE,
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP,
ITEM_BLOCK_JAR_GLASS_LIQUID_STEP
)
private fun create(name: String): SoundEvent = this.create(name, SoundEvent.createVariableRangeEvent(cobblemonResource(name)))
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
@@ -18,7 +17,6 @@ import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.withQueryValue
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import com.mojang.datafixers.util.Either
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
@@ -36,8 +34,6 @@ import net.minecraft.world.entity.ai.sensing.SensorType
class HateEntityTaskConfig : SingleTaskConfig {
val entityCondition: Expression = "true".asExpression()
val range: ExpressionOrEntityVariable = Either.left("24".asExpression())
val chance: Float = Cobblemon.config.predationChance
private val coercedChance = chance.coerceIn(0f, 1f)
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = emptyList<MoLangConfigVariable>()
override fun createTask(
@@ -47,44 +43,24 @@ class HateEntityTaskConfig : SingleTaskConfig {
val range = range.resolveFloat(behaviourConfigurationContext.runtime)
behaviourConfigurationContext.addMemories(
MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES,
MemoryModuleType.ANGRY_AT,
MemoryModuleType.ATTACK_TARGET
MemoryModuleType.ANGRY_AT
)
behaviourConfigurationContext.addSensors(SensorType.NEAREST_LIVING_ENTITIES)
return BehaviorBuilder.create { instance ->
instance.group(
instance.present(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES),
instance.registered(MemoryModuleType.ANGRY_AT),
instance.registered(MemoryModuleType.ATTACK_TARGET),
).apply(instance) { entities, angryAt, attackTarget ->
instance.registered(MemoryModuleType.ANGRY_AT)
).apply(instance) { entities, angryAt ->
Trigger { world, entity, _ ->
// bail if gamerule is disabled
if (!world.gameRules.getBoolean(CobblemonGameRules.DO_POKEMON_PREDATION)) return@Trigger false
val target = instance.get(entities).findClosest { hated ->
mainThreadRuntime.withQueryValue("entity", hated.asMostSpecificMoLangValue())
val target = instance.get(entities).findClosest {
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
return@findClosest mainThreadRuntime.resolveBoolean(entityCondition)
}.orElse(null)
var shouldTrigger = false
if (target != null && target.distanceTo(entity) <= range) {
shouldTrigger = when {
// set always attacking if activity chance is equal to 1.0
coercedChance == 1f -> {
angryAt.set(target.uuid)
attackTarget.set(target)
true
}
// roll the dice in other way
coercedChance != 1f && world.random.nextFloat() < coercedChance -> {
angryAt.set(target.uuid)
attackTarget.set(target)
true
}
// fallback to peaceful behavior in unattended cases
else -> false
}
angryAt.set(target.uuid)
}
return@Trigger shouldTrigger
return@Trigger true
}
}
}
@@ -1,25 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.battles.model
/**
* How a [PokemonBattle] came to an end.
*/
enum class BattleEndResult {
/** One side defeated the other. */
VICTORY,
/** A wild Pokémon was captured, ending the battle. */
CAPTURE,
/** An actor forfeited, giving the victory to the other side. */
FORFEIT,
/** A player fled from a wild Pokémon. */
FLED,
/** The battle was stopped without a winner, such as by a command, a player disconnecting or an error. */
STOPPED
}
@@ -21,7 +21,6 @@ import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
import com.cobblemon.mod.common.api.battles.model.actor.FleeableBattleActor
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.BattleEndedEvent
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
import com.cobblemon.mod.common.api.events.battles.BattleFleeAttemptEvent
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
@@ -36,6 +35,7 @@ import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.BattleCaptureAction
import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.stadium.StadiumBattleSession
import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.battles.FleeAttemptActionResponse
import com.cobblemon.mod.common.battles.ForfeitActionResponse
@@ -81,14 +81,18 @@ import net.minecraft.world.item.ItemStack
open class PokemonBattle(
val format: BattleFormat,
val side1: BattleSide,
val side2: BattleSide
val side2: BattleSide,
initializeProgress: Boolean = true
) {
/** 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()
var stadiumSession: StadiumBattleSession? = null
val battlePartyStores = mutableListOf<PlayerPartyStore>()
@@ -97,6 +101,12 @@ open class PokemonBattle(
side2.battle = this
this.actors.forEach { actor ->
actor.battle = this
}
if (initializeProgress) resetEvolutionProgress()
}
internal fun resetEvolutionProgress() {
actors.forEach { actor ->
actor.pokemonList.forEach { battlePokemon ->
battlePokemon.effectedPokemon.evolutionProxy.current().progress()
.filterIsInstance<LastBattleCriticalHitsEvolutionProgress>()
@@ -125,12 +135,6 @@ open class PokemonBattle(
var winners = listOf<BattleActor>()
var losers = listOf<BattleActor>()
var ended = false
/** How the battle ended, or null if it hasn't ended yet. */
var endResult: BattleEndResult? = null
/** The actor that forfeited the battle, if any. */
var forfeitedBy: BattleActor? = null
/** Whether a player fled from the battle. */
var fled = false
// TEMP battle showcase stuff
var announcingRules = false
var turn: Int = 0
@@ -239,16 +243,14 @@ open class PokemonBattle(
}
fun writeShowdownAction(vararg messages: String) {
if (messages.any { it.contains("\n") || it.contains("\r") }) {
LOGGER.warn("Refusing to forward Showdown action with an embedded line break for battle {}: {}", battleId, messages.joinToString(" | "))
return
}
log(messages.joinToString("\n"))
ShowdownService.service.send(battleId, messages.toList().toTypedArray())
}
fun turn(newTurnNumber: Int) {
actors.forEach { it.turn() }
// Prepare all actors before notifying synchronous AI, or its first response can clear other actors' requests.
actors.forEach { it.prepareTurn() }
actors.forEach { it.fireChoiceRequest() }
// TODO: If a pokemon switches in the same turn another pokemon is KO'd it will not receive exp for the KO
for (side in sides) {
val opposite = side.getOppositeSide()
@@ -261,15 +263,8 @@ open class PokemonBattle(
}
fun end() {
if (ended) return
ended = true
val result = when {
winners.isNotEmpty() && showdownMessages.any { "capture" in it } -> BattleEndResult.CAPTURE
winners.isNotEmpty() && forfeitedBy != null -> BattleEndResult.FORFEIT
winners.isNotEmpty() -> BattleEndResult.VICTORY
fled -> BattleEndResult.FLED
else -> BattleEndResult.STOPPED
}
endResult = result
val awardToFainted = Cobblemon.config.awardExperienceToFaintedPokemon
val awardOnLoss = Cobblemon.config.awardExperienceOnBattleLoss
this.actors.forEach { actor ->
@@ -351,7 +346,6 @@ open class PokemonBattle(
}
}
}
CobblemonEvents.BATTLE_ENDED.post(BattleEndedEvent(this, result, forfeitedBy.takeIf { result == BattleEndResult.FORFEIT }))
sendUpdate(BattleEndPacket())
BattleRegistry.closeBattle(this)
}
@@ -484,7 +478,7 @@ open class PokemonBattle(
val message = battleLang("crash").red()
this.actors.filterIsInstance<PlayerBattleActor>().forEach { it.entity?.sendSystemMessage(message) }
this.saveBattleLog()
this.stop()
if (stadiumSession != null) BattleRegistry.abortStadiumBattle(battleId) else this.stop()
return
}
@@ -492,6 +486,7 @@ open class PokemonBattle(
ticks++
if (isPvW && !ended && dispatches.isEmpty()) checkFlee()
}
stadiumSession?.tick()
}
open fun checkFlee() {
@@ -522,12 +517,12 @@ open class PokemonBattle(
.forEach{it.pokemon.heal()}
CobblemonEvents.BATTLE_FLED.post(BattleFledEvent(this, actors.asSequence().filterIsInstance<PlayerBattleActor>().iterator().next()))
actors.filterIsInstance<EntityBackedBattleActor<*>>().mapNotNull { it.entity }.forEach { it.sendSystemMessage(battleLang("flee").yellow()) }
fled = true
stop()
}
}
fun stop() {
if (ended) return
writeShowdownAction(">forcetie") // This will terminate the Showdown connection
end()
}
@@ -560,7 +555,6 @@ open class PokemonBattle(
private fun checkForfeit(): Boolean {
val forfeit = actors.find { it.responses.any { it is ForfeitActionResponse } }
return forfeit?.let {
forfeitedBy = it
this.dispatchWaiting { this.broadcastChatMessage(battleLang("forfeit", it.getName()).red()) }
writeShowdownAction(">forcelose ${it.showdownId}")
true
@@ -605,4 +599,4 @@ open class PokemonBattle(
queryStruct.addFunction("has_rule") { params -> DoubleValue(params.getString(0) in format.ruleSet) }
return queryStruct
}
}
}
@@ -11,8 +11,9 @@ package com.cobblemon.mod.common.api.battles.model.actor
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.battles.DefaultActionResponse
import com.cobblemon.mod.common.battles.ForcePassActionResponse
import com.cobblemon.mod.common.battles.PassActionResponse
import com.cobblemon.mod.common.battles.ShowdownActionResponse
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.exception.IllegalActionChoiceException
import com.cobblemon.mod.common.net.messages.client.battle.BattleFaintPacket
@@ -49,36 +50,40 @@ abstract class AIBattleActor(
* Called when the AI is requested to make a choice.
*/
open fun onChoiceRequested() {
val req = request
if (req == null) {
responses.clear()
expectingPassActions.clear()
mustChoose = false
battle.checkForInputDispatch()
return
}
val passActions = expectingPassActions.toList()
try {
request?.let {
setActionResponses(it.iterate(this.activePokemon) { battleMon, moveset, forceSwitch ->
battleAI.choose(battleMon, battle, getSide(), moveset, forceSwitch)
})
pokemonList.forEach { pokemon ->
pokemon.willBeSwitchedIn = false
}
} ?: {
val response = mutableListOf<ShowdownActionResponse>()
repeat(activePokemon.size) {
response.add(PassActionResponse)
}
setActionResponses(response)
LOGGER.warn("AI requested choice, but no request was set. Returning PassActionResponses.")
}
setActionResponses(req.iterate(this.activePokemon) { battleMon, moveset, forceSwitch ->
battleAI.choose(battleMon, battle, getSide(), moveset, forceSwitch)
})
} catch (exception: IllegalActionChoiceException) {
// Validation may have consumed forced passes and accepted a prefix before rejecting a later slot.
responses.clear()
expectingPassActions.clear()
expectingPassActions.addAll(passActions)
if (req.forceSwitch.any { it }) {
LOGGER.error("AI could not choose replacements; using Showdown's default choices", exception)
setActionResponses(req.iterate(activePokemon) { _, _, _ -> DefaultActionResponse() })
return
}
LOGGER.error("AI was unable to choose an action, we're going to need to pass!")
exception.printStackTrace()
request?.let {
setActionResponses(it.iterate(this.activePokemon) { _, _ , _->
PassActionResponse
})
} ?: {
val response = mutableListOf<ShowdownActionResponse>()
repeat(activePokemon.size) {
response.add(PassActionResponse)
}
setActionResponses(response)
}
var forcedPasses = passActions.size
setActionResponses(req.iterate(this.activePokemon) { _, moveset, forceSwitch ->
if (!forceSwitch && moveset != null && forcedPasses > 0) {
forcedPasses--
ForcePassActionResponse()
} else PassActionResponse
})
} finally {
pokemonList.forEach { it.willBeSwitchedIn = false }
}
}
}
}
@@ -24,8 +24,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 +53,8 @@ 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
/** Positions follow active slot order: slot a is index 0. */
var stadiumSendOutPositions: List<Vec3>? = null
abstract val type: ActorType
@@ -71,21 +74,31 @@ abstract class BattleActor(
open fun isForPlayer(serverPlayerEntity: ServerPlayer) = serverPlayerEntity.uuid in getPlayerUUIDs()
open fun isForPokemon(pokemonEntity: PokemonEntity) = activePokemon.any { it.battlePokemon?.effectedPokemon?.entity == pokemonEntity }
fun turn() {
val request = request ?: return
responses.clear()
if (activePokemon.any { it.isAlive() }) {
mustChoose = true
sendUpdate(BattleMakeChoicePacket())
}
val requestActive = request.active
if (requestActive == null || requestActive.isEmpty() || request.wait) {
this.request = null
/** Prepare every actor before notifying any AI, since synchronous choices can dispatch immediately. */
fun prepareTurn() {
mustChoose = false
val req = request ?: run {
responses.clear()
expectingPassActions.clear()
return
}
responses.clear()
if (activePokemon.any { it.isAlive() }) {
mustChoose = true
}
val requestActive = req.active
if (req.wait || (requestActive.isNullOrEmpty() && req.forceSwitch.none { it })) {
this.request = null
expectingPassActions.clear()
mustChoose = false
} else if (req.forceSwitch.any { it }) {
mustChoose = true
}
}
/** Call only after every actor has been prepared for this turn. */
fun fireChoiceRequest() {
if (mustChoose) sendUpdate(BattleMakeChoicePacket())
}
fun postUpdate() {
@@ -162,4 +175,4 @@ abstract class BattleActor(
open fun win(otherWinners: List<BattleActor>, losers: List<BattleActor>) {}
open fun lose(winners: List<BattleActor>, otherLosers: List<BattleActor>) {}
}
}
@@ -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.api.camera
/** Minecraft yaw and pitch, in degrees. */
data class CameraRotation(
val yaw: Float,
val pitch: Float
)
@@ -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
// Air bubbles have no separate vanilla call that Fabric can cancel.
enum class HudElement(val id: String) {
CROSSHAIR("crosshair"),
HOTBAR("hotbar"),
EXPERIENCE_BAR("experience_bar"),
PLAYER_HEALTH("player_health"),
ARMOR("armor"),
FOOD("food"),
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,27 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.camera.model
data class ParameterDefinition(
val type: ParameterType,
val description: String? = null,
val default: ParameterDefault? = null
)
enum class ParameterType {
NUMBER,
VEC3,
STRING
}
sealed class ParameterDefault {
data class NumberDefault(val value: Double) : ParameterDefault()
data class Vec3Default(val x: Double, val y: Double, val z: Double) : ParameterDefault()
data class StringDefault(val value: String) : ParameterDefault()
}
@@ -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.model
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.resolve
import com.google.gson.JsonElement
import net.minecraft.world.phys.Vec3
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 -> {
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() {
init { require(value.isFinite()) { "Static coordinate must be finite" } }
}
data class Dynamic(val expression: ExpressionLike) : CoordinateValue()
fun evaluate(
context: Map<String, MoValue>,
runtime: MoLangRuntime
): Double {
return when (this) {
is Static -> value
is Dynamic -> runtime.resolve(expression, context).asDouble().also {
require(it.isFinite()) { "Dynamic coordinate must evaluate to a finite value" }
}
}
}
}
@@ -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.api.camera.result
import net.minecraft.network.chat.Component
data class ValidationError(
val path: String,
val message: Component,
val severity: Severity = Severity.ERROR
) {
enum class Severity { WARNING, ERROR }
override fun toString(): String = "[$severity] $path: ${message.string}"
}
@@ -0,0 +1,49 @@
/*
* 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.cinematic.model
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.Entity
sealed class ActorBinding {
abstract val kind: ActorKind
/** Visible player actors override server position and suppress movement packets. */
data class Player(
val player: ServerPlayer,
val fallbackPokemon: PokemonProperties? = null
) : ActorBinding() {
override val kind: ActorKind = ActorKind.PlayerTakeover
}
data class Real(val entity: Entity) : ActorBinding() {
override val kind: ActorKind = ActorKind.RealEntity
}
data class SynthPokemon(
val properties: PokemonProperties,
val aspects: Set<String> = emptySet()
) : ActorBinding() {
override val kind: ActorKind = ActorKind.SynthesizedPokemon
}
data class SynthBedrock(
val category: ResourceLocation,
val aspects: Set<String> = emptySet(),
val scale: Float = 1.0f
) : ActorBinding() {
override val kind: ActorKind = ActorKind.SynthesizedBedrock
init {
require(scale.isFinite() && scale > 0f) { "SynthBedrock scale must be positive, got $scale" }
}
}
}
@@ -0,0 +1,28 @@
/*
* 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.cinematic.model
data class ActorDeclaration(
val id: String,
val kind: ActorKind
) {
init {
require(id.isNotBlank()) { "ActorDeclaration id must be non-blank" }
}
}
sealed class ActorKind {
object PlayerTakeover : ActorKind()
object RealEntity : ActorKind()
object SynthesizedPokemon : ActorKind()
object SynthesizedBedrock : ActorKind()
}
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.cinematic.model
import com.cobblemon.mod.common.api.cinematic.model.tracks.ActorTrack
/** Null visibility carries over between beats; tracks never carry over. */
data class ActorDirection(
val visible: Boolean? = null,
val startPose: StartPose? = null,
val startPoseMode: StartPoseMode? = null,
val tracks: List<ActorTrack> = emptyList()
) {
init {
if (startPose != null) {
require(visible != false) {
"ActorDirection.startPose declared but visible = false; hidden actors cannot have a start pose"
}
}
}
}
@@ -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.cinematic.model
import com.cobblemon.mod.common.api.camera.model.HudElement
/** Null duration leaves the beat unbounded; feeds without cuts are inactive. */
data class Beat(
val id: String,
val duration: Float? = null,
val isLooping: Boolean = false,
val speed: Float = 1.0f,
val actorDirections: Map<String, ActorDirection> = emptyMap(),
val cuts: Map<String, CutTrack> = emptyMap(),
val guiTrack: SceneGuiTrack? = null,
val renderPlayerBody: Boolean = true,
val hiddenHudElements: Set<HudElement> = emptySet(),
val cues: List<SceneCue> = emptyList()
) {
init {
require(id.isNotBlank()) { "Beat id must be non-blank" }
require(speed.isFinite() && speed > 0f) { "Beat speed must be positive, got $speed" }
duration?.let {
require(it.isFinite() && it >= 0f) { "Beat duration must be non-negative, got $it" }
}
}
}
@@ -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.api.cinematic.model
import com.cobblemon.mod.common.api.molang.ExpressionLike
/** Clamping at a beat's duration does not end the scene; completion requires an explicit transition. */
data class BeatTransition(
val sourceBeatId: String,
val targetBeatId: String,
val condition: ExpressionLike,
val transition: CutTransition = CutTransition.Blend(0.4f),
val endsScene: Boolean = false
) {
init {
require(sourceBeatId.isNotBlank()) { "BeatTransition sourceBeatId must be non-blank" }
require(endsScene || targetBeatId.isNotBlank()) { "BeatTransition targetBeatId must be non-blank" }
}
}
@@ -6,11 +6,8 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.item
package com.cobblemon.mod.common.api.cinematic.model
import net.minecraft.world.item.ItemNameBlockItem
import net.minecraft.world.level.block.Block
import net.minecraft.network.chat.Component
class WearableBlockItem(override val name: String, block: Block): ItemNameBlockItem(block, Properties()), WearableItem{
}
class CinematicValidationException(val reason: Component) : IllegalArgumentException(reason.string)
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.cinematic.model
data class CutTrack(
val entries: List<CutEntry>
) {
init {
require(entries.isNotEmpty()) { "CutTrack must have at least one entry" }
require(entries.zipWithNext().all { (a, b) -> a.time <= b.time }) {
"CutTrack entries must be sorted ascending by time"
}
}
}
data class CutEntry(
val time: Float,
val shotId: String,
val transition: CutTransition = CutTransition.Instant
) {
init {
require(time.isFinite() && time >= 0f) { "CutEntry time must be non-negative, got $time" }
require(shotId.isNotBlank()) { "CutEntry shotId must be non-blank" }
}
}
sealed class CutTransition {
object Instant : CutTransition()
data class Blend(val duration: Float) : CutTransition() {
init {
require(duration.isFinite() && duration > 0f) { "Blend duration must be positive, got $duration" }
}
}
}
@@ -0,0 +1,16 @@
/*
* 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.cinematic.model
enum class DisconnectPolicy {
END_SCENE,
SUBSTITUTE,
HIDE_ACTOR,
FAIL
}
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.cinematic.model
data class FeedDeclaration(
val id: String,
val kind: FeedKind
) {
init {
require(id.isNotBlank()) { "FeedDeclaration id must be non-blank" }
}
companion object {
/** Bound for feed counts read off the wire, so a crafted packet can't drive a huge allocation. */
const val MAX_FEEDS = 64
}
}
sealed class FeedKind {
/** Occupies the full viewport. */
object Fullscreen : FeedKind()
/** Picture-in-picture overlay in a viewport corner. [size] is a fraction of viewport. */
data class Pip(val corner: Corner, val size: Float) : FeedKind() {
init {
require(size > 0f && size <= 1f) { "Pip size must be in (0, 1], got $size" }
}
}
/** Relative share of a paired split. */
data class Split(val side: Side, val size: Float) : FeedKind() {
init {
require(size > 0f && size <= 1f) { "Split size must be in (0, 1], got $size" }
}
}
data class Diagonal(val half: DiagonalHalf) : FeedKind()
enum class Corner { TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT }
enum class Side { LEFT, RIGHT, TOP, BOTTOM }
enum class DiagonalHalf { UPPER, LOWER }
}
@@ -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.api.cinematic.model
fun <T> orderedKeyframes(keyframes: List<T>, time: (T) -> Float): List<T> {
require(keyframes.isNotEmpty()) { "At least one keyframe is required" }
require(keyframes.all { time(it).isFinite() && time(it) >= 0f }) { "Keyframe times must be finite and nonnegative" }
val ordered = ArrayList<T>(keyframes.size)
for (keyframe in keyframes.sortedBy(time)) {
if (ordered.isNotEmpty() && time(ordered.last()) == time(keyframe)) {
ordered[ordered.lastIndex] = keyframe
} else {
ordered.add(keyframe)
}
}
return ordered
}
@@ -0,0 +1,231 @@
/*
* 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.cinematic.model
import net.minecraft.network.chat.Component
import com.cobblemon.mod.common.api.cinematic.runtime.ActorFraming
import com.cobblemon.mod.common.api.camera.model.ParameterDefinition
import com.cobblemon.mod.common.api.camera.result.ValidationError
import com.cobblemon.mod.common.api.cinematic.runtime.ActorDependencyOrder
import com.cobblemon.mod.common.api.cinematic.model.sources.builtin.OrbitPosition
import com.cobblemon.mod.common.api.cinematic.model.sources.builtin.LookAt
import com.cobblemon.mod.common.api.cinematic.model.sources.builtin.LookAtTarget
data class Scene(
val id: String,
val parameters: Map<String, ParameterDefinition> = emptyMap(),
val actors: Map<String, ActorDeclaration> = emptyMap(),
val shots: Map<String, Shot> = emptyMap(),
val feeds: List<FeedDeclaration> = emptyList(),
val startBeatId: String,
val beats: Map<String, Beat>,
val transitions: List<BeatTransition> = emptyList(),
val version: Int = 2,
val damageImmunityDuringTakeover: Boolean = false,
val disconnectPolicy: DisconnectPolicy = DisconnectPolicy.END_SCENE
) {
init {
require(id.isNotBlank()) { "Scene id must be non-blank" }
require(startBeatId.isNotBlank()) { "Scene startBeatId must be non-blank" }
require(version == 2) { "Scene.version must be 2, got $version" }
}
fun getBeat(beatId: String): Beat? = beats[beatId]
fun getShot(shotId: String): Shot? = shots[shotId]
fun getActor(actorId: String): ActorDeclaration? = actors[actorId]
fun getFeed(feedId: String): FeedDeclaration? = feeds.firstOrNull { it.id == feedId }
fun getTransitionsFrom(beatId: String): List<BeatTransition> =
transitions.filter { it.sourceBeatId == beatId }
fun validate(): List<ValidationError> {
val errors = mutableListOf<ValidationError>()
if (startBeatId !in beats) {
errors.add(ValidationError(
path = "startBeatId",
message = Component.translatable("cobblemon.cinematic_editor.validation.start_missing", startBeatId)
))
}
actors.forEach { (key, actor) ->
if (actor.id != key) {
errors.add(ValidationError(
path = "actors[$key].id",
message = Component.translatable("cobblemon.cinematic_editor.validation.actor_id", key, actor.id)
))
}
}
shots.forEach { (key, shot) ->
val framingActors = (shot.position as? OrbitPosition)?.actorIds.orEmpty() +
listOfNotNull((shot.position as? OrbitPosition)?.bearingActorId) +
((shot.rotation as? LookAt)?.target as? LookAtTarget.Actors)?.actorIds.orEmpty()
framingActors.distinct().filter { it !in actors && it !in ActorFraming.groups }.forEach { actorId ->
errors.add(ValidationError(path = "shots[$key]", message = Component.translatable("cobblemon.cinematic_editor.validation.framing_actor", actorId)))
}
if (shot.id != key) {
errors.add(ValidationError(
path = "shots[$key].id",
message = Component.translatable("cobblemon.cinematic_editor.validation.shot_id", key, shot.id)
))
}
}
val feedIds = feeds.map { it.id }
val duplicateFeedIds = feedIds.groupingBy { it }.eachCount().filter { it.value > 1 }.keys
duplicateFeedIds.forEach { duplicateId ->
errors.add(ValidationError(
path = "feeds",
message = Component.translatable("cobblemon.cinematic_editor.validation.duplicate_feed", duplicateId)
))
}
val cueIds = mutableSetOf<String>()
beats.forEach { (beatKey, beat) ->
beat.actorDirections.forEach { (actorId, direction) ->
if (direction.tracks.any { it.trackType == "pathfinding" } &&
direction.tracks.count { it.trackType in setOf("pathfinding", "spline_movement", "keyframe_movement", "follow_actor", "attach", "pose_lock") } > 1) {
errors.add(ValidationError(
path = "beats[$beatKey].actorDirections[$actorId].tracks",
message = Component.translatable("cobblemon.cinematic_editor.validation.movement_conflict")
))
}
}
beat.cues.forEachIndexed { index, cue ->
if (!cueIds.add(cue.id)) errors.add(ValidationError(
path = "beats[$beatKey].cues[$index].id", message = Component.translatable("cobblemon.cinematic_editor.validation.duplicate_cue", cue.id)))
if (beat.duration != null && cue.time > beat.duration) errors.add(ValidationError(
path = "beats[$beatKey].cues[$index].time", message = Component.translatable("cobblemon.cinematic_editor.validation.late_cue", cue.id)))
}
try {
ActorDependencyOrder.resolve(actors.keys, beat)
} catch (error: CinematicValidationException) {
errors.add(ValidationError(path = "beats[$beatKey].actorDirections", message = error.reason))
}
if (beat.id != beatKey) {
errors.add(ValidationError(
path = "beats[$beatKey].id",
message = Component.translatable("cobblemon.cinematic_editor.validation.beat_id", beatKey, beat.id)
))
}
beat.actorDirections.keys.forEach { actorRef ->
if (actorRef !in actors) {
errors.add(ValidationError(
path = "beats[$beatKey].actorDirections[$actorRef]",
message = Component.translatable("cobblemon.cinematic_editor.validation.actor_missing", actorRef)
))
}
}
beat.cuts.forEach { (feedRef, cutTrack) ->
if (feedRef !in feedIds) {
errors.add(ValidationError(
path = "beats[$beatKey].cuts[$feedRef]",
message = Component.translatable("cobblemon.cinematic_editor.validation.feed_missing", feedRef)
))
}
cutTrack.entries.forEach { entry ->
if (entry.shotId !in shots) {
errors.add(ValidationError(
path = "beats[$beatKey].cuts[$feedRef]",
message = Component.translatable("cobblemon.cinematic_editor.validation.shot_missing", entry.shotId, entry.time)
))
}
}
}
}
transitions.forEachIndexed { index, transition ->
if (transition.sourceBeatId !in beats) {
errors.add(ValidationError(
path = "transitions[$index].sourceBeatId",
message = Component.translatable("cobblemon.cinematic_editor.validation.source_missing", transition.sourceBeatId)
))
}
if (!transition.endsScene && transition.targetBeatId !in beats) {
errors.add(ValidationError(
path = "transitions[$index].targetBeatId",
message = Component.translatable("cobblemon.cinematic_editor.validation.target_missing", transition.targetBeatId)
))
}
}
val graph = transitions.filter { !it.endsScene && it.sourceBeatId in beats && it.targetBeatId in beats }
.groupBy({ it.sourceBeatId }, { it.targetBeatId })
val reachable = findReachableBeats(graph)
beats.keys.forEach { beatId ->
if (beatId !in reachable) {
errors.add(ValidationError(
path = "beats[$beatId]",
message = Component.translatable("cobblemon.cinematic_editor.validation.unreachable", startBeatId),
severity = ValidationError.Severity.WARNING
))
}
}
val cycle = findCycle(graph)
if (cycle != null) {
errors.add(ValidationError(
path = "transitions",
message = Component.translatable("cobblemon.cinematic_editor.validation.cycle", cycle.joinToString(" -> ")),
severity = ValidationError.Severity.WARNING
))
}
return errors
}
private fun findReachableBeats(graph: Map<String, List<String>>): Set<String> {
val reachable = mutableSetOf<String>()
val queue = ArrayDeque<String>()
if (startBeatId in beats) {
queue.add(startBeatId)
reachable.add(startBeatId)
}
while (queue.isNotEmpty()) {
for (target in graph[queue.removeFirst()].orEmpty()) {
if (reachable.add(target)) queue.addLast(target)
}
}
return reachable
}
private fun findCycle(graph: Map<String, List<String>>): List<String>? {
val visited = mutableSetOf<String>()
val active = mutableMapOf<String, Int>()
val path = mutableListOf<String>()
val stack = ArrayDeque<Iterator<String>>()
fun enter(id: String) {
visited.add(id)
active[id] = path.size
path.add(id)
stack.addLast(graph[id].orEmpty().iterator())
}
for (id in beats.keys) {
if (id in visited) continue
enter(id)
while (stack.isNotEmpty()) {
val remaining = stack.last()
if (remaining.hasNext()) {
val target = remaining.next()
val cycleStart = active[target]
if (cycleStart != null) return path.subList(cycleStart, path.size) + target
if (target !in visited) enter(target)
} else {
stack.removeLast()
active.remove(path.removeAt(path.lastIndex))
}
}
}
return null
}
}
@@ -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.cinematic.model
/** A scene-unique, one-shot signal; loops and beat re-entry never fire it again. */
data class SceneCue(val id: String, val time: Float) {
init {
require(id.isNotBlank() && id.length <= 128) { "Cue id must contain 1 to 128 characters" }
require(time.isFinite() && time >= 0f) { "Cue time must be finite and non-negative" }
}
}
@@ -0,0 +1,42 @@
/*
* 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.cinematic.model
data class SceneGuiTrack(
val actions: List<SceneGuiAction>
) {
init {
require(actions.zipWithNext().all { (a, b) -> a.time <= b.time }) {
"SceneGuiTrack actions must be sorted by time"
}
}
}
data class SceneGuiAction(
val time: Float,
val type: SceneGuiActionType,
val guiId: String,
val args: Map<String, SceneGuiArgValue> = emptyMap()
) {
init {
require(time.isFinite() && time >= 0f) { "SceneGuiAction time must be non-negative, got $time" }
}
}
enum class SceneGuiActionType {
OPEN,
CLOSE,
UPDATE_ARGS
}
sealed class SceneGuiArgValue {
data class Expression(val value: String) : SceneGuiArgValue()
data class StringLiteral(val value: String) : SceneGuiArgValue()
}
@@ -0,0 +1,30 @@
/*
* 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.cinematic.model
import com.cobblemon.mod.common.api.cinematic.model.sources.FovSource
import com.cobblemon.mod.common.api.cinematic.model.sources.Lens
import com.cobblemon.mod.common.api.cinematic.model.sources.PositionSource
import com.cobblemon.mod.common.api.cinematic.model.sources.RollSource
import com.cobblemon.mod.common.api.cinematic.model.sources.RotationSource
import com.cobblemon.mod.common.api.cinematic.model.sources.ShotModifier
data class Shot(
val id: String,
val position: PositionSource,
val rotation: RotationSource,
val fov: FovSource? = null,
val roll: RollSource? = null,
val lens: Lens? = null,
val modifiers: List<ShotModifier> = emptyList()
) {
init {
require(id.isNotBlank()) { "Shot id must be non-blank" }
}
}
@@ -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.cinematic.model
import com.cobblemon.mod.common.api.camera.CameraRotation
import com.cobblemon.mod.common.api.camera.model.PositionExpression
data class StartPose(
val position: PositionExpression,
val rotation: CameraRotation,
val blendDurationSeconds: Float = 0.5f
) {
init {
require(rotation.yaw.isFinite() && rotation.pitch.isFinite()) { "Start pose rotation must be finite" }
require(blendDurationSeconds.isFinite() && blendDurationSeconds >= 0f) { "Start pose blend duration must be finite and nonnegative" }
}
}
/** Unset mode teleports newly visible actors and ignores the start pose for already visible actors. */
enum class StartPoseMode {
TELEPORT,
BLEND,
EXPECT,
IGNORE
}
@@ -0,0 +1,13 @@
/*
* 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.cinematic.model.sources
abstract class FovSource {
abstract val type: String
}
@@ -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.api.cinematic.model.sources
data class Lens(
val aspectCrop: Float? = null,
val letterbox: Float = 0f
) {
init {
aspectCrop?.let {
require(it.isFinite() && it > 0f) { "Lens aspectCrop must be positive, got $it" }
}
require(letterbox >= 0f && letterbox <= 0.5f) {
"Lens letterbox must be in [0, 0.5], got $letterbox"
}
}
}
@@ -0,0 +1,13 @@
/*
* 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.cinematic.model.sources
abstract class PositionSource {
abstract val type: String
}
@@ -0,0 +1,13 @@
/*
* 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.cinematic.model.sources
abstract class RollSource {
abstract val type: String
}
@@ -0,0 +1,13 @@
/*
* 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.cinematic.model.sources
abstract class RotationSource {
abstract val type: String
}
@@ -0,0 +1,13 @@
/*
* 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.cinematic.model.sources
abstract class ShotModifier {
abstract val type: String
}
@@ -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.cinematic.model.sources.builtin
import com.cobblemon.mod.common.api.cinematic.model.sources.ShotModifier
import com.google.gson.JsonObject
import net.minecraft.world.phys.Vec3
data class ConstraintModifier(
val minX: Double? = null, val maxX: Double? = null,
val minY: Double? = null, val maxY: Double? = null,
val minZ: Double? = null, val maxZ: Double? = null
) : ShotModifier() {
override val type: String = TYPE
init {
require(listOfNotNull(minX, maxX, minY, maxY, minZ, maxZ).all { it.isFinite() }) { "Constraint bounds must be finite" }
require(minX == null || maxX == null || minX <= maxX) { "Minimum X must not exceed maximum X" }
require(minY == null || maxY == null || minY <= maxY) { "Minimum Y must not exceed maximum Y" }
require(minZ == null || maxZ == null || minZ <= maxZ) { "Minimum Z must not exceed maximum Z" }
}
fun apply(pos: Vec3): Vec3 = Vec3(
pos.x.let { if (minX != null && it < minX) minX else if (maxX != null && it > maxX) maxX else it },
pos.y.let { if (minY != null && it < minY) minY else if (maxY != null && it > maxY) maxY else it },
pos.z.let { if (minZ != null && it < minZ) minZ else if (maxZ != null && it > maxZ) maxZ else it }
)
companion object {
const val TYPE = "constraint"
fun fromJson(obj: JsonObject): ConstraintModifier = ConstraintModifier(
minX = obj.get("minX")?.asDouble, maxX = obj.get("maxX")?.asDouble,
minY = obj.get("minY")?.asDouble, maxY = obj.get("maxY")?.asDouble,
minZ = obj.get("minZ")?.asDouble, maxZ = obj.get("maxZ")?.asDouble
)
}
}
@@ -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.cinematic.model.sources.builtin
import kotlin.math.cos
import kotlin.math.sin
import com.cobblemon.mod.common.api.cinematic.model.sources.PositionSource
import com.google.gson.JsonObject
import net.minecraft.world.phys.Vec3
data class CranePosition(
val pivot: Vec3,
val armLength: Float,
val startAngleDeg: Float,
val angularVelocityDegPerSec: Float,
val height: Float = 0f
) : PositionSource() {
override val type: String = TYPE
init {
require(armLength.isFinite() && armLength > 0f) { "CranePosition armLength must be finite and positive" }
require(startAngleDeg.isFinite() && angularVelocityDegPerSec.isFinite() && height.isFinite()) {
"CranePosition angles, speed and height must be finite"
}
require(pivot.x.isFinite() && pivot.y.isFinite() && pivot.z.isFinite()) { "CranePosition pivot must be finite" }
}
fun evaluate(time: Float): Vec3 {
val angleRad = Math.toRadians(startAngleDeg + angularVelocityDegPerSec.toDouble() * time)
return Vec3(
pivot.x + armLength * cos(angleRad),
pivot.y + height,
pivot.z + armLength * sin(angleRad)
)
}
companion object {
const val TYPE = "crane"
fun fromJson(obj: JsonObject): CranePosition {
val p = obj.getAsJsonObject("pivot")
return CranePosition(
pivot = Vec3(p.get("x").asDouble, p.get("y").asDouble, p.get("z").asDouble),
armLength = obj.get("armLength").asFloat,
startAngleDeg = obj.get("startAngle")?.asFloat ?: 0f,
angularVelocityDegPerSec = obj.get("angularVelocity").asFloat,
height = obj.get("height")?.asFloat ?: 0f
)
}
}
}
@@ -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.cinematic.model.sources.builtin
import com.cobblemon.mod.common.api.cinematic.model.sources.PositionSource
import com.google.gson.JsonObject
import net.minecraft.world.phys.Vec3
data class DollyPosition(
val from: Vec3,
val to: Vec3,
val duration: Float
) : PositionSource() {
override val type: String = TYPE
init {
require(duration.isFinite() && duration > 0f) { "DollyPosition duration must be finite and positive" }
require(listOf(from.x, from.y, from.z, to.x, to.y, to.z).all { it.isFinite() }) {
"DollyPosition endpoints must be finite"
}
}
fun evaluate(time: Float): Vec3 {
val t = (time / duration).coerceIn(0f, 1f)
return Vec3(
from.x + (to.x - from.x) * t,
from.y + (to.y - from.y) * t,
from.z + (to.z - from.z) * t
)
}
companion object {
const val TYPE = "dolly"
fun fromJson(obj: JsonObject): DollyPosition {
val f = obj.getAsJsonObject("from")
val t = obj.getAsJsonObject("to")
return DollyPosition(
from = Vec3(f.get("x").asDouble, f.get("y").asDouble, f.get("z").asDouble),
to = Vec3(t.get("x").asDouble, t.get("y").asDouble, t.get("z").asDouble),
duration = obj.get("duration").asFloat
)
}
}
}
@@ -0,0 +1,60 @@
/*
* 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.cinematic.model.sources.builtin
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.cinematic.model.sources.FovSource
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.resolveDouble
import com.google.gson.JsonObject
import java.util.concurrent.atomic.AtomicBoolean
data class ExpressionFov(
val expression: ExpressionLike,
val fallbackFov: Float = 70f
) : FovSource() {
override val type: String = "expression"
@Transient private val warningReported = AtomicBoolean()
init {
require(fallbackFov in 1f..179f) { "ExpressionFov fallbackFov must be in [1, 179]" }
}
fun evaluate(runtime: MoLangRuntime): Float {
return try {
// coerceIn does not filter NaN — its comparisons are false either way — so check first.
val raw = runtime.resolveDouble(expression).toFloat()
if (!raw.isFinite()) {
if (warningReported.compareAndSet(false, true)) {
Cobblemon.LOGGER.warn("[ExpressionFov] Non-finite result $raw; using fallback $fallbackFov")
}
fallbackFov
} else {
raw.coerceIn(1f, 179f)
}
} catch (e: Exception) {
if (warningReported.compareAndSet(false, true)) {
Cobblemon.LOGGER.warn("[ExpressionFov] Evaluation failed: ${e.message}; using fallback $fallbackFov", e)
}
fallbackFov
}
}
companion object {
const val TYPE = "expression"
fun fromJson(obj: JsonObject): ExpressionFov {
val exprStr = obj.get("expression")?.asString
?: throw IllegalArgumentException("ExpressionFov missing 'expression' field")
val fallback = obj.get("fallback")?.asFloat ?: 70f
return ExpressionFov(expression = exprStr.asExpressionLike(), fallbackFov = fallback)
}
}
}
@@ -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.cinematic.model.sources.builtin
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.cinematic.model.sources.PositionSource
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.resolveDouble
import com.google.gson.JsonObject
import net.minecraft.world.phys.Vec3
import java.util.concurrent.atomic.AtomicBoolean
data class ExpressionVec3Position(
val xExpression: ExpressionLike,
val yExpression: ExpressionLike,
val zExpression: ExpressionLike,
val fallback: Vec3 = Vec3.ZERO
) : PositionSource() {
override val type: String = "expression_vec3"
@Transient private val warningReported = AtomicBoolean()
init {
require(fallback.x.isFinite() && fallback.y.isFinite() && fallback.z.isFinite()) { "Expression position fallback must be finite" }
}
fun evaluate(runtime: MoLangRuntime?): Vec3 {
if (runtime == null) return fallback
return try {
val x = runtime.resolveDouble(xExpression)
val y = runtime.resolveDouble(yExpression)
val z = runtime.resolveDouble(zExpression)
// MoLang can return NaN or infinity without throwing, so validate before sending transforms.
if (!x.isFinite() || !y.isFinite() || !z.isFinite()) {
if (warningReported.compareAndSet(false, true)) {
Cobblemon.LOGGER.warn("[ExpressionVec3Position] Non-finite result ($x, $y, $z); using fallback $fallback")
}
fallback
} else {
Vec3(x, y, z)
}
} catch (e: Exception) {
if (warningReported.compareAndSet(false, true)) {
Cobblemon.LOGGER.warn("[ExpressionVec3Position] Evaluation failed: ${e.message}; using fallback $fallback", e)
}
fallback
}
}
companion object {
const val TYPE = "expression_vec3"
fun fromJson(obj: JsonObject): ExpressionVec3Position {
val x = obj.get("x")?.asString
?: throw IllegalArgumentException("ExpressionVec3Position missing 'x'")
val y = obj.get("y")?.asString
?: throw IllegalArgumentException("ExpressionVec3Position missing 'y'")
val z = obj.get("z")?.asString
?: throw IllegalArgumentException("ExpressionVec3Position missing 'z'")
val fb = obj.get("fallback")?.asJsonObject
val fallback = if (fb != null) {
Vec3(fb.get("x").asDouble, fb.get("y").asDouble, fb.get("z").asDouble)
} else Vec3.ZERO
return ExpressionVec3Position(
xExpression = x.asExpressionLike(),
yExpression = y.asExpressionLike(),
zExpression = z.asExpressionLike(),
fallback = fallback
)
}
}
}
@@ -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.api.cinematic.model.sources.builtin
import com.cobblemon.mod.common.api.cinematic.model.sources.FovSource
import com.google.gson.JsonObject
data class FixedFov(val fov: Float) : FovSource() {
override val type: String = "fixed"
init {
require(fov in 1f..179f) { "FixedFov must be in [1, 179], got $fov" }
}
companion object {
const val TYPE = "fixed"
fun fromJson(obj: JsonObject): FixedFov = FixedFov(obj.get("value").asFloat)
}
}
@@ -0,0 +1,29 @@
/*
* 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.cinematic.model.sources.builtin
import com.cobblemon.mod.common.api.cinematic.model.sources.PositionSource
import com.google.gson.JsonObject
import net.minecraft.world.phys.Vec3
data class FixedPosition(val position: Vec3) : PositionSource() {
override val type: String = "fixed"
init {
require(position.x.isFinite() && position.y.isFinite() && position.z.isFinite()) { "Fixed position must be finite" }
}
companion object {
const val TYPE = "fixed"
fun fromJson(obj: JsonObject): FixedPosition {
val pos = obj.getAsJsonObject("position") ?: throw IllegalArgumentException("FixedPosition missing 'position'")
return FixedPosition(Vec3(pos.get("x").asDouble, pos.get("y").asDouble, pos.get("z").asDouble))
}
}
}
@@ -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.api.cinematic.model.sources.builtin
import com.cobblemon.mod.common.api.cinematic.model.sources.RollSource
import com.google.gson.JsonObject
data class FixedRoll(val roll: Float) : RollSource() {
override val type: String = "fixed"
init {
require(roll.isFinite()) { "Fixed roll must be finite" }
}
companion object {
const val TYPE = "fixed"
fun fromJson(obj: JsonObject): FixedRoll = FixedRoll(obj.get("value").asFloat)
}
}
@@ -0,0 +1,31 @@
/*
* 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.cinematic.model.sources.builtin
import com.cobblemon.mod.common.api.camera.CameraRotation
import com.cobblemon.mod.common.api.cinematic.model.sources.RotationSource
import com.google.gson.JsonObject
data class FixedRotation(val rotation: CameraRotation) : RotationSource() {
override val type: String = "fixed"
init {
require(rotation.yaw.isFinite() && rotation.pitch.isFinite()) { "Fixed rotation must be finite" }
}
companion object {
const val TYPE = "fixed"
fun fromJson(obj: JsonObject): FixedRotation {
return FixedRotation(CameraRotation(
yaw = obj.get("yaw").asFloat,
pitch = obj.get("pitch").asFloat
))
}
}
}
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.cinematic.model.sources.builtin
import com.cobblemon.mod.common.api.cinematic.model.sources.PositionSource
import com.google.gson.JsonObject
import net.minecraft.world.phys.Vec3
/** Offset is world-space and ignores actor orientation. */
data class FollowActorPosition(
val actorId: String,
val offset: Vec3,
val fallback: Vec3 = Vec3.ZERO
) : PositionSource() {
override val type: String = TYPE
init {
require(listOf(offset.x, offset.y, offset.z, fallback.x, fallback.y, fallback.z).all { it.isFinite() }) {
"FollowActorPosition offset and fallback must be finite"
}
}
companion object {
const val TYPE = "follow_actor"
fun fromJson(obj: JsonObject): FollowActorPosition {
val o = obj.getAsJsonObject("offset")
val fObj = obj.getAsJsonObject("fallback")
return FollowActorPosition(
actorId = obj.get("actorId").asString,
offset = Vec3(o.get("x").asDouble, o.get("y").asDouble, o.get("z").asDouble),
fallback = if (fObj != null) Vec3(fObj.get("x").asDouble, fObj.get("y").asDouble, fObj.get("z").asDouble) else Vec3.ZERO
)
}
}
}
@@ -0,0 +1,53 @@
/*
* 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.cinematic.model.sources.builtin
import com.cobblemon.mod.common.api.cinematic.model.sources.FovSource
import com.cobblemon.mod.common.api.cinematic.model.orderedKeyframes
import com.google.gson.JsonObject
data class KeyframeFov(val keyframes: List<Kf>) : FovSource() {
override val type: String = TYPE
data class Kf(val time: Float, val fov: Float)
init {
keyframes.forEach { require(it.fov in 1f..179f) { "Fov must be in [1, 179], got ${it.fov}" } }
}
@Transient private val playbackKeyframes = orderedKeyframes(keyframes) { it.time }
fun evaluate(time: Float): Float {
val keyframes = playbackKeyframes
if (keyframes.size == 1) return keyframes[0].fov
if (time <= keyframes.first().time) return keyframes.first().fov
if (time >= keyframes.last().time) return keyframes.last().fov
for (i in 0 until keyframes.size - 1) {
val a = keyframes[i]
val b = keyframes[i + 1]
if (time >= a.time && time <= b.time) {
val d = b.time - a.time
val t = if (d > 0f) (time - a.time) / d else 0f
return a.fov + (b.fov - a.fov) * t
}
}
return keyframes.last().fov
}
companion object {
const val TYPE = "keyframe"
fun fromJson(obj: JsonObject): KeyframeFov {
val kfs = obj.getAsJsonArray("keyframes").map { el ->
val k = el.asJsonObject
Kf(time = k.get("time").asFloat, fov = k.get("fov").asFloat)
}
return KeyframeFov(kfs)
}
}
}
@@ -0,0 +1,65 @@
/*
* 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.cinematic.model.sources.builtin
import com.cobblemon.mod.common.api.cinematic.model.sources.PositionSource
import com.cobblemon.mod.common.api.cinematic.model.orderedKeyframes
import com.cobblemon.mod.common.api.tween.EasingRegistry
import com.google.gson.JsonObject
import net.minecraft.world.phys.Vec3
data class KeyframePosition(
val keyframes: List<Kf>,
val defaultEasing: String = "linear"
) : PositionSource() {
override val type: String = "keyframe"
init {
require(keyframes.all { it.position.x.isFinite() && it.position.y.isFinite() && it.position.z.isFinite() }) {
"Keyframe positions must be finite"
}
}
// Keep authoring indices intact; coincident keyframes use the last authored value during playback.
@Transient private val playbackKeyframes = orderedKeyframes(keyframes) { it.time }
data class Kf(val time: Float, val position: Vec3, val easing: String? = null)
fun evaluate(time: Float): Vec3 {
if (time <= playbackKeyframes.first().time) return playbackKeyframes.first().position
for (index in 1 until playbackKeyframes.size) {
val start = playbackKeyframes[index - 1]
val end = playbackKeyframes[index]
if (time <= end.time) {
val progress = ((time - start.time) / (end.time - start.time)).coerceIn(0f, 1f)
val eased = EasingRegistry.getEasingFunction(end.easing ?: defaultEasing)(progress)
return start.position.lerp(end.position, eased.toDouble())
}
}
return playbackKeyframes.last().position
}
companion object {
const val TYPE = "keyframe"
fun fromJson(obj: JsonObject): KeyframePosition {
val kfArray = obj.getAsJsonArray("keyframes") ?: throw IllegalArgumentException("KeyframePosition missing 'keyframes'")
val kfs = kfArray.map { el ->
val kfObj = el.asJsonObject
val pos = kfObj.getAsJsonObject("position")
Kf(
time = kfObj.get("time").asFloat,
position = Vec3(pos.get("x").asDouble, pos.get("y").asDouble, pos.get("z").asDouble),
easing = kfObj.get("easing")?.asString
)
}
val easing = obj.get("easing")?.asString ?: "linear"
return KeyframePosition(kfs, easing)
}
}
}
@@ -0,0 +1,53 @@
/*
* 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.cinematic.model.sources.builtin
import com.cobblemon.mod.common.api.cinematic.model.sources.RollSource
import com.cobblemon.mod.common.api.cinematic.model.orderedKeyframes
import com.google.gson.JsonObject
data class KeyframeRoll(val keyframes: List<Kf>) : RollSource() {
override val type: String = TYPE
data class Kf(val time: Float, val roll: Float)
init {
require(keyframes.all { it.roll.isFinite() }) { "Keyframe roll must be finite" }
}
@Transient private val playbackKeyframes = orderedKeyframes(keyframes) { it.time }
fun evaluate(time: Float): Float {
val keyframes = playbackKeyframes
if (keyframes.size == 1) return keyframes[0].roll
if (time <= keyframes.first().time) return keyframes.first().roll
if (time >= keyframes.last().time) return keyframes.last().roll
for (i in 0 until keyframes.size - 1) {
val a = keyframes[i]
val b = keyframes[i + 1]
if (time >= a.time && time <= b.time) {
val d = b.time - a.time
val t = if (d > 0f) (time - a.time) / d else 0f
return (a.roll + (b.roll.toDouble() - a.roll) * t).toFloat()
}
}
return keyframes.last().roll
}
companion object {
const val TYPE = "keyframe"
fun fromJson(obj: JsonObject): KeyframeRoll {
val kfs = obj.getAsJsonArray("keyframes").map { el ->
val k = el.asJsonObject
Kf(time = k.get("time").asFloat, roll = k.get("roll").asFloat)
}
return KeyframeRoll(kfs)
}
}
}
@@ -0,0 +1,57 @@
/*
* 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.cinematic.model.sources.builtin
import com.cobblemon.mod.common.api.camera.CameraRotation
import com.cobblemon.mod.common.api.cinematic.model.orderedKeyframes
import com.cobblemon.mod.common.api.cinematic.model.sources.RotationSource
import com.google.gson.JsonObject
data class KeyframeRotation(val keyframes: List<Kf>) : RotationSource() {
override val type: String = TYPE
data class Kf(val time: Float, val yaw: Float, val pitch: Float)
init {
require(keyframes.all { it.yaw.isFinite() && it.pitch.isFinite() }) { "Keyframe rotations must be finite" }
}
@Transient private val playbackKeyframes = orderedKeyframes(keyframes) { it.time }
fun evaluate(time: Float): CameraRotation {
val keyframes = playbackKeyframes
if (keyframes.size == 1) return CameraRotation(keyframes[0].yaw, keyframes[0].pitch)
if (time <= keyframes.first().time) return CameraRotation(keyframes.first().yaw, keyframes.first().pitch)
if (time >= keyframes.last().time) return CameraRotation(keyframes.last().yaw, keyframes.last().pitch)
for (i in 0 until keyframes.size - 1) {
val a = keyframes[i]
val b = keyframes[i + 1]
if (time >= a.time && time <= b.time) {
val d = b.time - a.time
val t = if (d > 0f) (time - a.time) / d else 0f
return CameraRotation(
(a.yaw + (b.yaw.toDouble() - a.yaw) * t).toFloat(),
(a.pitch + (b.pitch.toDouble() - a.pitch) * t).toFloat()
)
}
}
return CameraRotation(keyframes.last().yaw, keyframes.last().pitch)
}
companion object {
const val TYPE = "keyframe"
fun fromJson(obj: JsonObject): KeyframeRotation {
val kfs = obj.getAsJsonArray("keyframes").map { el ->
val k = el.asJsonObject
Kf(time = k.get("time").asFloat, yaw = k.get("yaw").asFloat, pitch = k.get("pitch").asFloat)
}
return KeyframeRotation(kfs)
}
}
}
@@ -0,0 +1,47 @@
/*
* 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.cinematic.model.sources.builtin
import com.cobblemon.mod.common.api.camera.CameraRotation
import com.cobblemon.mod.common.api.cinematic.model.sources.RotationSource
import com.google.gson.JsonObject
import net.minecraft.world.phys.Vec3
import kotlin.math.atan2
import kotlin.math.sqrt
data class LookAlongPath(
val epsilon: Float = 1f / 60f
) : RotationSource() {
override val type: String = "look_along_path"
init {
require(epsilon.isFinite() && epsilon > 0f) { "LookAlongPath epsilon must be finite and positive" }
}
fun computeRotation(current: Vec3, ahead: Vec3): CameraRotation {
val dx = ahead.x - current.x
val dy = ahead.y - current.y
val dz = ahead.z - current.z
if (dx * dx + dy * dy + dz * dz < 1e-8) {
// Motion near zero — return forward-facing default to avoid NaN.
return CameraRotation(0f, 0f)
}
val horizontal = sqrt(dx * dx + dz * dz)
val yaw = Math.toDegrees(atan2(-dx, dz)).toFloat()
val pitch = Math.toDegrees(-atan2(dy, horizontal)).toFloat()
return CameraRotation(yaw = yaw, pitch = pitch)
}
companion object {
const val TYPE = "look_along_path"
fun fromJson(obj: JsonObject): LookAlongPath = LookAlongPath(
epsilon = obj.get("epsilon")?.asFloat ?: (1f / 60f)
)
}
}
@@ -0,0 +1,149 @@
/*
* 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.cinematic.model.sources.builtin
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.camera.CameraRotation
import com.cobblemon.mod.common.api.cinematic.model.sources.RotationSource
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.util.asExpressionLike
import com.google.gson.JsonObject
import net.minecraft.world.phys.Vec3
import kotlin.math.atan2
import kotlin.math.sqrt
sealed class LookAtTarget {
data class Position(val pos: Vec3) : LookAtTarget() {
init {
require(pos.x.isFinite() && pos.y.isFinite() && pos.z.isFinite()) { "Look-at position must be finite" }
}
}
data class Actor(val actorId: String) : LookAtTarget() {
init { require(actorId.isNotBlank()) { "Look-at actor id must be non-blank" } }
}
data class Actors(val actorIds: List<String>) : LookAtTarget() {
init {
require(actorIds.size in 1..16)
require(actorIds.all(String::isNotBlank)) { "Look-at actor ids must be non-blank" }
}
}
data class Variable(val prefix: String, val fallback: Vec3 = Vec3.ZERO) : LookAtTarget() {
@Transient private val position = VariableVec3Position(prefix, fallback = fallback)
fun evaluate(runtime: MoLangRuntime?): Vec3 = position.evaluate(runtime)
}
data class Expression(
val xExpression: ExpressionLike,
val yExpression: ExpressionLike,
val zExpression: ExpressionLike,
val fallback: Vec3 = Vec3.ZERO
) : LookAtTarget() {
@Transient private val position = ExpressionVec3Position(xExpression, yExpression, zExpression, fallback)
fun evaluate(runtime: MoLangRuntime?): Vec3 = position.evaluate(runtime)
}
/** Bones resolve client-side; offsets are in world coordinates. */
data class ActorBone(
val actorId: String,
val boneName: String,
val offset: Vec3 = Vec3.ZERO,
val smooth: Boolean = true
) : LookAtTarget() {
init {
require(actorId.isNotBlank()) { "Look-at bone actor id must be non-blank" }
require(boneName.isNotBlank()) { "Look-at bone name must be non-blank" }
require(offset.x.isFinite() && offset.y.isFinite() && offset.z.isFinite()) { "Look-at bone offset must be finite" }
}
}
}
data class LookAt(
val target: LookAtTarget
) : RotationSource() {
override val type: String = "look_at"
fun computeRotation(cameraPos: Vec3, targetPos: Vec3): CameraRotation {
val dx = targetPos.x - cameraPos.x
val dy = targetPos.y - cameraPos.y
val dz = targetPos.z - cameraPos.z
val horizontal = sqrt(dx * dx + dz * dz)
val yaw = Math.toDegrees(atan2(-dx, dz)).toFloat()
val pitch = Math.toDegrees(-atan2(dy, horizontal)).toFloat()
return CameraRotation(yaw = yaw, pitch = pitch)
}
companion object {
const val TYPE = "look_at"
fun fromJson(obj: JsonObject): LookAt {
val targetObj = obj.getAsJsonObject("target")
?: throw IllegalArgumentException("LookAt missing 'target'")
val targetType = targetObj.get("type")?.asString ?: "position"
val target = when (targetType) {
"actors" -> LookAtTarget.Actors(targetObj.getAsJsonArray("actorIds").map { it.asString })
"position" -> LookAtTarget.Position(
Vec3(
targetObj.get("x").asDouble,
targetObj.get("y").asDouble,
targetObj.get("z").asDouble
)
)
"actor" -> LookAtTarget.Actor(
targetObj.get("actorId")?.asString
?: throw IllegalArgumentException("LookAt actor target missing 'actorId'")
)
"actor_bone" -> {
val actorId = targetObj.get("actorId")?.asString
?: throw IllegalArgumentException("LookAt actor_bone target missing 'actorId'")
val boneName = targetObj.get("bone")?.asString
?: throw IllegalArgumentException("LookAt actor_bone target missing 'bone'")
val off = targetObj.getAsJsonObject("offset")
val offset = if (off != null) {
Vec3(off.get("x").asDouble, off.get("y").asDouble, off.get("z").asDouble)
} else Vec3.ZERO
val smooth = targetObj.get("smooth")?.asBoolean ?: true
LookAtTarget.ActorBone(actorId, boneName, offset, smooth)
}
"variable" -> {
val prefix = targetObj.get("prefix")?.asString
?: throw IllegalArgumentException("LookAt variable target missing 'prefix'")
val fb = targetObj.getAsJsonObject("fallback")
val fallback = if (fb != null) {
Vec3(fb.get("x").asDouble, fb.get("y").asDouble, fb.get("z").asDouble)
} else Vec3.ZERO
LookAtTarget.Variable(prefix, fallback)
}
"expression" -> {
val x = targetObj.get("x")?.asString
?: throw IllegalArgumentException("LookAt expression target missing 'x'")
val y = targetObj.get("y")?.asString
?: throw IllegalArgumentException("LookAt expression target missing 'y'")
val z = targetObj.get("z")?.asString
?: throw IllegalArgumentException("LookAt expression target missing 'z'")
val fb = targetObj.getAsJsonObject("fallback")
val fallback = if (fb != null) {
Vec3(fb.get("x").asDouble, fb.get("y").asDouble, fb.get("z").asDouble)
} else Vec3.ZERO
LookAtTarget.Expression(
xExpression = x.asExpressionLike(),
yExpression = y.asExpressionLike(),
zExpression = z.asExpressionLike(),
fallback = fallback
)
}
else -> throw IllegalArgumentException("LookAt target unknown type '$targetType'")
}
return LookAt(target)
}
}
}
@@ -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.cinematic.model.sources.builtin
import com.cobblemon.mod.common.api.cinematic.model.sources.ShotModifier
import com.google.gson.JsonObject
import net.minecraft.world.phys.Vec3
data class OffsetModifier(
val positionOffset: Vec3 = Vec3.ZERO,
val yawOffset: Float = 0f,
val pitchOffset: Float = 0f,
val fovOffset: Float = 0f,
val rollOffset: Float = 0f
) : ShotModifier() {
override val type: String = TYPE
init {
require(positionOffset.x.isFinite() && positionOffset.y.isFinite() && positionOffset.z.isFinite()) { "Position offset must be finite" }
require(listOf(yawOffset, pitchOffset, fovOffset, rollOffset).all { it.isFinite() }) { "Camera offsets must be finite" }
}
companion object {
const val TYPE = "offset"
fun fromJson(obj: JsonObject): OffsetModifier {
val p = obj.getAsJsonObject("position")
val posOff = if (p != null) Vec3(p.get("x").asDouble, p.get("y").asDouble, p.get("z").asDouble) else Vec3.ZERO
return OffsetModifier(
positionOffset = posOff,
yawOffset = obj.get("yaw")?.asFloat ?: 0f,
pitchOffset = obj.get("pitch")?.asFloat ?: 0f,
fovOffset = obj.get("fov")?.asFloat ?: 0f,
rollOffset = obj.get("roll")?.asFloat ?: 0f
)
}
}
}
@@ -0,0 +1,98 @@
/*
* 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.cinematic.model.sources.builtin
import com.cobblemon.mod.common.api.cinematic.model.sources.PositionSource
import com.google.gson.JsonObject
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.AABB
import kotlin.math.max
import kotlin.math.sin
import kotlin.math.sqrt
import kotlin.math.cos
import kotlin.math.atan2
data class OrbitPosition(
val center: Vec3,
val radius: Float,
val angularVelocityDegPerSec: Float,
val startAngleDeg: Float = 0f,
val height: Float = 0f,
val actorIds: List<String> = emptyList(),
val padding: Float = 2f,
val framingFov: Float = 60f,
val bearingActorId: String? = null,
val avoidBlocks: Boolean = true,
val avoidanceSmoothing: Float = 0f,
val fitArena: Boolean = false,
val arenaWidth: Float? = null,
val arenaDepth: Float? = null,
val rememberProgress: Boolean = false
) : PositionSource() {
override val type: String = TYPE
init {
require(center.x.isFinite() && center.y.isFinite() && center.z.isFinite()) { "Orbit center must be finite" }
require(radius > 0f) { "OrbitPosition radius must be positive" }
require(listOf(radius, angularVelocityDegPerSec, startAngleDeg, height, padding, framingFov).all { it.isFinite() })
require(padding >= 0f && framingFov in 10f..120f && actorIds.size <= 16)
require(avoidanceSmoothing.isFinite() && avoidanceSmoothing in 0f..8f)
require(listOfNotNull(arenaWidth, arenaDepth).all { it.isFinite() && it in 2f..128f })
}
val hasArena get() = fitArena && arenaWidth != null && arenaDepth != null
fun focus(bounds: AABB?) = if (hasArena) center else bounds?.center ?: center
fun evaluate(time: Float, bounds: AABB? = null, lensFov: Float = framingFov, bearingPosition: Vec3? = null): Vec3 {
require(time.isFinite()) { "Orbit time must be finite" }
val focus = focus(bounds)
val bearing = bearingPosition?.subtract(focus)?.takeIf { it.horizontalDistanceSqr() > 0.000001 }
?.let { atan2(it.z, it.x) } ?: 0.0
val angleDeg = (startAngleDeg.toDouble() + angularVelocityDegPerSec.toDouble() * time.toDouble()) % 360.0
val angleRad = bearing + Math.toRadians(angleDeg)
if (hasArena) return Vec3(
focus.x + max(radius, arenaWidth!! / 2 + padding) * cos(angleRad),
focus.y + height,
focus.z + max(radius, arenaDepth!! / 2 + padding) * sin(angleRad))
val fitFov = lensFov.takeIf { it.isFinite() }?.coerceIn(1f, 179f) ?: framingFov
val fitRadius = bounds?.let {
sqrt(it.xsize * it.xsize + it.ysize * it.ysize + it.zsize * it.zsize) / 2 / sin(Math.toRadians(fitFov / 2.0)) + padding
} ?: radius.toDouble()
val distance = max(radius.toDouble(), fitRadius)
return Vec3(
focus.x + distance * cos(angleRad),
focus.y + height,
focus.z + distance * sin(angleRad)
)
}
companion object {
const val TYPE = "orbit"
fun fromJson(obj: JsonObject): OrbitPosition {
val c = obj.getAsJsonObject("center")
return OrbitPosition(
center = Vec3(c.get("x").asDouble, c.get("y").asDouble, c.get("z").asDouble),
radius = obj.get("radius").asFloat,
angularVelocityDegPerSec = obj.get("angularVelocity").asFloat,
startAngleDeg = obj.get("startAngle")?.asFloat ?: 0f,
height = obj.get("height")?.asFloat ?: 0f,
actorIds = obj.getAsJsonArray("actorIds")?.map { it.asString } ?: emptyList(),
padding = obj.get("padding")?.asFloat ?: 2f,
framingFov = obj.get("framingFov")?.asFloat ?: 60f,
bearingActorId = obj.get("bearingActorId")?.asString,
avoidBlocks = obj.get("avoidBlocks")?.asBoolean ?: true,
avoidanceSmoothing = obj.get("avoidanceSmoothing")?.asFloat ?: 0f,
fitArena = obj.get("fitArena")?.asBoolean ?: false,
arenaWidth = obj.get("arenaWidth")?.asFloat,
arenaDepth = obj.get("arenaDepth")?.asFloat,
rememberProgress = obj.get("rememberProgress")?.asBoolean ?: false
)
}
}
}
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.cinematic.model.sources.builtin
import kotlin.math.sin
import com.cobblemon.mod.common.api.cinematic.model.sources.RollSource
import com.google.gson.JsonObject
data class ShakeDerivedRoll(
val amplitudeDeg: Float,
val frequencyHz: Float = 8f,
val decaySecs: Float = 0f
) : RollSource() {
override val type: String = "shake"
init {
require(amplitudeDeg.isFinite() && amplitudeDeg >= 0f)
require(frequencyHz in 0f..60f)
require(decaySecs.isFinite() && decaySecs >= 0f)
}
fun evaluate(time: Float): Float {
val decay = if (decaySecs <= 0f) 1f else (1f - (time / decaySecs)).coerceAtLeast(0f)
if (decay <= 0f) return 0f
val t = time.toDouble() * frequencyHz
val n = (sin(1.0 * t) + sin(2.3 * t + 1.3) + sin(3.7 * t + 2.7)) / 3.0
return (n.toFloat() * amplitudeDeg * decay)
}
companion object {
const val TYPE = "shake"
fun fromJson(obj: JsonObject): ShakeDerivedRoll = ShakeDerivedRoll(
amplitudeDeg = obj.get("amplitude").asFloat,
frequencyHz = obj.get("frequency")?.asFloat ?: 8f,
decaySecs = obj.get("decay")?.asFloat ?: 0f
)
}
}
@@ -0,0 +1,53 @@
/*
* 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.cinematic.model.sources.builtin
import kotlin.math.sin
import com.cobblemon.mod.common.api.cinematic.model.sources.ShotModifier
import com.google.gson.JsonObject
data class ShakeModifier(
val positionAmplitude: Float = 0f,
val rotationAmplitudeDeg: Float = 0f,
val frequencyHz: Float = 8f,
val decaySecs: Float = 0f
) : ShotModifier() {
override val type: String = TYPE
init {
require(positionAmplitude.isFinite() && positionAmplitude >= 0f) { "positionAmplitude must be >= 0" }
require(rotationAmplitudeDeg.isFinite() && rotationAmplitudeDeg >= 0f) { "rotationAmplitudeDeg must be >= 0" }
require(frequencyHz in 0f..60f) { "frequency must be in [0, 60] Hz" }
require(decaySecs.isFinite() && decaySecs >= 0f) { "decaySecs must be >= 0" }
}
fun noise3(time: Float): Triple<Float, Float, Float> {
val t = time.toDouble() * frequencyHz
fun s(a: Double, b: Double, c: Double) =
(sin(a * t) + sin(b * t + 1.3) + sin(c * t + 2.7)) / 3.0
return Triple(
s(1.0, 2.3, 3.7).toFloat(),
s(1.1, 2.7, 3.3).toFloat(),
s(0.9, 2.1, 4.1).toFloat()
)
}
fun decayFactor(time: Float): Float =
if (decaySecs <= 0f) 1f else (1f - (time / decaySecs)).coerceAtLeast(0f)
companion object {
const val TYPE = "shake"
fun fromJson(obj: JsonObject): ShakeModifier = ShakeModifier(
positionAmplitude = obj.get("positionAmplitude")?.asFloat ?: 0f,
rotationAmplitudeDeg = obj.get("rotationAmplitude")?.asFloat ?: 0f,
frequencyHz = obj.get("frequency")?.asFloat ?: 8f,
decaySecs = obj.get("decay")?.asFloat ?: 0f
)
}
}
Loaded 100 of 4290 files, more files were not shown because too many files have changed in this diff. Show more