Compare commits

...
Author SHA1 Message Date
Kenji_64 0930721865 Fixed Swampert's weird head_ai 2026-10-19 16:34:31 +02:00
Gitoido Okaski 644ff1772e chore: mudkip animation call fix, resolver orderings streamlined 2026-10-19 16:56:42 +03:00
Kenji_64 3c7e0bebb6 Added Ninetales + alolan placeholder animations 2026-10-19 15:44:42 +02:00
Kenji_64 0c0eea3876 Converted Squawkabilly's poser to JSON and added placeholder animations 2026-10-19 15:44:20 +02:00
ewwww 5535140bb7 vikavolt can be mounted (stat placeholders) 2026-10-19 03:37:39 -04:00
Kenji_64 9b0ca1c16b Made Pichu shut up in its sleep 2026-10-18 18:46:52 +02:00
Kenji_64 052ff59f57 Wave 2 of profile adjustments 2026-10-18 18:46:15 +02:00
Kenji_64 40e5ef1c46 Added Fearow placeholders and fixed its neck (also adjusted Swampert placeholder) 2026-10-18 18:45:31 +02:00
Kenji_64 5025977d23 Added transparency to Vikavolt 2026-10-18 18:44:22 +02:00
Kenji_64 922006afe7 Implemented Magby and Magmar animations 2026-10-18 16:14:45 +02:00
Kenji_64 46cc7cb04d Added placeholder animations to Mudkip, Marshtomp, Swampert and Spearow 2026-10-18 15:48:35 +02:00
Kenji_64 fa3e3bbb1b Scaled down a huge lot of small Pokémon's profiles and fixed some that were off over all 2026-10-18 15:47:46 +02:00
Kenji_64 afbdcce337 Added Eerie Spell activate and Hospitality lang (Hospitality's text does not work properly atm due to a Doubles bug) 2026-10-18 15:46:19 +02:00
whatsy f680682f61 Revert "Rescrape drops"
This reverts commit 6a7d5d2133.
2026-10-17 22:51:02 -04:00
whatsy 4d93d1244d Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-10-17 22:40:25 -04:00
whatsy 6a7d5d2133 Rescrape drops 2026-10-17 22:40:13 -04:00
ewwww 98397cd1da removed igglybuff, jigglypuff, and wigglytuff's rights to float 2026-10-17 21:57:59 -04:00
ewwww fc5a610394 added water shuriken fx 2026-10-17 20:37:00 -04:00
ewwww 504df5d998 sped up cacnea and hippowdon's walk speed 2026-10-17 20:16:26 -04:00
ewwww 3b4d3ebf50 fixed t-posing archeops during air_cry 2026-10-17 20:01:56 -04:00
ewwww 2e460e2ca2 fixed floating frogadier 2026-10-17 19:43:44 -04:00
ewwww 49e4dd0298 helioptile & heliolisk cries 2026-10-17 19:01:46 -04:00
ewwww 120520f51d vikavolt animations + cries for grubbin, charjabug, & vikavolt 2026-10-17 18:01:25 -04:00
ewwww 95e79865e5 updated posers for fennekin & braixen 2026-10-17 16:24:40 -04:00
ewwww 43277d18dc Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-10-17 13:37:05 -04:00
Gitoido Okaski 2b4d9c451c fix: Various poser/animation fixes (Wave 3.4.1) 2026-10-17 20:07:17 +03:00
Gitoido Okaski 7f0e2fa9d8 fix: Various poser/animation fixes (Wave 3.3) 2026-10-17 19:43:00 +03:00
Gitoido Okaski e165095bd5 fix: Various poser/animation fixes (Wave 3.2.1) 2026-10-17 19:38:06 +03:00
Gitoido Okaski b8708d6e10 fix: Various poser/animation fixes (Wave 3.2) 2026-10-17 19:36:25 +03:00
Kenji_64 ef536390ca Various poser/animation fixes (Wave 3.1). The rats make me crazy. 2026-10-17 18:27:28 +02:00
ewwww 28befab173 cries for combee & vespiquen 2026-10-17 12:22:20 -04:00
ewwww c142439d5b cries for wurmple, silcoon, cascoon. beautifly, & dustox 2026-10-17 12:21:49 -04:00
Jerzykv 1ca7335654 Various poser/animation fixes (Wave 3) 2026-10-17 18:18:39 +02:00
ewwww 9197d9cdcd cries for runerigus 2026-10-17 11:52:04 -04:00
ewwww f75813e64d cries for dusknoir 2026-10-17 11:45:44 -04:00
ewwww 1f617c76a4 cries for torkoal 2026-10-17 11:45:20 -04:00
ewwww ebf573aa7a cries for surskit & masquerain 2026-10-17 11:44:29 -04:00
Kenji_64 75363da470 Various poser/animation fixes (Wave 2.5) 2026-10-17 17:42:04 +02:00
ewwww cce4b932cc cries for hoppip, skiploom, & jumpluff 2026-10-17 11:41:45 -04:00
Kenji_64 0973e1ced0 Various poser/animation fixes (Wave 2) 2026-10-17 17:31:30 +02:00
Kenji_64 e7b5c84024 Various poser/animation fixes 2026-10-17 13:43:58 +02:00
ewwww 4001cc2a89 vikavolt animations + cries for grubbin, charjabug, & vikavolt 2026-10-17 06:08:31 -04:00
ewwww a4f204a5a3 changelog 2026-10-16 06:06:11 -04:00
ewwww 31b6be2164 applin & flapple cry updates + cries for appletun & dipplin 2026-10-16 06:06:00 -04:00
ewwww 0dbf73067b houndour & houndoom cries 2026-10-16 06:03:48 -04:00
ewwww 775703e1f5 starly, staravia, and staraptor cries 2026-10-16 06:03:20 -04:00
ewwww 9beee548d4 Merge branch 'main' into implement_pokemon 2026-10-15 01:30:50 -04:00
Polymeta 6f3894f27b fix playermolang sometimes failing when they die
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-14 13:33:40 +01:00
Polymeta bbc9a5e62e Merge branch 'campfire-book-lies' into 'main'
Recipe Book Lies No More

See merge request cable-mc/cobblemon!2487
2026-10-14 11:32:52 +00:00
dhi_holo 8caa82f123 Recipe Book Lies No More
Changed the icon for the misc category
2026-10-14 18:42:58 +10:00
Hiroku 2f87d53af6 Merge remote-tracking branch 'origin/lang-updates' 2026-10-13 17:34:12 +10:00
RedGenesectNinja 546f10eca7 Gave Audino its alpha eyes back 2026-10-12 19:19:05 -05:00
Polymeta 542839ba69 bump neoforge version
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-12 14:36:24 +01:00
Landon efab1bba08 Merge branch 'fix/custom-choice-feature-tab-completion' into 'main'
fix: choice properties must always tab complete

See merge request cable-mc/cobblemon!2483
2026-10-12 08:42:42 +00:00
Landon 8f986bc326 Merge branch 'fix/molang-fn-changes' into 'main'
fix: behavior of `q.pokemon.nature` and `q.entity.is_looking_at`

Closes #2113

See merge request cable-mc/cobblemon!2482
2026-10-12 08:41:14 +00:00
Polymeta e155d32aaf small pokeball fix
Closes #1601

Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-12 08:54:52 +01:00
Polymeta 743e0b7d66 changelog [skip-ci]
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-12 07:49:14 +01:00
Polymeta 1518d703f9 Merge branch 'restore-the-menu' into 'main'
Fixed Mixins for Recipe Book causing conflict issues with other mods.

See merge request cable-mc/cobblemon!2484
2026-10-12 06:44:16 +00:00
Gertimoshka af9d851d67 Fixed Mixins being aggresive, causing massive issues with mod compatibility 2026-10-12 00:17:09 +02:00
Polymeta 20ca91f1c9 fix friendship booster item tag location
Closes #2125

Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-11 21:15:53 +01:00
Polymeta 6fb3c0e49a more evo dex fixes and crashes
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-11 21:12:22 +01:00
Watuneni iteiteandWeblate 5cb4ff5f5f Translated using Weblate (Japanese)
Currently translated at 100.0% (9464 of 9464 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-10-11 15:26:12 +00:00
Gitoido d035c08a5c chore: tidy up imports 2026-10-11 13:43:38 +03:00
Gitoido fda7b13f50 fix: choice properties must always tab complete 2026-10-11 13:41:21 +03:00
Gitoido f595724971 fix: q.entity.is_looking_at now returns proper results based on target passed 2026-10-11 10:55:46 +03:00
Gitoido 0511ba147b fix: use serialized name in q.pokemon.nature molang fn 2026-10-11 10:49:23 +03:00
Polymeta 760f48d850 Merge branch 'evo-tree-fumble' into 'main'
Evo tree now throws a warning instead of crashing when failing to build a mon's evo tree

See merge request cable-mc/cobblemon!2481
2026-10-11 06:41:08 +00:00
lionandPolymeta bf296b4921 Evo tree now throws a warning instead of crashing when failing to build a mon's evo tree 2026-10-11 06:41:08 +00:00
ewwww 6f4aae8b7d grubbin, charjabug, and vikavolt are marked as implemented 2026-10-10 23:33:06 -04:00
ewwww 01e5e74e64 snover and abomasnow are marked as implemented 2026-10-10 23:32:49 -04:00
ewwww b6565a5dee greavard and houndstone are marked as implemented 2026-10-10 23:29:38 -04:00
Hiroku 49886389cf Fine the snapshot can be true again 2026-10-11 12:57:43 +10:00
Hiroku 643bf9068b But what if the snapshot was false 2026-10-11 10:54:55 +10:00
Hiroku 53f3258246 Merge remote-tracking branch 'origin/lang-updates' 2026-10-11 10:53:42 +10:00
Hiroku 1e9895ec84 buncha changelog updates [skip-ci] 2026-10-11 10:49:11 +10:00
Polymeta 52dacb03ae Merge branch 'evo-tree-pls-behave' into 'main'
Evo tree small adjustments

See merge request cable-mc/cobblemon!2479
2026-10-10 23:47:53 +00:00
lionandPolymeta 85b0f3f393 Evo tree small adjustments 2026-10-10 23:47:53 +00:00
Polymeta a04a91bf24 Merge branch 'feature/evolution-mania' into 'main'
tweak some evo requirements

See merge request cable-mc/cobblemon!2476
2026-10-10 21:25:13 +00:00
Polymeta 5e814f7ab5 tweak some evo requirements 2026-10-10 21:25:13 +00:00
Polymeta 2780f491d6 Merge branch 'implement_pokemon' into 'main'
implement_pokemon

See merge request cable-mc/cobblemon!2478
2026-10-10 19:44:59 +00:00
ewwww 2d3ec7aff1 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-10-10 14:51:28 -04:00
ewwww cb7baf2696 changelog 2026-10-10 14:51:12 -04:00
Polymeta 5cc069df78 Merge branch 'implement_pokemon' into 'main'
Dipplin and Hydrapple shiny fixes

See merge request cable-mc/cobblemon!2477
2026-10-10 16:03:26 +00:00
Polymeta 9222476c1f Merge branch 'fix/qa-bundle' into 'main'
fix: an assortment of qa bugs

Closes #2070

See merge request cable-mc/cobblemon!2473
2026-10-10 15:40:51 +00:00
Kenji_64 f220755f99 Dipplin and Hydrapple shiny fixes 2026-10-10 17:31:21 +02:00
Gitoido d55bbf237c chore: move scale changes to other place for now 2026-10-10 18:22:31 +03:00
Watuneni iteiteandWeblate 83a9d44c3a Translated using Weblate (Japanese)
Currently translated at 100.0% (9472 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-10-10 14:22:54 +00:00
Polymeta c5a802eaf6 changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-10 15:04:26 +01:00
Polymeta 2e27875460 Merge branch 'lang-updates' 2026-10-10 15:04:06 +01:00
Polymeta cd66887faa [skip-ci] tidy up changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-10 14:11:50 +01:00
Polymeta d665c87c21 more spawn tweaks
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-10 14:02:26 +01:00
Polymeta 044d3102b6 Merge branch 'fix-evo-tree-crash' into 'main'
Fix grotle evolution crashing evo tree and species features not synching to the client

See merge request cable-mc/cobblemon!2456
2026-10-10 12:51:04 +00:00
lionandPolymeta c421a93b06 Fix grotle evolution crashing evo tree and species features not synching to the client 2026-10-10 12:51:03 +00:00
Polymeta 2865aa2272 Merge branch 'implement_pokemon' into 'main'
poser tweaks + jellicent placeholders

See merge request cable-mc/cobblemon!2474
2026-10-10 12:15:04 +00:00
Polymeta 7c4aae3e9d Merge branch '1.8.2-tag-fixes' into 'main'
Fixes to regional tags.

See merge request cable-mc/cobblemon!2475
2026-10-10 12:14:09 +00:00
Veraxiel d9f671f2a8 Fixes to regional tags. 2026-10-10 07:00:14 -04:00
ewwww 69733b530b poser tweaks + jellicent placeholders 2026-10-10 05:44:57 -04:00
Gitoido d4471c30bc fix: use effective scale instead of scale modifier to get a size category for summary/scanner/molang 2026-10-10 09:38:02 +03:00
Gitoido 185f7e03f5 chore: changelog skip-ci 2026-10-10 09:24:24 +03:00
Gitoido a45eb0eb21 fix: close and open pc gui silently via command 2026-10-10 09:18:30 +03:00
Polymeta ed67142379 Merge branch '1.8.2-tag-fixes' into 'main'
Updates to some tags to fix some errors and add future biome support.

See merge request cable-mc/cobblemon!2472
2026-10-10 05:46:36 +00:00
Gitoido 13169c6848 fix: align oricorio pau form translation keys 2026-10-10 08:35:25 +03:00
Veraxiel 8be134ee2f More fixes. 2026-10-09 21:13:26 -04:00
Veraxiel 8c3206a413 Updates to some tags to fix some errors and add future biome support. 2026-10-09 20:03:32 -04:00
Watuneni iteiteandWeblate 4d4c071b95 Translated using Weblate (Japanese)
Currently translated at 100.0% (9472 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-10-09 23:21:40 +00:00
Polymeta b82c27754f Merge branch 'fix/minior-random-core-color' into 'main'
fix: minior core color should be decided randomly

See merge request cable-mc/cobblemon!2471
2026-10-09 16:57:27 +00:00
Polymeta 63afec184e Merge branch 'implement_pokemon' into 'main'
implement_pokemon

See merge request cable-mc/cobblemon!2463
2026-10-09 16:41:24 +00:00
Gitoido 65699f045e fix: minior core color should be decided randomly 2026-10-09 19:38:34 +03:00
Polymeta d02441a273 Merge branch 'fix/mooshtank-checks' into 'main'
fix: added checks for mooshtank aspect, denying usage of bowl on common miltanks

See merge request cable-mc/cobblemon!2468
2026-10-09 15:25:11 +00:00
Gitoido f76ab784b5 fix: item stack must be copied into lastFlowerFed 2026-10-09 18:06:43 +03:00
Polymeta a566e36754 Merge branch 'fix/tms-consumption-tmm-screen' into 'main'
fix: tm stack not consumed on use in tmm screen

See merge request cable-mc/cobblemon!2466
2026-10-09 14:31:51 +00:00
Polymeta ef1479fde6 Merge branch 'fix/equip-one-not-whole' into 'main'
fix: equipping stackable items now equips one item of stack

See merge request cable-mc/cobblemon!2469
2026-10-09 14:31:02 +00:00
Gitoido d6c7b271e4 chore: flip when condition for readability 2026-10-09 11:11:34 +03:00
Gitoido df9acfa0d1 fix: equipping stackable items now equips one item of stack 2026-10-09 11:03:06 +03:00
Polymeta 9d7fc8435e scraped spawns again and pokemon that are part of herd will also get their specific drops
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-09 08:23:40 +01:00
Gitoido ab4492ac5c fix: forgor clientside level check 2026-10-09 09:43:38 +03:00
Gitoido cb78dc8d88 fix: flower should be consumed when feeding brown mooshtank 2026-10-09 09:42:44 +03:00
Gitoido 020adabe26 fix: added checks for mooshtank aspect, denying usage of bowl on common miltanks 2026-10-09 09:33:03 +03:00
Gitoido 08aca7a274 chore: changelog skip-ci 2026-10-09 09:14:02 +03:00
Gitoido 2a9ef2b9fe fix: increment menu state id on held tm stack click, small code cleanup 2026-10-09 08:39:46 +03:00
JakotensandJakotens 90dc489547 Minor poser fixes for Mime Jr. and slight model adjustments to Mime Jr. and Corsola (Johtonian form only).
Signed-off-by: Jakotens <jjakobdupuyy@gmail.com>
2026-10-08 13:47:42 -06:00
Polymeta 5ec5cfbc4a Merge branch 'fix/bees-grow-apricorns-galarica' into 'main'
Fixed bees not growing Apricorns or the Galarica Nut Bush

Closes #2095

See merge request cable-mc/cobblemon!2427
2026-10-08 14:59:41 +00:00
Polymeta 1ef65449a8 Merge branch 'fix/addon-species-namespaces' into 'main'
Fix crashes with addon-namespace species in evolution, bait and Pokédex lookups

Closes #1353

See merge request cable-mc/cobblemon!2465
2026-10-08 14:56:31 +00:00
Fyre520 c6ff1ea72e Fix crashes with addon-namespace species in evolution, bait and Pokédex lookups 2026-10-08 18:50:53 +05:30
Polymeta cbfa8d3e78 Merge branch 'fix/alpha-chance-config-category' into 'main'
fix: config category for alpha/shiny chance config fields for fossil machine...

See merge request cable-mc/cobblemon!2464
2026-10-08 09:45:34 +00:00
Gitoido 5098ff3cfd fix: config category for alpha/shiny chance config fields for fossil machine and honey log slathering should be Spawning 2026-10-08 12:27:35 +03:00
Polymeta 7c32b07d3f scraped spawns
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-08 08:42:43 +01:00
ewwww b2142fed1d frog tweaks 2026-10-08 03:07:03 -04:00
ewwww fef21516e9 lang 2026-10-08 03:05:45 -04:00
Polymeta 6fba0f55c1 Merge branch 'fix/recall-beam-3rdperson' into 'main'
fix: optimize condition checker for recall beam source position

See merge request cable-mc/cobblemon!2462
2026-10-08 06:48:03 +00:00
ewwww 95d43de79f various minior tweaks 2026-10-08 02:16:38 -04:00
Polymeta 0be617b8ab Merge branch 'fix/1359-sweep-fire-owned-pokemon' into 'main'
Fix sweeping attacks igniting owned Pokémon

Closes #1359

See merge request cable-mc/cobblemon!2460
2026-10-08 06:08:35 +00:00
Polymeta c02d22f449 Merge branch 'fix/1492-edible-medicinal-leek' into 'main'
Fix Medicinal Leek not being edible

Closes #1492

See merge request cable-mc/cobblemon!2459
2026-10-08 06:03:59 +00:00
Polymeta 846b645856 Merge branch 'type-gem-min-growth-fix' into 'main'
fix wrong value in CobblemonConfig for type gem min growth

See merge request cable-mc/cobblemon!2461
2026-10-08 06:01:53 +00:00
whatsyandPolymeta 25d93a771c fix wrong value in CobblemonConfig for type gem min growth 2026-10-08 06:01:53 +00:00
Gitoido f16c782336 fix: optimize condition checker for recall beam source position 2026-10-08 08:56:53 +03:00
shg ed0f315e41 Fix sweeping attacks igniting owned Pokémon
Closes #1359
2026-10-08 00:42:28 +00:00
shg d20b03f4c5 Fix Medicinal Leek not being edible
Closes #1492
2026-10-08 00:42:25 +00:00
Polymeta a3920413ac fix TMM UI not closing when supposed to 2026-10-07 21:48:28 +00:00
ewwww 92bccf620f Merge branch 'main' into implement_pokemon 2026-10-07 15:40:14 -04:00
ewwww 15f8a9ee71 Merge branch 'main' into implement_pokemon 2026-10-07 15:39:42 -04:00
Polymeta dfb9ee755e Merge branch 'Slippy-1.8.2-Fixes' into 'main'
Slippy 1.8.2 fixes

See merge request cable-mc/cobblemon!2458
2026-10-07 17:25:48 +00:00
Slippy 68f4117e29 Focus Sash changed to focus band in ruin sus loot 2026-10-07 12:09:05 -05:00
Slippy f74a24a056 Console Log Spam Fix 2026-10-07 12:06:19 -05:00
ewwww e5750cbba0 lang fixes for groudon, kyogre, and oricorio 2026-10-07 12:58:20 -04:00
ewwww c6482c82cd various minior tweaks 2026-10-07 12:57:06 -04:00
Slippy b4c23a9132 Alpha Loot Table nerfed 2026-10-07 11:34:35 -05:00
Kenji_64 c470894bd3 Fixed Politoed's hitbox 2026-10-07 15:29:52 +02:00
Kenji_64 a940e32f8d Fixed a texture error with Shiny Greninja 2026-10-07 15:29:21 +02:00
ewwww b6b59eccad vivillon dex tweak 2026-10-07 06:52:25 -04:00
ewwww b520b94fae minior lang fixes + tweaks to form & dex data 2026-10-07 06:52:16 -04:00
Polymeta 9af99d16f7 Merge branch 'feature/the-center-of-attention' into 'main'
constraining should start from center of spawn area

Closes #2119

See merge request cable-mc/cobblemon!2457
2026-10-07 07:17:30 +00:00
Polymeta 2cc17d3280 constraining should start from center of spawn area 2026-10-07 07:17:30 +00:00
Polymeta 20b2bcaa5a Merge branch 'implement_pokemon' into 'main'
Implement pokemon

See merge request cable-mc/cobblemon!2455
2026-10-06 11:47:36 +00:00
Kenji_64 d1ada605fd Fixed Alolan Bias Pichu resolver issues (again) 2026-10-06 13:20:30 +02:00
Polymeta d37f73626d Merge branch 'type-gem-growth-rework' into 'main'
Type gem growth rework

See merge request cable-mc/cobblemon!2451
2026-10-06 07:07:32 +00:00
Polymeta 73a94cb02e Merge branch 'burmy-cloak-change' into 'implement_pokemon'
Burmy Cloak Changing & Battle Ended Event Refinements

See merge request cable-mc/cobblemon!2452
2026-10-06 07:04:32 +00:00
Polymeta 5a48945e50 Merge branch 'fix/particles-respecting-graphics-setting' into 'main'
fix: particle density should respect minecraft particle density settings

See merge request cable-mc/cobblemon!2446
2026-10-06 05:49:29 +00:00
Polymeta 476448a2ce Merge branch 'more-block-noises' into 'main'
More more item blocks sounds

See merge request cable-mc/cobblemon!2450
2026-10-06 05:48:55 +00:00
Polymeta bcba4f7fb7 Merge branch 'implement_pokemon' into 'main'
implement_pokemon

See merge request cable-mc/cobblemon!2445
2026-10-06 05:47:25 +00:00
Konoha 20e9608ff3 cleanup: biome tag overlaps and priority 2026-10-05 22:08:49 -04:00
ewwww 3e5733ceaf GREAVARD EMISSIVES 2026-10-05 19:29:25 -04:00
ewwww af29c99b03 properly defined vivillon's pattern forms as forms viewable in the pokedex with form names and dex entries 2026-10-05 18:42:09 -04:00
ewwww 60d596a24f riolu + lucario remodels 2026-10-05 17:14:24 -04:00
Konoha 3f993e0466 feature: Burmy cloak changing (and battle event refinements) 2026-10-05 12:21:18 -04:00
whatsy 9e3d22ee54 default type gem cluster growth type is decoration, so porting doesn't give people an infinite gem block glitch 2026-10-05 11:04:59 -04:00
whatsy e86cb1981f Merge remote-tracking branch 'origin/type-gem-growth-rework' into type-gem-growth-rework 2026-10-05 10:55:16 -04:00
jackowesandwhatsy e94f265adb Add datafixer for type gems. Also ensure the fix runs even when mc version stays the same 2026-10-05 10:55:00 -04:00
whatsy b3fc35240a no i didnt forget the changelog 2026-10-05 10:55:00 -04:00
whatsy d74618bddb basically finalized 2026-10-05 10:55:00 -04:00
whatsy 8b54a8d459 moved stuff to config + more adjustments 2026-10-05 10:55:00 -04:00
whatsy c17104a0fd Basics for the type gem growth rework 2026-10-05 10:55:00 -04:00
Sterrezo 88ca3ebbf3 Added item block sound sets for glass jars with liquid 2026-10-05 15:14:53 +02:00
ewwww 4003456e5c slakoth animations + minor model tweaks 2026-10-05 05:41:06 -04:00
ewwww 1d21a52d28 shinx, luxio, and luxray remodels 2026-10-05 05:38:23 -04:00
ewwww 2d49c62f85 froakie, frogadier, greninja, & ash-greninja remodels + greninja animations 2026-10-05 05:38:23 -04:00
Polymeta 20fac724f6 Merge branch 'testing-fixes' into 'main'
Small Fixes

See merge request cable-mc/cobblemon!2448
2026-10-05 07:02:45 +00:00
dhi_holoandPolymeta 0807d73646 Small Fixes 2026-10-05 07:02:45 +00:00
Jackson Bowes 3013302f35 Merge branch 'drained' into 'main'
Mega Doinked

See merge request cable-mc/cobblemon!2447
2026-10-04 19:22:42 -05:00
dhi_holo 879e7bef21 Mega Doinked
Removes Mega Drain as a TM, due to only being learnt by a single line of Pokemon
2026-10-05 10:04:29 +10:00
jackowes 557623dfe2 Add datafixer for type gems. Also ensure the fix runs even when mc version stays the same 2026-10-04 15:01:49 -05:00
Gitoido a28d1e176d chore: remove log spam skip-ci 2026-10-04 18:24:18 +03:00
Gitoido f3c52db70d chore: changelog + unused imports cleanup skip-ci 2026-10-04 18:22:22 +03:00
Gitoido 525575b0ba fix: particle density should respect minecraft particle density settings 2026-10-04 18:10:54 +03:00
Sterrezo a55efeb378 Added metal can item block sounds, fixed missing sound implementation for a bunch of other item block sounds 2026-10-04 15:44:04 +02:00
Kenji_64 da18b082a6 Fixed Alolan Bias Pichu and Pikachu referring to the wrong models and removed the old ones 2026-10-04 12:49:46 +02:00
Polymeta 7511facebc Merge branch 'decorative-items-for-all' into 'main'
More Decorative Items

See merge request cable-mc/cobblemon!2430
2026-10-04 06:58:14 +00:00
dhi_holo 1486f5cfc2 Merge branch 'main' into decorative-items-for-all 2026-10-04 12:43:57 +10:00
ewwww f8a16e5081 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-10-03 22:23:06 -04:00
dhi_holo 558e2d89ff Mipmap Fix
shhhh, I did it the cheap and nasty way rather than tweaking the models, it's for the greater good though
2026-10-04 12:01:29 +10:00
dhi_holo ea7b688655 Asset Updates 2026-10-04 11:35:22 +10:00
Polymeta fdafa3435c Merge branch 'flora-heals-an-aching-soul-' into 'main'
Flora Update

See merge request cable-mc/cobblemon!2412
2026-10-04 00:02:50 +00:00
dhi_holo f17c90f6ea Changelog
(also fixed some issues from the merge conflicts I missed)
2026-10-04 09:14:54 +10:00
dhi_holo 1dc45669bb Recipe Updates the Third 2026-10-04 09:14:12 +10:00
dhi_holo f3658614a4 Merge branch 'main' into decorative-items-for-all 2026-10-04 08:35:05 +10:00
Polymeta cad39d8802 Merge branch 'form-move-fix' into 'implement_pokemon'
Form Move Fix

See merge request cable-mc/cobblemon!2420
2026-10-03 22:33:27 +00:00
Polymeta 422f60f587 Merge branch 'feat/tm-creative-tab' into 'main'
Add Technical Machines creative tab

See merge request cable-mc/cobblemon!2443
2026-10-03 22:31:41 +00:00
dhi_holo 8190ddf0e9 Merge branch 'main' into flora-heals-an-aching-soul- 2026-10-04 08:31:26 +10:00
dhi_holo af281d0bde Pa'uvillea 2026-10-04 07:57:39 +10:00
Kenji_64 00905b8c32 Oops, fixed Pikachu's walk 2026-10-03 21:54:11 +02:00
ewwww 79b3cb8611 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-10-03 15:47:45 -04:00
ewwww 797e908621 restored placeholder animations for chespin and quilladin 2026-10-03 15:47:40 -04:00
Kenji_64 3df3c395de Cap Animations for the entire Pikachu line, updated Raichu animations, added faint to Pikachu and 1 new cosmetic for Pikachu!!! 2026-10-03 21:25:11 +02:00
whatsy b2f682ebb1 no i didnt forget the changelog 2026-10-03 15:09:15 -04:00
Gitoido 754e9bc158 chore: changelog skip_ci 2026-10-03 20:48:36 +03:00
Gitoido 2df8504139 feat: lore correct sorting 2026-10-03 20:35:44 +03:00
Gitoido 2838f97c56 feat: add Technical Machines dynamic creative tab 2026-10-03 19:55:32 +03:00
Gitoido 68c75deeeb feat: Technical Machines creative tab i18n 2026-10-03 19:55:04 +03:00
whatsy 3bc2659740 basically finalized 2026-10-02 22:42:57 -04:00
Konoha 89bba8a67a Quick fix changelog and sneaky Spinda fix 2026-10-02 20:51:52 -04:00
whatsy cbd5cdaf92 moved stuff to config + more adjustments 2026-10-02 14:01:04 -04:00
Polymeta 5b9d5cbb55 [skip-ci] tidy up changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-02 08:34:22 +01:00
Jerzykv 6d6e13be83 Changelog 2026-10-02 09:29:46 +02:00
Jerzykv 899bec77b6 Implemented Fennekin, Braixen and Delphox remodels 2026-10-02 09:28:58 +02:00
Jerzykv 6dd8325772 Implemented Chespin, Quilladin and Chesnaught remodels 2026-10-02 09:28:39 +02:00
dhi_holo e9bffd3ad7 Minty Fresh
Updated Mint Leaf Litter to use a texture closer to Vanilla to make the tiling nicer
2026-10-02 17:18:54 +10:00
Polymeta ff890307c2 Merge branch 'feature/change-the-rules' into 'main'
change how item interaction evolutions are processed

Closes #1864 and #1874

See merge request cable-mc/cobblemon!2399
2026-10-02 07:07:29 +00:00
Polymeta dda9d3e790 [skip-ci] tidy up changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-10-02 08:02:57 +01:00
Polymeta 1acc02a2ec Merge branch 'more-block-noises' into 'main'
Added sounds for liquid bottle, pill bottle and dice blocks

See merge request cable-mc/cobblemon!2441
2026-10-02 06:56:17 +00:00
dhi_holo 460c472c55 Asset Updates
Approved assets for all Terrain Plants, Mint Leaf Litter, and three of the nectar flowers (at my own fault). Potted plants already match approval
2026-10-02 16:09:11 +10:00
dhi_holo 69c3b6142d Merge branch 'main' into flora-heals-an-aching-soul- 2026-10-02 14:56:06 +10:00
Jackson Bowes 9a036a4c01 Merge branch 'implement_pokemon' into 'main'
implement_pokemon

See merge request cable-mc/cobblemon!2439
2026-10-01 22:48:51 -05:00
whatsy 0fbe206893 Basics for the type gem growth rework 2026-10-01 23:46:18 -04:00
Sterrezo 3cea6dd175 Forgot powders 2026-10-01 19:03:52 +02:00
Sterrezo 2fa053f833 Update CHANGELOG.md 2026-10-01 18:55:35 +02:00
Sterrezo ea4f858919 Added sounds for liquid bottle, pill bottle and dice blocks 2026-10-01 18:53:06 +02:00
Sterrezo 2aa4ab4b52 Added sounds to bright powder illuminating signs 2026-10-01 17:40:13 +02:00
Gitoido 113977990e feat: helper functions to register plants and pots 2026-10-01 17:22:09 +03:00
Polymeta d86021e2a1 Merge branch 'fix/safari-ball-catchrate' into 'main'
fix: change safari ball catchrate modifier

See merge request cable-mc/cobblemon!2440
2026-10-01 05:26:45 +00:00
JakotensandJakotens 79faadef59 Slight texture updates to Sudowoodo.
Signed-off-by: Jakotens <jjakobdupuyy@gmail.com>
2026-09-30 19:54:48 -06:00
Gitoido 9b6629db7f chore: update changelog skip-ci 2026-10-01 02:47:45 +03:00
Gitoido d151a1ce65 fix: change safari ball catchrate modifier
Changed safari ball catchrate modifier to properly account for battle status
2026-10-01 02:44:20 +03:00
ewwww d9ca7b483d tweaked sudowoodo textures 2026-09-30 19:40:29 -04:00
Instrafe 708f7fc1b8 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-09-30 18:59:11 -04:00
Instrafe 621db85845 The evolution knows where it is because it knows where it isn't 2026-09-30 18:59:01 -04:00
Instrafe b09066c580 Added negate tags to regional and Vivillon evolutions to prevent biome overlap 2026-09-30 18:58:23 -04:00
ewwww 7c45f28827 ekans remodel + animations 2026-09-30 13:39:45 -04:00
ewwww 9c1b2cae22 vikavolt fixed placeholder animations 2026-09-30 08:59:25 -04:00
ewwww 16b2f81bd4 arbok animations 2026-09-30 08:57:47 -04:00
YasitoandWeblate 1969af65b9 Translated using Weblate (French (Canada))
Currently translated at 95.1% (9016 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/fr_CA/
2026-09-30 09:25:23 +00:00
dhi_holo 3bd3ce5688 Delete wisteria_root_side.json 2026-09-30 10:58:27 +10:00
Jackson Bowes 108f0e3d88 Merge branch 'pokemon-return-update' into 'main'
Pokeball Recall and Capture FX Improvement

See merge request cable-mc/cobblemon!2317
2026-09-29 19:44:15 -05:00
Plastered_CrabandJackson Bowes 4344c127eb Pokeball Recall and Capture FX Improvement 2026-09-29 19:44:15 -05:00
dhi_holo 881a87216b Merge branch 'main' into flora-heals-an-aching-soul- 2026-09-30 10:11:19 +10:00
dhi_holo 1d473b82af Refactor
Renamed Voltconia to Voltaconia, and also fixed the models being in a temp folder for easier file editing because that's not needed anymore
2026-09-30 10:10:55 +10:00
Konoha 6e0c005587 Changelog 2026-09-29 16:25:13 -04:00
Polymeta 5955547c6f Merge branch 'evolution-tree' into 'main'
Evolution tree

See merge request cable-mc/cobblemon!2385
2026-09-29 10:14:09 +00:00
lionandPolymeta 7a3a1cb8de Evolution tree 2026-09-29 10:14:09 +00:00
Polymeta 1674cb0913 fix typo
Closes #2115
2026-09-29 06:44:27 +00:00
Polymeta 152ce4e923 Merge branch 'shoulder-mount-armor-clipping' into 'main'
Shoulder Mount Enhancements

See merge request cable-mc/cobblemon!2410
2026-09-29 06:42:36 +00:00
BreezyashaandPolymeta 3a6d024217 Shoulder Mount Enhancements 2026-09-29 06:42:36 +00:00
Fantastic-Fanta 3b722068b0 Fixed bees not growing Apricorns or the Galarica Nut Bush
Bonemealable blocks in the bee growables tag are now grown through performBonemeal, with the nectar and particle updates vanilla skips.

Closes #2095
2026-09-29 11:25:59 +08:00
Polymeta f2fcc5ba79 Merge branch 'implement_pokemon' into 'main'
implement_pokemon

Closes #2106

See merge request cable-mc/cobblemon!2428
2026-09-28 20:25:03 +00:00
RedGenesectNinja db0fb8e287 Added implemented flag to Tympole, Palpitoad and Seismitoad 2026-09-28 15:06:55 -05:00
Jerzykv dae36a67d1 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-09-28 19:11:43 +02:00
JakotensandJerzykv 6f99862288 Fixed some small texture issues on Pidgeot.
Signed-off-by: Jakotens <jjakobdupuyy@gmail.com>
2026-09-28 19:11:27 +02:00
Jerzykv 0377747e1e implemented flag 2026-09-28 19:11:26 +02:00
Polymeta 6bc974f90f Merge branch 'keyframe-super-ghostbusters' into 'main'
Fix for doubled effect keyframes

Closes #2107

See merge request cable-mc/cobblemon!2436
2026-09-28 13:14:51 +00:00
Sterrezo aca53de259 Update CHANGELOG.md 2026-09-28 14:50:24 +02:00
Sterrezo 41bde38727 Fixed effect keyframes sometimes getting doubled probably 2026-09-28 14:49:06 +02:00
Polymeta a4b7ee3b38 follow-up fix from registry change
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-28 09:01:58 +01:00
JakotensandJakotens 154b653c57 Fixed some small texture issues on Pidgeot.
Signed-off-by: Jakotens <jjakobdupuyy@gmail.com>
2026-09-27 17:33:09 -06:00
Jerzykv 04489bc969 changelog 2026-09-27 12:39:00 +02:00
Jerzykv 25499b3e7a Fixed #2106 2026-09-27 12:37:49 +02:00
Instrafe bb6bda54dc Updated Changelog 2026-09-26 14:17:30 -04:00
Instrafe 9f3dbdc2d8 Implemented remodel for Shroomish 2026-09-26 14:13:33 -04:00
JakotensandJakotens 932148338c Added animations for Treecko and Bonsly. Added remodel and animations for Sudowoodo.
Signed-off-by: Jakotens <jjakobdupuyy@gmail.com>
2026-09-25 23:05:21 -06:00
dhi_holo e0d0c38232 Merge branch 'main' into decorative-items-for-all 2026-09-25 15:02:09 +10:00
Polymeta a0e732dc6e Merge branch 'perf/build-speed' into 'main'
Speed up the Gradle build

See merge request cable-mc/cobblemon!2429
2026-09-24 14:35:25 +00:00
Jerzykv 309f674853 Implemented Minior 2026-09-24 13:38:30 +02:00
terrainwax ea20c2aa4c CI: don't upload dev-shadow jars 2026-09-24 10:43:07 +02:00
terrainwax da5272c473 Speed up the Gradle build
- Exclude assets/data (~250MB) from sources jars
- Limit licenser to .kt/.java so it doesn't scan ~29k resource files
- Enable the Gradle daemon and build cache
- The versioning plugin runs a full JGit status (~28s on this repo) in every project replace by ou own helper that use git local or CI env variable
2026-09-24 10:04:35 +02:00
Jerzykv dc705c7720 Fixed Alolan Diglett's cry 2026-09-24 09:48:23 +02:00
Polymeta a785e574e0 Merge branch 'feature/data-registries' into 'main'
26.1.2 backport of dataregistry

See merge request cable-mc/cobblemon!2285
2026-09-22 21:08:14 +00:00
Polymeta 4b236b0268 Merge branch 'feature/remember-your-tm' into 'main'
load features a lot sooner to avoid form switching

Closes #2085

See merge request cable-mc/cobblemon!2406
2026-09-22 21:07:44 +00:00
Polymeta 1cd86ae5d6 small fixes
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-22 16:43:33 +01:00
Polymeta b7489a0b14 tidy up changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-22 15:32:25 +01:00
Polymeta f7554425dc Merge branch 'tm-shelf-mipmap-correction' into 'main'
Fix mip map of TM shelf disc textures to be 16x16

Closes #2081

See merge request cable-mc/cobblemon!2398
2026-09-22 14:26:41 +00:00
ewwww 9bfc577444 oddish, gloom, vileplume, and bellossom remodels + animations 2026-09-22 04:35:07 -04:00
dhi_holo 217d4cda42 Update CHANGELOG.md 2026-09-22 10:44:12 +10:00
dhi_holo 08daa43aae Update blank_tm.json 2026-09-22 10:41:01 +10:00
dhi_holo 184d889831 TM Boost 2026-09-22 10:36:59 +10:00
dhi_holo c6e0f431ae Update never_melt_ice.json 2026-09-22 10:31:59 +10:00
dhi_holo 93daeab840 Merge branch 'main' into decorative-items-for-all 2026-09-22 10:16:29 +10:00
dhi_holo f132b297c6 Never-Melted
Recipe Update
2026-09-22 10:16:17 +10:00
Polymeta 858d2e908a I tidied up too well
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 22:12:18 +01:00
Polymeta b61e0b0c82 tidy up changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 22:11:43 +01:00
Polymeta 4d9a6e71c9 use correct texture for upgrade and dubious disc
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 22:07:52 +01:00
Plastered_Crab a0010de504 Merge remote-tracking branch 'origin/tm-shelf-mipmap-correction' into tm-shelf-mipmap-correction
# Conflicts:
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/dubious_disc.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_11.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_13.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_5.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_blocks.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_cat.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_chirp.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_creator.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_creator_music_box.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_far.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_mall.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_mellohi.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_otherside.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_pigstep.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_precipice.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_relic.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_stal.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_strad.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_unknown.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_wait.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/music_disc_ward.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/technical_machine.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/technical_machine_base.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/technical_machine_overlay.png
#	common/src/main/resources/assets/cobblemon/textures/block/disc_shelf/upgrade.png
2026-09-21 10:34:36 -07:00
Plastered_Crab cb73a30e23 changeloguwu 2026-09-21 10:18:50 -07:00
SultanandWeblate 905d63e098 Translated using Weblate (Russian)
Currently translated at 85.6% (8110 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-09-21 16:38:11 +00:00
Nico MerenqandWeblate 1ea8ab7dcc Translated using Weblate (Russian)
Currently translated at 85.6% (8110 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-09-21 16:38:11 +00:00
geumuandWeblate a45d21b279 Translated using Weblate (Korean)
Currently translated at 99.6% (9436 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ko/
2026-09-21 16:38:10 +00:00
ewwww 3ca6d350fe these were forgotten 2026-09-21 11:40:14 -04:00
Instrafe 2997ce8457 Updated Changelog 2026-09-21 09:54:23 -04:00
Instrafe ff279d8310 Poliwag and Politoed now have remodels + Implemented gender differences for Politoed 2026-09-21 09:53:53 -04:00
Polymeta 9a7536b068 you were not supposed to be there
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 14:42:23 +01:00
Polymeta bd973e702e per type texture for disc shelf tms
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 14:40:24 +01:00
Plastered_CrabandPolymeta ede1851d34 Fix mip map of TM shelf disc textures to be 16x16 2026-09-21 14:28:14 +01:00
Polymeta cdb8ee0d8e [skip-ci] tidy up changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 12:07:55 +01:00
Polymeta e7d86a382c Merge branch 'dex_molang' into 'main'
Molang: Individual Dex molang

See merge request cable-mc/cobblemon!2273
2026-09-21 09:16:10 +00:00
Konoha DandPolymeta eb911df830 Molang: Individual Dex molang 2026-09-21 09:16:10 +00:00
Polymeta 1c30acc53c Merge branch 'implement_pokemon' into 'main'
implement_pokemon

See merge request cable-mc/cobblemon!2417
2026-09-21 09:14:23 +00:00
Polymeta 32b0699235 load features a lot sooner to avoid form switching
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 10:13:59 +01:00
Polymeta e9c6580230 fix merge conflicts
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 10:13:27 +01:00
Polymeta f893eb48fd fix some errors I forgot about
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 09:38:51 +01:00
Polymeta 232e2f18bf MR feedback
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 09:38:51 +01:00
Polymeta c266c99710 26.1.2 backport of dataregistry
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 09:38:49 +01:00
Polymeta 2a0e8f6d47 [skip-ci] tidy up changelog
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 09:32:44 +01:00
Polymeta e6bd8ec529 Merge branch 'neoforge-reload-fix' into 'main'
Neoforge resource fix

See merge request cable-mc/cobblemon!2333
2026-09-21 08:30:54 +00:00
CraterandPolymeta 911a95e873 Neoforge resource fix 2026-09-21 08:30:53 +00:00
Polymeta 447cfc2ae5 Merge branch 'useful-misc-functions' into 'main'
Smoother ride rotations.

See merge request cable-mc/cobblemon!2407
2026-09-21 08:28:50 +00:00
Polymeta 8dfeb0d260 Merge branch 'no-glass-bottles-in-those' into 'main'
Fixed Mochis and Feathers dropping glass bottles when they shouldn't

See merge request cable-mc/cobblemon!2425
2026-09-21 08:24:28 +00:00
Polymeta caa7b19833 Merge branch 'Snowy-Burrow-Spawns-Fix' into 'main'
Fix Snowy burrows spawns and some floating water in one difting iceberg

See merge request cable-mc/cobblemon!2423
2026-09-21 08:23:17 +00:00
Polymeta 91d6fb62ba dedicated handling for forced evolutions, those take priority and only one at a time
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 09:18:06 +01:00
Polymeta 8f4a5b2015 fix typo in changelog 2026-09-21 09:18:06 +01:00
Polymeta 07604c9a3b change how item interaction evolutions are processed
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 09:18:06 +01:00
Polymeta 64dd8ae193 fix license header
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 09:17:44 +01:00
Polymeta 156ee6f290 new negate requirement
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-21 08:23:04 +01:00
dhi_holo c0a524a718 Jars and Generators and a forgotten Pouch
6 new items ready to decorate your worlds
2026-09-21 11:58:11 +10:00
Gertimoshka fe433d1417 Fixed Mochis and Feathers dropping glass bottles when they shouldn't 2026-09-20 20:10:25 +02:00
geumuandWeblate efe8f34413 Translated using Weblate (Korean)
Currently translated at 99.6% (9436 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ko/
2026-09-20 13:11:32 +00:00
Hiroku 0dc662d295 Merge branch 'starter-screen-fixes' into 'main'
Fixes minor but annoying starter screen fixes

See merge request cable-mc/cobblemon!2424
2026-09-20 09:33:15 +00:00
Gertimoshka 081e370bfd User friendlier changelog 2026-09-20 11:13:08 +02:00
Gertimoshka 3ef0567287 simplifications 2026-09-20 11:05:26 +02:00
Gertimoshka 3cad9e5481 Forgor the Changelog 2026-09-20 11:01:52 +02:00
Gertimoshka dc8b7908be Fixed impropper rendering culling of entries, and random starter not being part of height calculation of the category 2026-09-20 10:52:15 +02:00
Slippy 85085a1eca Decaying Crypt tombstones now will generate with TMs in the gidled chest 2026-09-19 22:40:20 -05:00
Slippy bff59dee6e Fix Snowy burrows spawns and some floating water in one difting iceberg 2026-09-19 22:01:17 -05:00
Hiroku bb5828531a Merge branch 'type-gem-fix' into 'main'
Fix typo and update changelog.

See merge request cable-mc/cobblemon!2422
2026-09-20 02:29:36 +00:00
Deltric cd08e98c28 Fix typo and update changelog. 2026-09-19 21:09:32 -05:00
Nico MerenqandWeblate 6bb23582c4 Translated using Weblate (Russian)
Currently translated at 85.5% (8102 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-09-19 04:42:37 +00:00
SultanandWeblate 56ccb43997 Translated using Weblate (Russian)
Currently translated at 85.5% (8102 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ru/
2026-09-19 04:42:36 +00:00
Polymeta bf7d40c9b5 Merge branch 'small-jvm-fix' into 'main'
Added JvmField to Some values in Cobblemon Blocks cause it was forgotten

See merge request cable-mc/cobblemon!2419
2026-09-18 18:30:24 +00:00
Polymeta 5abb3d7f97 Merge branch 'fix/molang-delete-variables-fn' into 'main'
fix: reintroduce `delete_variables` general molang fn

See merge request cable-mc/cobblemon!2421
2026-09-18 18:29:23 +00:00
Gitoido 975ad8186a chore: dangling variable 2026-09-18 18:03:41 +00:00
Gitoido 26b6419f04 fix: reintroduce delete_variables general molang fn 2026-09-18 18:00:44 +00:00
Jerzykv 331f7ca425 Implemented Burmy, Wormadam and Mothim 2026-09-18 07:15:33 +02:00
Konoha f445c05333 fix: Quick-fix For #updateMovesOnFormChange 2026-09-17 17:38:02 -04:00
RedGenesectNinja 71c9ac0ff0 Updated changelog 2026-09-17 15:15:20 -05:00
GenotypeXD 1404cc0ab5 Sun Lizards Implementation Tags 2026-09-17 15:11:31 -05:00
Konoha a62485fc1f Fix Calyrex form moves 2026-09-17 15:14:27 -04:00
Konoha 044f3080ff Fix Hoopa form moves 2026-09-17 15:09:43 -04:00
Konoha 28b0cab906 Adjust Form Change moves for Kyurem's level-gated ones. 2026-09-17 14:35:15 -04:00
Konoha 82f34d651c Add Kyurem's form moves 2026-09-17 14:32:47 -04:00
GenotypeXD 6ca908a91d Implemented Helioptile Line 2026-09-17 13:05:12 -05:00
Jerzykv 0948b8da9a Fixed Dragonair not using its idle correctly 2026-09-17 19:17:29 +02:00
Jerzykv 22e04a53b3 changelog 2026-09-17 18:39:10 +02:00
Jerzykv df3217b41b Fixed Bramblin always sleeping 2026-09-17 18:38:48 +02:00
Jerzykv 9ff15a1913 Flygon poser fix 2026-09-17 06:43:03 +02:00
Aram 89e43945a3 Edit CobblemonBlocks.kt 2026-09-16 14:25:26 +02:00
Aram bd8dcddf35 Edit CobblemonBlocks.kt 2026-09-16 14:24:31 +02:00
Polymeta dd372f3160 Merge branch 'recipe-booking' into 'main'
Recipe Book Tweaks

Closes #2088

See merge request cable-mc/cobblemon!2418
2026-09-16 10:03:42 +00:00
dhi_holo c56aab28d4 Recipe Book Tweaks
Closes #2088 as well as fixes a lot of similar issues for gameplay consistency
2026-09-16 18:41:05 +10:00
RedGenesectNinja ca58801794 Implemented Tympole, Palpitoad and Seismitoad 2026-09-15 23:36:40 -05:00
Instrafe 38fa5e2628 Updated Changelog 2026-09-15 16:46:25 -04:00
Instrafe fae1ae7124 Implemented Absol's remodel 2026-09-15 16:45:59 -04:00
Jerzykv 3ec5f76c73 Implemented Deerling and Sawsbuck remodels 2026-09-15 14:15:37 +02:00
Dmytro BunnuaandWeblate 5fbbcb5707 Translated using Weblate (Ukrainian)
Currently translated at 75.9% (7194 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/uk/
2026-09-15 09:37:39 +00:00
ewwww 5bb8aaba55 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-09-15 05:05:08 -04:00
ewwww 23ba41e981 wiglett + wugtrio underwater tweaks 2026-09-15 05:04:40 -04:00
Instrafe 29b049452b Updated Changelog 2026-09-14 19:24:03 -04:00
Instrafe 0b0f665804 Glimmet and Glimmora now have implemented remodels 2026-09-14 19:23:30 -04:00
Polymeta 3cdf28ffdd Merge branch 'Chorus-Briar-Fix' into 'main'
I'm never doing an update where I have to keep track of 650+ structure files again

See merge request cable-mc/cobblemon!2414
2026-09-14 21:18:29 +00:00
Kenji_64 5d53fba2ce Fixed Rockruff line missing blinks 2026-09-14 20:47:40 +02:00
Slippy c6fb18ea51 I'm never doing an update hwere I have to keep track of 650+ structure files again 2026-09-14 10:48:01 -05:00
Polymeta 2d851cb134 Merge branch 'tm-crafting-corrections' into 'main'
TM Recipes

See merge request cable-mc/cobblemon!2413
2026-09-14 13:03:40 +00:00
dhi_holo 92b0ef84e9 Yeah, I've Got Time
This'll hopefully be ready in time for 1.8.2 so I'm making the decision to treat it as if it is now, so it almost certainly will be
2026-09-14 16:37:09 +10:00
dhi_holo dc8d18384c Merge branch 'main' into flora-heals-an-aching-soul- 2026-09-14 16:33:26 +10:00
dhi_holo f685be0aab TM Recipes
Corrected two incorrect TM recieps, both in the jsons and the spreadsheets
2026-09-14 16:30:06 +10:00
dhi_holo 44c056ecd5 Update CHANGELOG-DecorativeWv2.md
Fixed that up for cleanup reasons
2026-09-14 11:34:16 +10:00
dhi_holo dcc707c20d Model Fix
Improved the visuals and log spam of the Wistensu blocks
2026-09-14 10:53:12 +10:00
FrankTheFarmer 14cc851562 Tweaked Mimikyus poser. Hopefully fixes an issue nobody can replicate 😭 2026-09-14 09:46:55 +10:00
FrankTheFarmer 7223d1fd68 Merge branch 'main' into implement_pokemon 2026-09-14 09:21:43 +10:00
Polymeta 6ab5ad52ec Merge branch 'typegem-advancement-trigger' into 'main'
Adds typegem item advancement trigger

Closes #2086

See merge request cable-mc/cobblemon!2411
2026-09-13 20:58:08 +00:00
PieDuck 079b2ae766 Merge branch 'typegem-advancement-trigger' of https://gitlab.com/dmnaru/cobblemon into typegem-advancement-trigger 2026-09-13 21:51:29 +01:00
PieDuck 34eea827e7 changelog 2026-09-13 21:51:09 +01:00
PieDuck 06c6ee5a9c Merge branch cobblemon:main into typegem-advancement-trigger 2026-09-13 21:37:22 +01:00
PieDuck 54748c20ea Add type gem item advancement trigger 2026-09-13 21:36:18 +01:00
Instrafe 75f52e4cf4 Updated Changelog 2026-09-13 11:10:48 -04:00
Instrafe 55446dd4b1 Implemented remodels for Binacle and Barbaracle 2026-09-13 11:08:58 -04:00
Polymeta 02e63b6fc8 Merge branch 'gem-block-alert' into 'main'
Fixed Missing Recipe Advancements

See merge request cable-mc/cobblemon!2409
2026-09-13 14:41:13 +00:00
Polymeta 3aebcf66ec rename lang file to match convention 2026-09-13 14:26:44 +00:00
Gustavo PuerariandWeblate 580a24eac3 Translated using Weblate (Portuguese (Brazil))
Currently translated at 96.8% (9175 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-13 13:18:28 +00:00
Gertimoshka 07b1b22c2c Wistensu Flower Logic Implementation 2026-09-13 15:08:44 +02:00
dhi_holo 49a5f716b7 Fixed Missing Recipe Advancements
Why were the advancements in the recipes..?
2026-09-13 22:05:44 +10:00
dhi_holo 23078eccba Wistensu Root Models
Fixed a tag and added the models for the Wistensu Roots
2026-09-13 21:11:11 +10:00
ewwww 8b825d558f cacnea & cacturne remodels + animations 2026-09-13 06:28:57 -04:00
dhi_holo 8a82f35edf Seasonings 2026-09-13 19:59:19 +10:00
dhi_holo bec664c073 Merge Conflict Fixes 2026-09-13 13:01:20 +10:00
jackowes 9a7f97f11f changelog 2026-09-12 21:52:56 -05:00
jackowes 95b8d62e4a Fix issue with quat spring and replace orientation controller tick slerp with quat spring 2026-09-12 21:45:26 -05:00
dhi_holo 3e61424d8f Merge branch 'main' into flora-heals-an-aching-soul- 2026-09-13 12:28:17 +10:00
Hiroku 0bd7b4dab3 Merge branch 'decorative-items-restored' into 'main'
Decorative Items Project Returns

See merge request cable-mc/cobblemon!2343
2026-09-13 02:24:00 +00:00
Hiroku 8cbada0cf5 Fix changelog whackiness [skip-ci] 2026-09-13 12:23:40 +10:00
Hiroku f0784fec2d Merge remote-tracking branch 'origin/main' into decorative-items-restored 2026-09-13 12:21:54 +10:00
dhi_holo c1606c6fc9 Textures!
Terrain Plants now have proper textures, finishing them off beyond some data file work waiting on some conversations
2026-09-13 11:42:42 +10:00
Hiroku cfe0c5e0a5 Snap on 2026-09-13 11:27:40 +10:00
dhi_holo e0108cc99f Merge branch 'main' into flora-heals-an-aching-soul- 2026-09-13 10:47:37 +10:00
Hiroku 0bc5bb5caa Snap off and changelog updates 2026-09-13 08:03:56 +10:00
Hiroku 3330941a2c Merge remote-tracking branch 'origin/lang-updates' 2026-09-13 07:58:31 +10:00
Polymeta 79ea082d7e Merge branch '1.8.1-final-engi-fixes' into 'main'
1.8.1 final engi fixes

See merge request cable-mc/cobblemon!2404
2026-09-12 20:09:26 +00:00
Kenji_64 3d78929ff0 Entire Applin line has placeholder anims now! + other Applin related minor fixes 2026-09-12 21:36:12 +02:00
Kenji_64 5d94b03f7a Fixed Lucario's aura particles not emitting 2026-09-12 21:32:48 +02:00
Kenji_64 587305eb52 Added Fickle Beam's double damage text to en_us 2026-09-12 21:28:52 +02:00
Polymeta e363ce5e0e Merge branch 'Log-Spam-Fix' into 'main'
Fixed console spam with carved_ice_spikes. Fixed air pockets in deep sea spires

See merge request cable-mc/cobblemon!2403
2026-09-12 17:14:50 +00:00
Slippy e49b704200 Fixed console spam with carved_ice_spikes. Fixed air pockets in deep sea spires 2026-09-12 11:39:48 -05:00
Polymeta ec8b35860c Merge branch '1.8.1-implementation-fixes' into 'main'
fixed sableye's shoulder mount animations (thanks @kiteman.exe from discord)

See merge request cable-mc/cobblemon!2402
2026-09-12 06:40:07 +00:00
dhi_holo c0de8a2591 Update TerrainSeedBlock.kt
Actually fixed it this time
2026-09-12 15:19:09 +10:00
dhi_holo b8733eed52 Revert "Update TerrainSeedBlock.kt"
This reverts commit bd0511a9fb.
2026-09-12 14:31:19 +10:00
dhi_holo 4aa30efec7 Merge branch 'main' into flora-heals-an-aching-soul- 2026-09-12 13:04:47 +10:00
ewwww 74c49bf2da fixed sableye's shoulder mount animations (thanks @kiteman.exe from discord) 2026-09-11 22:39:37 -04:00
Plastered_Crab 4354c3fb03 Fix mip map of TM shelf disc textures to be 16x16 2026-09-10 11:56:30 -07:00
Gustavo PuerariandWeblate c03e7d9ed3 Translated using Weblate (Portuguese (Brazil))
Currently translated at 96.8% (9175 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-10 18:30:26 +00:00
Polymeta b54f2442c9 Merge branch '1.8.1-implementation-fixes' into 'main'
1.8.1 implementation fixes (again)

See merge request cable-mc/cobblemon!2393
2026-09-10 12:07:05 +00:00
Jerzykv 62c0545ea7 Moved recent implement_pokemon fixes to this branch 2026-09-10 13:49:26 +02:00
Kenji_64 f25a676bd5 Changelog 2026-09-10 13:26:54 +02:00
Kenji_64 a9906762a0 Fixed head_ai and removed battle condition on Lycanroc (all forms) 2026-09-10 13:23:03 +02:00
Kenji_64 7b5b88eca2 Added head movement to Archaludon and added missing sleep poses to Duraludon line 2026-09-10 13:22:34 +02:00
Polymeta f821abd7e5 fix helditem notation in spawn file 2026-09-10 11:04:04 +00:00
Kenji_64 291682ba52 Added Diglett line faints (which were in the files, but not implemented) 2026-09-10 12:50:56 +02:00
Kenji_64 546d403b45 Fixed Kingambit waddling its legs around when moving 2026-09-10 12:50:22 +02:00
Gustavo PuerariandWeblate 2652e53e0c Translated using Weblate (Portuguese (Brazil))
Currently translated at 96.0% (9097 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-10 10:03:46 +00:00
Polymeta 08ac7d478d Merge branch 'shoulder-fix' into 'main'
Fixed Pokémon on shoulders not animating if they are visibly holding a held item.

Closes #1859

See merge request cable-mc/cobblemon!2392
2026-09-10 09:37:54 +00:00
Polymeta 4f51016ca9 Merge branch '1.8.1-implementation-fixes' into 'main'
1.8.1-implementation-fixes

See merge request cable-mc/cobblemon!2390
2026-09-10 09:34:16 +00:00
Paul ef8b4fd3cd Fixed Pokémon on shoulders not animating if they are visibly holding a held item.
Closes #1859
2026-09-10 19:20:36 +10:00
FrankTheFarmer f55972c190 Mantyke, Mantine, Quaxly recoil expression changelog 2026-09-10 19:15:14 +10:00
FrankTheFarmer 2dbf304ebe Merge branch 'main' into 1.8.1-implementation-fixes 2026-09-10 19:08:55 +10:00
FrankTheFarmer 1c5feab9c9 Removed recoil animation from Mantyke, Mantine, and Quaxly 2026-09-10 19:08:35 +10:00
Paul 1c720793c4 Rescraped spawns 2026-09-10 18:56:17 +10:00
FrankTheFarmer 6a170f8ce8 Merge branch 'main' into 1.8.1-implementation-fixes 2026-09-10 18:09:00 +10:00
Polymeta b4aba94f89 bump neoforge.mods.toml loaderVersion to be in line with what cobblemon is built against
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-10 08:22:19 +01:00
Polymeta 9832730f5c Merge branch 'Ancient-Wellspring-&-Chorus-Briar-Fixes' into 'main'
Ancient Wellsprings now how the correct habitat spawn pool. Chorus Briars no...

See merge request cable-mc/cobblemon!2391
2026-09-10 06:53:35 +00:00
Slippy ffdaec3e15 Ancient Wellsprings now how the correct habitat spawn pool. Chorus Briars no longer have air pockets. 2026-09-10 01:50:38 -05:00
ewwww d481f887a8 i just wanted to make it tinier c: 2026-09-10 02:47:35 -04:00
ewwww df3aec6222 updated region bias pack icon 2026-09-10 02:47:23 -04:00
Gustavo PuerariandWeblate b423fb1c46 Translated using Weblate (Portuguese (Brazil))
Currently translated at 95.4% (9040 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-10 00:39:17 +00:00
Gustavo PuerariandWeblate 8ae26a268a Translated using Weblate (Portuguese (Brazil))
Currently translated at 95.1% (9016 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-10 00:11:34 +00:00
Gustavo PuerariandWeblate 39b522abcc Translated using Weblate (Portuguese (Brazil))
Currently translated at 95.0% (9005 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-10 00:05:18 +00:00
Gustavo PuerariandWeblate 9bcfcef4fd Translated using Weblate (Portuguese (Brazil))
Currently translated at 94.8% (8983 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-09 23:29:29 +00:00
Gustavo PuerariandWeblate 60ac2d8058 Translated using Weblate (Portuguese (Brazil))
Currently translated at 94.4% (8947 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-09 23:06:46 +00:00
Gustavo PuerariandWeblate 9366d28141 Translated using Weblate (Portuguese (Brazil))
Currently translated at 94.4% (8946 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/pt_BR/
2026-09-09 22:24:00 +00:00
阿鱼andWeblate 75b48196b7 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (9472 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/zh_Hans/
2026-09-09 14:13:55 +00:00
FrankTheFarmer 5180e2b501 Changelog for blastoise mount fix 2026-09-09 22:42:08 +10:00
FrankTheFarmer bb407b1d65 Removed unintentional second seat for Blastoise mount 2026-09-09 22:40:02 +10:00
FrankTheFarmer 8b308e5595 RIP Hisiuan Melon 2026-09-09 22:16:18 +10:00
FrankTheFarmer 148526e5e8 Added a blank watermelon png to prevent unintentional applications of melon cosmetic for hisuian electrode 2026-09-09 22:13:45 +10:00
FrankTheFarmer f319efcf12 Removed Hisuian Electrode's ability to turn into a watermelon. witermlon 2026-09-09 22:13:02 +10:00
FrankTheFarmer c9f40e32a1 Changedog 2026-09-09 20:42:39 +10:00
FrankTheFarmer d642b8dd07 Candle dog's dad 🪦 🐶 2026-09-09 20:41:42 +10:00
FrankTheFarmer 549a11116e Candle dog. Dog with the candle. We put candle on him 🕯🐶 2026-09-09 20:39:45 +10:00
Polymeta b5468a4a4f Merge branch 'main' into implement_pokemon 2026-09-09 09:06:51 +01:00
Polymeta 5055e3926c Merge branch '1.8.1-engi-fixes' into 'main'
ampharos + alolan raticate fixes

See merge request cable-mc/cobblemon!2387
2026-09-09 08:05:11 +00:00
ewwww 3d92de18b8 ampharos + alolan raticate fixes 2026-09-09 03:47:57 -04:00
ewwww c5d278ccb7 spheal. 2026-09-09 03:42:50 -04:00
ewwww bf5e15de45 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-09-09 03:40:21 -04:00
Jerzykvandewwww f4af417267 Fixed Alolan Raticate's cry 2026-09-09 03:40:14 -04:00
FrankTheFarmerandewwww 69c04acaeb ⚡ Vikavolt 2026-09-09 03:40:14 -04:00
FrankTheFarmerandewwww 83a6bb188f 🚎Charjabug💖 2026-09-09 03:40:14 -04:00
FrankTheFarmerandewwww 494a8b5e91 🐛Grubbin 2026-09-09 03:40:14 -04:00
FrankTheFarmerandewwww c8021f2f08 Im putting bugs into cobblemon changelog 2026-09-09 03:40:14 -04:00
Jerzykvandewwww 8cdf3af047 Ampharos now has eye locators 2026-09-09 03:40:14 -04:00
ewwww c90a24e1ab switcheroo part two 2026-09-09 03:40:14 -04:00
FrankTheFarmerandewwww 83e3cfc34e Placeholder idle animation for snover 2026-09-09 03:40:14 -04:00
FrankTheFarmerandewwww 133dd4e5b7 🏔 Added snover line 🌲 2026-09-09 03:40:13 -04:00
FrankTheFarmerandewwww a8cca14b86 Obamasnow 2026-09-09 03:40:13 -04:00
FrankTheFarmerandewwww 9b0f356fa4 Donald Snover 2026-09-09 03:40:13 -04:00
FrankTheFarmerandewwww f2b0a102b6 What if we started implementing pokemon rn rn 2026-09-09 03:40:13 -04:00
Polymeta ab9851fd12 fixed size altering effect and ensure pokemon dont get too small to live
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-09 08:05:55 +01:00
Polymeta a9df6bc709 Merge branch 'pokeball-catchrate-fix' into 'main'
Fixed Beast, Moon, Love, Timer and Luxury Ball behaviour not matching their tooltips

Closes #2062, #2063, #2064, #2065, and #2066

See merge request cable-mc/cobblemon!2384
2026-09-09 06:33:15 +00:00
FrankTheFarmer a7d01b230a Merge branch 'main' into implement_pokemon 2026-09-09 16:04:20 +10:00
jackowes 2a7db4b9b8 bring in util functions 2026-09-08 20:15:54 -05:00
Jerzykv 77e5f2501d Fixed Alolan Raticate's cry 2026-09-08 19:56:35 +02:00
Polymeta 3681813976 Merge branch 'misc-bug-fixes' into 'main'
Fix spawn file typos and type gem cluster bug.

See merge request cable-mc/cobblemon!2386
2026-09-08 14:38:12 +00:00
FrankTheFarmer e6513717bd ⚡ Vikavolt 2026-09-08 21:34:26 +10:00
FrankTheFarmer 0e0c98f693 🚎Charjabug💖 2026-09-08 21:32:56 +10:00
FrankTheFarmer 528bfa4e22 🐛Grubbin 2026-09-08 21:32:09 +10:00
FrankTheFarmer 83c87779d7 Im putting bugs into cobblemon changelog 2026-09-08 21:30:42 +10:00
FrankTheFarmer dc33235144 Merge branch 'refs/heads/main' into implement_pokemon 2026-09-08 21:29:01 +10:00
Polymeta 333d234dbe [skip-ci] push new tag documentation for 1.8
Signed-off-by: Polymeta <4726322-polymeta@users.noreply.gitlab.com>
2026-09-08 10:21:46 +01:00
FrankTheFarmer 935c658c5f Merge branch 'main' into implement_pokemon 2026-09-08 17:59:22 +10:00
Deltric ee42825dc3 Fix spawn file typos and type gem cluster bug. 2026-09-07 15:31:10 -05:00
Paul 0d9a4f9392 Some changelog [skip-ci] 2026-09-07 20:04:41 +10:00
Jerzykv 0426f4fbf9 Ampharos now has eye locators 2026-09-07 08:31:09 +02:00
Kiritiop 5e90c05fdd Fixed Beast, Moon, Love, Timer and Luxury Ball behaviour not matching their tooltips
Closes #2062
Closes #2063
Closes #2064
Closes #2065
Closes #2066
2026-09-07 05:42:05 +00:00
dhi_holo 7930ec52fa Merge branch 'main' into flora-heals-an-aching-soul- 2026-09-07 15:20:47 +10:00
dhi_holo 75bc03fdcb Merge branch 'main' into decorative-items-restored 2026-09-07 15:19:27 +10:00
Hiroku 9a0648d956 Merge branch 'issue-2061' into 'main'
Fixed the TM Machine rejecting moves unlocked by unlockAllMoveDexMovesByDefault

Closes #2061

See merge request cable-mc/cobblemon!2383
2026-09-07 01:43:53 +00:00
Paul 91187ce952 Fixed NPCs getting their own TM progress file. Closes #2059 2026-09-07 11:42:33 +10:00
Hiroku 686bc62996 Merge branch 'refactor/molang-fn-code-smell' into 'main'
style: MoLang functions autumn codestyle cleanup

See merge request cable-mc/cobblemon!2368
2026-09-07 01:22:48 +00:00
Hiroku 5925247ace Merge branch 'wing_flap_fix' into 'main'
Removed incorrect sound keyframes from Rufflet, Braviary and Hisuian Braviary

See merge request cable-mc/cobblemon!2381
2026-09-07 01:05:11 +00:00
Kiritiop c6a4bec3f3 Fixed the TM Machine rejecting moves unlocked by unlockAllMoveDexMovesByDefault
The server-side guard in SetActiveTMPacketHandler ignored the config, so it rejected every move the config had revealed.

Closes #2061
2026-09-07 01:01:17 +00:00
Hiroku 7e582090f8 Merge branch 'issue-1774v2' into 'main'
Fixed stat_compare and stat_equal evolution requirements crashing on special stats (Version 2)

Closes #1774

See merge request cable-mc/cobblemon!2373
2026-09-07 00:25:47 +00:00
Kiritiop 55eef6ea69 Fixed stat_compare and stat_equal evolution requirements crashing on special stats
Stats.getStat did not recognise special_attack or special_defence and fell through to ACCURACY, which has no IV entry, so the requirement threw a NullPointerException from the server tick loop. Added the identifier names to getStat and made both requirements fail instead of crash for any non-permanent stat.

Closes #1774
2026-09-07 00:18:45 +00:00
Watuneni iteiteandWeblate f65cb9538c Translated using Weblate (Japanese)
Currently translated at 100.0% (9472 of 9472 strings)

Translation: Cobblemon/Mod
Translate-URL: https://lang.cobblemon.com/projects/cobblemon/mod/ja/
2026-09-06 14:57:47 +00:00
dhi_holo 57eb306e4f Update TODO List - Flora Heals An Aching Soul.md 2026-09-06 21:25:18 +10:00
dhi_holo bd0511a9fb Update TerrainSeedBlock.kt
Terrain crops now break properly after growing to be 2 blocks tall
2026-09-06 21:24:28 +10:00
dhi_holo c14f3292aa Merge branch 'main' into flora-heals-an-aching-soul- 2026-09-06 19:57:15 +10:00
Sterrezo 8dbce8fefe Removed incorrect sound keyframes from Rufflet, Braviary and Hisuian Braviary 2026-09-06 10:00:31 +02:00
ewwww 46e771a062 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2026-09-05 22:07:28 -04:00
ewwww 4c0095844b Merge remote-tracking branch 'origin/implement_pokemon_2' into implement_pokemon 2026-09-05 22:06:39 -04:00
Gitoido d034469ebb style: additional cleanups 2026-09-05 16:00:00 +03:00
Gitoido 4f8a44c95b style: missed one return 2026-09-05 12:52:22 +00:00
Gitoido 52b17ca39e style: homogeneous code style in molang function classes 2026-09-05 12:52:22 +00:00
ewwww 10a2ef27d6 switcheroo part two 2026-08-24 15:36:02 -04:00
dhi_holo 309ff3da13 Loaded Cleanup
Added Loaded Dice, cleaned up some particles
2026-08-12 20:15:52 +10:00
dhi_holo 3af91e2a74 Further Tweaking 2026-08-12 14:26:20 +10:00
dhi_holo 2e027e3db7 Some Visual and Auditory Tweaks 2026-08-12 14:13:21 +10:00
dhi_holo fc40fb02c0 Next Batch
Never-Melt Ice, Wide, Scope, and Zoom Lens, as well as new designs for Upgrade and Dubious Disc
2026-08-12 13:52:13 +10:00
dhi_holo 2c1c0299f9 SPHERICAL!
Adds Flame Orb, Life Orb, Toxic Orb, Light Ball, and Smoke Ball blocks
2026-08-12 12:24:21 +10:00
FrankTheFarmer 3c7637515c Placeholder idle animation for snover 2026-08-10 13:03:00 +10:00
dhi_holo 08cbbaf68c Merge branch 'main' into decorative-items-restored 2026-08-10 10:21:06 +10:00
Sterrezo 6e911acf01 Sound tweaks
Swapped pot sound types from tumblestone to decorated pots and rock sounds from tumblestone to deepslate.
2026-08-09 12:20:13 +02:00
dhi_holo f6d82a1b28 Changelog
and some last second sound tweaks
2026-08-09 19:17:19 +10:00
dhi_holo 702126aeab Adds Throat Spray and Iron Ball
Adds in Throat Spray and Iron Ball blocks, finishes clean up of King's Rock to look a bit more consistent
2026-08-09 19:04:08 +10:00
dhi_holo df566e8fcb Wearable Hat Cleanup
Fixed King's Rock Blockstate and loot table as well

Wearable "Hat" Items can now be prevented from swapping when trying to open the interaction wheel (sneak right-click), making it not a nightmare to give certain items to Pokemon. Yippee!
2026-08-09 18:53:58 +10:00
dhi_holo c83e0f33b5 Wearable Items Mostly Working Better
Wearable Items now can either be "Blocks" or "Hats"
"Blocks" can be placed as blocks in the world, while "Hats" can be put on the player's head by right clicking them in hand
Need to add some form of override when crouching and looking at a Pokemon though
2026-08-09 17:30:49 +10:00
dhi_holo f279f936b6 Generified Plaque/Wreath/Magnet Code
Moved shared code to a singular class
2026-08-09 16:04:22 +10:00
dhi_holo f8e20bafd9 Import Cleanup 2026-08-09 12:23:19 +10:00
dhi_holo a5211ca7ff Convention Alignment
Changed all stackable item blockstates/model files to utilise the convention from vanilla (seen on blocks such as pink petals)
This commit adds no files, and removes no files, it purely renames and swaps out numbers
2026-08-09 12:06:01 +10:00
FrankTheFarmer 0a5abdb331 🏔 Added snover line 🌲 2026-08-09 11:15:45 +10:00
FrankTheFarmer 4ed7413dd2 Obamasnow 2026-08-09 11:13:17 +10:00
FrankTheFarmer 40b66c24fc Donald Snover 2026-08-09 11:12:56 +10:00
dhi_holo 2f7bedf001 Update scribblenotes - Decorative Items.txt 2026-08-08 18:05:17 +10:00
dhi_holo 44c31a9ddb Update StackableItemBlock.kt
Simplified the way the hitboxes are defined, to allow for future planned hitboxes to actually work as intended
2026-08-08 17:33:34 +10:00
FrankTheFarmer 20a35de3d8 What if we started implementing pokemon rn rn 2026-08-08 14:58:20 +10:00
dhi_holo cfc8a15db1 Update scribblenotes - Decorative Items.txt 2026-08-06 18:35:29 +10:00
dhi_holo d5ba22a86c Removal of Unneeded Lines
deprecate deez nuts
2026-08-06 15:01:34 +10:00
dhi_holo a38505f1c5 Update scribblenotes - Decorative Items.txt
Documenting ideas and plans for future me
2026-08-05 19:28:29 +10:00
dhi_holo 3cf1ba1fb3 Tag Tweaks
Added stuff to relevant tags, removed unused tag from removed feature
2026-08-05 18:36:38 +10:00
dhi_holo 9f5d595827 Import Cleanup
Just removing some imports from reverted changes that snuck into commits
2026-08-05 18:03:06 +10:00
dhi_holo 75673f0204 Mild Polish 2026-08-05 17:09:40 +10:00
dhi_holo b43a7571ff Eject Button
Now with cleaner code, I hope this is more acceptable
2026-08-05 16:13:24 +10:00
dhi_holo b071dc04d3 Metronome
Reimplemented the Metronome, the code has been genericized to allow for more items to be implemented off the same base, although a little bit more work does need to be done there with the comparator output specifically
2026-08-05 15:34:32 +10:00
dhi_holo 12f9ff1647 Wreath, Magnet, and some tweaks to Plaques
Plaques now place the way I had originally set them up, and Galarica Wreaths along with the Magnet are back
2026-08-05 14:34:41 +10:00
dhi_holo 3256a302df Ring Target
Could maybe be polished up, but is good
2026-08-05 13:06:07 +10:00
dhi_holo edbe447477 Basic Decorations
Reimplemented Galarica Cuff, Teapots, Teacups, Weather Rocks, Sachet, Metal Powder, Quick Powder, Bright Powder, Destiny Knot, and Luminous Moss
2026-08-05 12:04:48 +10:00
Gertimoshka 2f43910368 Cleaner Implementation with less duplicate code 2026-08-04 22:21:58 +03:00
dhi_holo c2e87e4cfa Placeable Vitamins Experiment A
Need coder feedback regarding this
Makes Mochi and Feathers require clicking in the air or directly on a Pokemon, if it thinks it can place as a block it prevents usage
2026-08-04 19:25:13 +10:00
dhi_holo e40d2ee806 Create scribblenotes - Decorative Items.txt
Making this a unique commit so it's easy to reference back to later
2026-08-04 14:53:53 +10:00
dhi_holo ba74e7603b Wearables 1
Made Sharp Beak, Covert Cloak, and Shell Helmet wearable
2026-08-04 13:51:49 +10:00
dhi_holo 19c3fe6dc4 Update StackableItemBlock.kt
Fixed hitbox oversight
2026-08-04 13:35:17 +10:00
dhi_holo d0778dd124 Missing Stackables 1
Reimplemented Metal Coat, PP Up, and PP Max's placeable logic
Began work for other Vitamins to work (incomplete)
Added models, textures, blockstates, and loot tables for most incoming blocks
2026-08-04 13:16:46 +10:00
dhi_holo 8fb2b1cf3d Merge branch 'main' into flora-heals-an-aching-soul- 2026-07-12 08:20:14 +10:00
dhi_holo 5c7d1cbe10 Fixed Case of the Missing Loot Table 2026-06-04 15:47:55 +10:00
dhi_holo 3348bfc362 Merge branch 'main' into flora-heals-an-aching-soul- 2026-06-04 14:35:35 +10:00
dhi_holo 8db9252434 Potted Berries for All 2026-05-25 18:09:49 +10:00
dhi_holo d9d4fe15cc Update TODO List - Flora Heals An Aching Soul.md 2026-05-25 08:40:11 +10:00
dhi_holo 8453e2d514 Milling Integration
with Create
2026-05-25 08:15:47 +10:00
dhi_holo 4d3070c0c2 Compost Levels 2026-05-25 08:03:54 +10:00
dhi_holo 15f273a9ef Bright Powder on Signs
It's so wonderful
2026-05-25 07:16:36 +10:00
dhi_holo 007c079cd6 WIP 2026-05-25 05:17:30 +10:00
dhi_holo 9851b1e8ad Merge branch 'main' into flora-heals-an-aching-soul- 2026-05-25 04:22:38 +10:00
dhi_holo c65c2610cd Merge branch 'main' into flora-heals-an-aching-soul- 2026-05-16 21:29:13 +10:00
dhi_holo 2be08a3fcc terminology corrections
also tag additions
2026-05-16 21:26:05 +10:00
dhi_holo 4cdf440eee More Tagging Work 2026-05-13 09:48:52 +10:00
dhi_holo 1545ee8fc2 Merge branch 'main' into flora-heals-an-aching-soul- 2026-05-13 09:12:37 +10:00
dhi_holo a009fabfe4 Create TODO List - Flora Heals An Aching Soul.md 2026-05-12 22:51:33 +10:00
dhi_holo 46e4bfdd13 CONSUME 2026-05-12 22:27:07 +10:00
dhi_holo e24c82860d No Bone 2026-05-12 22:11:53 +10:00
dhi_holo 1291dee1e1 Data Files 2026-05-12 21:39:03 +10:00
dhi_holo 69732be09b Mistleaf's Fae Magics 2026-05-12 18:24:23 +10:00
dhi_holo 8ce4ed7dee Update TerrainFlowerBlock.kt
Removed unused imports
2026-05-12 14:59:52 +10:00
dhi_holo 33d3cc0104 Merge branch 'flora-heals-an-aching-soul-' of https://gitlab.com/cable-mc/cobblemon into flora-heals-an-aching-soul- 2026-05-12 14:52:36 +10:00
dhi_holo 33b6b519ed Merge branch 'flora-heals-an-aching-soul-' of https://gitlab.com/cable-mc/cobblemon into flora-heals-an-aching-soul- 2026-05-12 12:26:39 +10:00
dhi_holo 5ec849f4e3 Merge branch 'main' into flora-heals-an-aching-soul- 2026-05-12 12:26:09 +10:00
Gertimoshka 6f4a2d2cd5 Made the flowers block entities so they can tick and adjust to weather changes 2026-05-11 21:35:48 +03:00
Gertimoshka 0bca89e77d Lucibell will now properly light up if any part of the block has an entity in it, by designating lower part as the 'driver' who controls the states 2026-05-11 17:21:22 +03:00
dhi_holo 8f9f162011 Merge branch 'main' into flora-heals-an-aching-soul- 2026-05-11 09:56:11 +10:00
dhi_holo ac3bea3d5e Update TerrainFlowerBlock.kt
Compacted but it STILL DOESN'T WORK
2026-05-10 21:23:17 +10:00
dhi_holo 178d4f199b Update TerrainFlowerBlock.kt
I feel so close, but the weather checks don't work
2026-05-09 23:04:07 +10:00
dhi_holo 933819380d Merge branch 'main' into flora-heals-an-aching-soul- 2026-05-09 21:15:27 +10:00
dhi_holo d68131e1b9 Update TerrainFlowerBlock.kt
It's starting to get there, probably. Not fully functional yet
2026-05-09 21:15:04 +10:00
dhi_holo ffbc379c0b Some More Work 2026-05-08 14:01:31 +10:00
dhi_holo 7053d12508 Update TerrainSeedBlock.kt
In theory this makes it grow slower on dry dirt, which would be pretty cool
2026-05-07 20:38:47 +10:00
dhi_holo d41442200f Models and Temp Textures and start of some code for glowing 2026-05-07 20:08:15 +10:00
dhi_holo d2564c343e Terrain Seed Beginnings 2026-05-04 22:43:54 +10:00
dhi_holo d1c19e8837 Visual FIxes 2026-05-04 14:30:31 +10:00
dhi_holo 0ec3bfabad Merge branch 'main' into flora-heals-an-aching-soul- 2026-05-04 11:37:59 +10:00
dhi_holo 43ffb04621 FINALLY FUCKING FIXED IT 2026-05-04 11:34:04 +10:00
dhi_holo eeaedf76a9 progress 2026-05-04 09:43:59 +10:00
dhi_holo 120fb4829a Pa'uvillea complete
Maash's tweaks to the wall version, plus placement logic working as desired (although may need further tweaks depending on feedback)
2026-05-03 11:10:00 +10:00
dhi_holo 12b93e53a7 Revenge of the Pa'uvillea 2026-05-02 22:43:34 +10:00
dhi_holo 00b17b9764 Update CobblemonBlocks.kt 2026-05-02 18:55:21 +10:00
dhi_holo 06192f809d Merge branch 'main' into flora-heals-an-aching-soul- 2026-05-02 18:54:04 +10:00
dhi_holo d09eb0d3ff Update LobailaFlowerBlock.kt 2026-04-23 08:05:13 +10:00
dhi_holo 416321679c Updated Textures 2026-04-22 20:29:02 +10:00
dhi_holo 72c4e6e54f Merge branch 'main' into flora-heals-an-aching-soul- 2026-04-19 16:45:10 +10:00
dhi_holo d3738e3424 Merge branch 'main' into flora-heals-an-aching-soul- 2025-12-13 18:45:37 +10:00
dhi_holo 2e183db903 Re-rename
Lobaila and Pa'uvillea changes
2025-12-02 12:33:55 +10:00
dhi_holo fa174b88dd OVERSIGHTS (may need to copy to another branch) 2025-12-01 17:09:00 +10:00
dhi_holo 0b4abe6d16 Texture Updates 2025-12-01 17:08:42 +10:00
dhi_holo 759fb0ecd8 Rename!
Lobalia, Pa'uvilla, and Pomohana are here!
2025-12-01 16:37:53 +10:00
dhi_holo 429e927c3f Potted Berries Part 1
All the data/asset stuff needed for Potted Berries.
Need to handle the codeside now, which will be trickier.
2025-11-30 22:27:29 +10:00
dhi_holo 643aed9133 Update HydrangeaFlowerBlock.kt
Comment
2025-11-30 12:02:26 +10:00
dhi_holo e701642de2 Hydrangea loot and tag starts 2025-11-30 12:00:14 +10:00
dhi_holo a3a03e3da6 Lobelia Polish and Hydrangea
Added start of Hydrangea flowers
2025-11-29 20:51:25 +10:00
dhi_holo 5dd3d5b552 Mint Rework
Totally reworked the Mint Leaf Litter blocks to not rely on Pink Petals due to the differences in behaviour, finally added Craspedia loot table, updated Craspedia texture, fixed Lobelia being shaded incorrectly
2025-11-29 16:20:45 +10:00
dhi_holo 1bae246e6c Gestdate
Ges updated textures
2025-11-29 14:39:30 +10:00
dhi_holo c94ef10189 Merge branch 'main' into flora-heals-an-aching-soul- 2025-11-29 14:24:30 +10:00
dhi_holo dfacf1f9df Merge branch 'main' into flora-heals-an-aching-soul- 2025-11-27 18:33:35 +10:00
dhi_holo 06c5e34f48 Merge branch 'main' into flora-heals-an-aching-soul- 2025-11-23 20:33:34 +10:00
dhi_holo 1ab8b35e98 Merge branch 'main' into flora-heals-an-aching-soul- 2025-11-23 12:50:52 +10:00
dhi_holo 261b61ee6c Updated Assets and removed TODO 2025-11-22 15:31:12 +10:00
dhi_holo 5abf8e1bab Merge branch 'main' into flora-heals-an-aching-soul- 2025-11-22 14:16:33 +10:00
dhi_holo 7c0b463637 Merge branch 'main' into flora-heals-an-aching-soul- 2025-11-21 18:14:42 +10:00
dhi_holo e156ab2432 Craspedia assets and Lobelia waterlogging
Still needs some more work done on both, but it's real nice now
2025-11-21 18:14:29 +10:00
dhi_holo 0b46a7163b Merge branch 'main' into flora-heals-an-aching-soul- 2025-11-21 13:35:16 +10:00
dhi_holo 21b76c008a Craspedia start
Needs work, but it's fully functional
2025-11-21 13:30:19 +10:00
dhi_holo b7e9f8248d Oricorio Start
Lobelia (Red) flower partially set up.
Wanna look into letting it be waterlogged, and no dye crafting recipe is set up yet
2025-11-20 19:34:10 +10:00
dhi_holo 8b21e76a36 Merge branch 'main' into flora-heals-an-aching-soul- 2025-11-20 17:06:31 +10:00
dhi_holo 515471185f Create CHANGELOG-DecorativeWv2.md 2025-11-19 19:54:49 +10:00
dhi_holo 628e8f4e5e Mint Leaf Litter and Potted Mint
"It's not fucking weed you piece of shit stoner" -Kermit the Frog

Needs some cleanup when it comes to how I've stolen the Pink Petals (due to Leaf Litter being a future block), but what we got rn is a good starting point
2025-11-19 19:42:38 +10:00
3016 changed files with 306913 additions and 50010 deletions
+3
View File
@@ -157,6 +157,9 @@ build:
paths:
- neoforge/build/libs
- fabric/build/libs
exclude:
- neoforge/build/libs/*-dev-shadow.jar
- fabric/build/libs/*-dev-shadow.jar
publish:
stage: deploy
+65 -6
View File
@@ -1,25 +1,84 @@
# Changelog
## [1.x.x (MONTH Xth, 2026)](#1-x-x)
## [1.8.3 (MONTH xth, 2026)](#1-8-3)
### Additions
### New rideable Pokémon
### New Rideable Pokémon
- Vikavolt
### Pokémon Added
### 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
### Model updates for the following Pokémon
### Animation updates for the following Pokémon
- Vikavolt
- Magby
- Magmar
### 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
- Prehistoric Eroded Pillar generates slightly more frequently
### 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 evolution trees not building properly when a pokemon has several pokedex 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 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, needing them to relog
- Prehistoric Frozen Pond, Prehistoric Frozen Spike, and Prehistoric Lush Den now generate
- Fixed Frogadier floating in its battle_idle animation
- 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
### Particles
- 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 target
### Localization
- Updated translations for:
- Updated translations for:
-9
View File
@@ -1,9 +0,0 @@
- Some kind of particle effect for them? they're meant to have glowing eyes, so maybe some red particles on the eye locator
--- global posers? stacked posers?
--- red texture seems to be what some people wanted.
/- shoulder mounts part of it
- omni boost
- roar when player comes within reach, adjust levels
- Riding settings to control what the min scale is for a pokemon to be ridable.
+5 -2
View File
@@ -1,3 +1,6 @@
import utilities.gitBranch
import utilities.gitShortCommit
plugins {
id("cobblemon.root-conventions")
}
@@ -6,6 +9,6 @@ version = "${project.property("mod_version")}+${project.property("mc_version")}"
val isSnapshot = project.property("snapshot")?.equals("true") == true
if (isSnapshot) {
val fixedBranchName = versioning.info.branch.substringAfter("/")
version = "$version-${fixedBranchName}-${versioning.info.build}"
val fixedBranchName = gitBranch().substringAfter("/")
version = "$version-${fixedBranchName}-${gitShortCommit()}"
}
+4 -1
View File
@@ -36,6 +36,7 @@
- Added `cobblemon:catch_alpha_pokemon` advancement trigger and `totalAlphaCaptureCount` stat for captured Alpha Pokémon.
### New rideable Pokémon
- Pidgeot
- Crobat
- Skarmory
- Milotic
@@ -106,7 +107,9 @@
- Avalugg (Hisuian Form)
### Model updates for the following Pokémon
- Pidgey
- Pidgeotto
- Pidgeot
- Sandshrew
- Sandslash
- Nidoking
+41
View File
@@ -0,0 +1,41 @@
# 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
@@ -0,0 +1,211 @@
# 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
+15 -6
View File
@@ -1,9 +1,8 @@
import utilities.isSnapshot
import utilities.version
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import utilities.gitBranch
import utilities.gitCommit
/*
*
@@ -72,6 +71,14 @@ tasks.withType<Test> {
}
}
// Git info lives in its own output dir (not processResources) so a new commit only rewrites this one file.
val writeGitInfo by tasks.registering(WriteProperties::class) {
destinationFile = layout.buildDirectory.file("generated/gitInfo/cobblemon-git.properties")
property("commit", gitCommit())
property("branch", gitBranch())
}
sourceSets.main { output.dir(mapOf("builtBy" to writeGitInfo), layout.buildDirectory.dir("generated/gitInfo")) }
tasks.processResources {
exclude("data/**/README.md")
}
@@ -98,11 +105,13 @@ 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") })
}
@@ -8,54 +8,42 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.block.SaccharineLeafBlock;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Block;
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.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
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.LocalCapture;
@Mixin(targets = "net.minecraft.world.entity.animal.Bee$BeeGrowCropGoal")
public abstract class BeeEntityMixin {
@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);
}
}
}
@Shadow(aliases = {"this$0", "field_20373"}) @Final private Bee bee;
// 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 applyCustomBlockState(BlockState before) {
if(this.cobblemon$result != null && before == null) {
BlockState result = this.cobblemon$result;
this.cobblemon$result = null;
return result;
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;
}
return before;
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);
}
}
@@ -15,6 +15,7 @@ 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;
@@ -45,4 +46,24 @@ 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));
}
}
@@ -35,13 +35,16 @@ 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;
@@ -50,6 +53,7 @@ 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;
@@ -371,4 +375,33 @@ 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);
}
}
@@ -16,6 +16,7 @@ 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;
@@ -63,23 +64,12 @@ public abstract class ClientRecipeBookMixin {
}
}
@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())));
@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(name = "map2") Map<RecipeBookCategories, List<RecipeCollection>> map2) {
CobblemonRecipeCategories.Companion.getCustomAggregateCategories().forEach((recipeBookCategories, list) -> map2.put(recipeBookCategories, (List) list.stream().flatMap((recipeBookCategoriesx) -> ((List) map2.getOrDefault(recipeBookCategoriesx, ImmutableList.of())).stream()).collect(ImmutableList.toImmutableList())));
this.collectionsByTab = ImmutableMap.copyOf(map2);
this.allCollections = builder.build();
}
}
@@ -10,11 +10,13 @@ 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;
@@ -96,4 +98,14 @@ 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);
}
}
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.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,6 +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.item.PokedexItem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
@@ -31,6 +32,9 @@ public class ItemInHandRendererMixin {
if (abstractClientPlayer.isUsingItem() && abstractClientPlayer.getUseItem().getItem() instanceof PokedexItem) {
ci.cancel();
}
else if (interactionHand == InteractionHand.MAIN_HAND && FirstPersonRecallBallRenderer.INSTANCE.render(poseStack, multiBufferSource, j, f)) {
ci.cancel();
}
else if (abstractClientPlayer instanceof OrientationControllable controllable && controllable.getOrientationController().isActive()) {
}
@@ -9,55 +9,25 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.CobblemonRecipeCategories;
import net.minecraft.client.ClientRecipeBook;
import net.minecraft.client.Minecraft;
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.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 {
@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();
@Definition(id = "recipeBookCategories", local = @Local(name = "recipeBookCategories"))
@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(name = "recipeBookCategories") RecipeBookCategories recipeBookCategories) {
return original && recipeBookCategories != CobblemonRecipeCategories.COOKING_POT_SEARCH.toVanillaCategory();
}
}
@@ -8,8 +8,12 @@ object CobblemonBuildDetails {
const val VERSION = "{{ version }}"
const val SNAPSHOT = {{ isSnapshot }}
const val GIT_COMMIT = "{{ gitCommit | default("Not Specified") }}"
const val BRANCH = "{{ branch | default("Not Specified") }}"
// 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")
fun smallCommitHash(): String {
return this.GIT_COMMIT.substring(0, 7)
@@ -260,6 +260,9 @@ object Cobblemon {
RequestManager.onLogoff(it.player)
serverPlayerStructs.remove(it.player.uuid)
}
PlatformEvents.PLAYER_RESPAWN.subscribe {
serverPlayerStructs.remove(it.player.uuid)
}
PlatformEvents.PLAYER_DEATH.subscribe {
PCLinkManager.removeLink(it.player.uuid)
battleRegistry.getBattleByParticipatingPlayer(it.player)?.stop()
@@ -109,4 +109,21 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
"poke_snack",
BlockEntityType.Builder.of(::PokeSnackBlockEntity, CobblemonBlocks.POKE_SNACK, CobblemonBlocks.POKE_CAKE).build(null)
)
@JvmField
val TERRAIN_PLANT: BlockEntityType<TerrainPlantBlockEntity> = this.create(
"terrain_plant",
BlockEntityType.Builder.of(::TerrainPlantBlockEntity,
CobblemonBlocks.VOLTACONIA,
CobblemonBlocks.LUCIBELL,
CobblemonBlocks.VERDROOT
).build(null)
)
@JvmField
val MISTLEAF_FLOWER: BlockEntityType<MistleafFlowerBlockEntity> = this.create(
"mistleaf",
BlockEntityType.Builder.of(::MistleafFlowerBlockEntity,
CobblemonBlocks.MISTLEAF
).build(null)
)
}
@@ -16,6 +16,7 @@ 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
@@ -26,6 +27,7 @@ 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
@@ -621,37 +623,131 @@ 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 FULL_HEAL = create("full_heal", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
val RING_TARGET = create("ring_target", RingTargetBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).mapColor(MapColor.NONE).noOcclusion().strength(0.125f)))
@JvmField
val ANTIDOTE = create("antidote", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.STATUS_RESTORE))
val MAGNET = create("magnet", MagnetBlock(BlockBehaviour.Properties.of().sound(SoundType.METAL).mapColor(MapColor.NONE).noOcclusion().strength(0.075f)))
@JvmField
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))
val GALARICA_WREATH = create("galarica_wreath", GalaricaWreathBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noOcclusion().noCollission()))
@JvmField
val ETHER = create("ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
val FLAME_ORB = create("flame_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{15}))
@JvmField
val MAX_ETHER = create("max_ether", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
val TOXIC_ORB = create("toxic_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion()))
@JvmField
val ELIXIR = create("elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
val LIFE_ORB = create("life_orb", OrbBlock(BlockBehaviour.Properties.of().sound(SoundType.GLASS).mapColor(MapColor.NONE).noOcclusion().lightLevel{7}))
@JvmField
val MAX_ELIXIR = create("max_elixir", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.ETHER))
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()))
@JvmField
val POTION = create("potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
val KINGS_ROCK = create("kings_rock", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.CENTERED_6x))
@JvmField
val SUPER_POTION = create("super_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
val GALARICA_CUFF = create("galarica_cuff", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.AZALEA_LEAVES).mapColor(MapColor.NONE).noCollission().noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.FLATTENED_10x4))
@JvmField
val HYPER_POTION = create("hyper_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.POTION))
val UNREMARKABLE_TEACUP = create("unremarkable_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
@JvmField
val MAX_POTION = create("max_potion", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
val MASTERPIECE_TEACUP = create("masterpiece_teacup", DecorativeItemBlock(BlockBehaviour.Properties.of().sound(SoundType.DECORATED_POT).mapColor(MapColor.NONE).noOcclusion(), DecorativeItemBlock.DecorativeItemBlockType.TEACUP))
@JvmField
val FULL_RESTORE = create("full_restore", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.MAX_POTION))
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))
@JvmField
val DIRE_HIT = create("dire_hit", StackableItemBlock(BlockBehaviour.Properties.of().sound(SoundType.CANDLE).mapColor(MapColor.NONE).noOcclusion(), StackableItemBlock.StackableItemBlockType.BATTLE_ITEM))
@@ -670,6 +766,23 @@ 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
@@ -685,6 +798,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val PC = create("pc", PCBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F).noOcclusion().lightLevel { if ((it.getValue(PCBlock.ON) as Boolean) && (it.getValue(PCBlock.PART) == PCBlock.PCPart.TOP)) 13 else 0 }))
@JvmField
val TM_MACHINE = create(
"tm_machine",
TMMachineBlock(
@@ -713,7 +827,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)))
@@ -755,6 +869,64 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val POTTED_SACCHARINE_SAPLING = this.create("potted_saccharine_sapling", BlocksInvoker.createFlowerPotBlock(SACCHARINE_SAPLING))
@JvmField
val RED_MINT_LEAF_LITTER = this.mintLitter("red_mint_leaf_litter", MapColor.COLOR_RED)
@JvmField
val POTTED_RED_MINT = this.pottedPlant("potted_red_mint", RED_MINT_LEAF_LITTER)
@JvmField
val BLUE_MINT_LEAF_LITTER = this.mintLitter("blue_mint_leaf_litter", MapColor.COLOR_BLUE)
@JvmField
val POTTED_BLUE_MINT = this.pottedPlant("potted_blue_mint", BLUE_MINT_LEAF_LITTER)
@JvmField
val CYAN_MINT_LEAF_LITTER = this.mintLitter("cyan_mint_leaf_litter", MapColor.COLOR_CYAN)
@JvmField
val POTTED_CYAN_MINT = this.pottedPlant("potted_cyan_mint", CYAN_MINT_LEAF_LITTER)
@JvmField
val PINK_MINT_LEAF_LITTER = this.mintLitter("pink_mint_leaf_litter", MapColor.COLOR_PINK)
@JvmField
val POTTED_PINK_MINT = this.pottedPlant("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.pottedPlant("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.pottedPlant("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
}
)
@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)))
@JvmField
@@ -931,6 +1103,147 @@ 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.create("potted_aguav_berry", BlocksInvoker.createFlowerPotBlock(AGUAV_BERRY))
@JvmField
val POTTED_APICOT_BERRY = this.create("potted_apicot_berry", BlocksInvoker.createFlowerPotBlock(APICOT_BERRY))
@JvmField
val POTTED_ASPEAR_BERRY = this.create("potted_aspear_berry", BlocksInvoker.createFlowerPotBlock(ASPEAR_BERRY))
@JvmField
val POTTED_BABIRI_BERRY = this.create("potted_babiri_berry", BlocksInvoker.createFlowerPotBlock(BABIRI_BERRY))
@JvmField
val POTTED_BELUE_BERRY = this.create("potted_belue_berry", BlocksInvoker.createFlowerPotBlock(BELUE_BERRY))
@JvmField
val POTTED_BLUK_BERRY = this.create("potted_bluk_berry", BlocksInvoker.createFlowerPotBlock(BLUK_BERRY))
@JvmField
val POTTED_CHARTI_BERRY = this.create("potted_charti_berry", BlocksInvoker.createFlowerPotBlock(CHARTI_BERRY))
@JvmField
val POTTED_CHERI_BERRY = this.create("potted_cheri_berry", BlocksInvoker.createFlowerPotBlock(CHERI_BERRY))
@JvmField
val POTTED_CHESTO_BERRY = this.create("potted_chesto_berry", BlocksInvoker.createFlowerPotBlock(CHESTO_BERRY))
@JvmField
val POTTED_CHILAN_BERRY = this.create("potted_chilan_berry", BlocksInvoker.createFlowerPotBlock(CHILAN_BERRY))
@JvmField
val POTTED_CHOPLE_BERRY = this.create("potted_chople_berry", BlocksInvoker.createFlowerPotBlock(CHOPLE_BERRY))
@JvmField
val POTTED_COBA_BERRY = this.create("potted_coba_berry", BlocksInvoker.createFlowerPotBlock(COBA_BERRY))
@JvmField
val POTTED_COLBUR_BERRY = this.create("potted_colbur_berry", BlocksInvoker.createFlowerPotBlock(COLBUR_BERRY))
@JvmField
val POTTED_CORNN_BERRY = this.create("potted_cornn_berry", BlocksInvoker.createFlowerPotBlock(CORNN_BERRY))
@JvmField
val POTTED_CUSTAP_BERRY = this.create("potted_custap_berry", BlocksInvoker.createFlowerPotBlock(CUSTAP_BERRY))
@JvmField
val POTTED_DURIN_BERRY = this.create("potted_durin_berry", BlocksInvoker.createFlowerPotBlock(DURIN_BERRY))
@JvmField
val POTTED_EGGANT_BERRY = this.create("potted_eggant_berry", BlocksInvoker.createFlowerPotBlock(EGGANT_BERRY))
@JvmField
val POTTED_ENIGMA_BERRY = this.create("potted_enigma_berry", BlocksInvoker.createFlowerPotBlock(ENIGMA_BERRY))
@JvmField
val POTTED_FIGY_BERRY = this.create("potted_figy_berry", BlocksInvoker.createFlowerPotBlock(FIGY_BERRY))
@JvmField
val POTTED_GANLON_BERRY = this.create("potted_ganlon_berry", BlocksInvoker.createFlowerPotBlock(GANLON_BERRY))
@JvmField
val POTTED_GREPA_BERRY = this.create("potted_grepa_berry", BlocksInvoker.createFlowerPotBlock(GREPA_BERRY))
@JvmField
val POTTED_HABAN_BERRY = this.create("potted_haban_berry", BlocksInvoker.createFlowerPotBlock(HABAN_BERRY))
@JvmField
val POTTED_HONDEW_BERRY = this.create("potted_hondew_berry", BlocksInvoker.createFlowerPotBlock(HONDEW_BERRY))
@JvmField
val POTTED_HOPO_BERRY = this.create("potted_hopo_berry", BlocksInvoker.createFlowerPotBlock(HOPO_BERRY))
@JvmField
val POTTED_IAPAPA_BERRY = this.create("potted_iapapa_berry", BlocksInvoker.createFlowerPotBlock(IAPAPA_BERRY))
@JvmField
val POTTED_JABOCA_BERRY = this.create("potted_jaboca_berry", BlocksInvoker.createFlowerPotBlock(JABOCA_BERRY))
@JvmField
val POTTED_KASIB_BERRY = this.create("potted_kasib_berry", BlocksInvoker.createFlowerPotBlock(KASIB_BERRY))
@JvmField
val POTTED_KEBIA_BERRY = this.create("potted_kebia_berry", BlocksInvoker.createFlowerPotBlock(KEBIA_BERRY))
@JvmField
val POTTED_KEE_BERRY = this.create("potted_kee_berry", BlocksInvoker.createFlowerPotBlock(KEE_BERRY))
@JvmField
val POTTED_KELPSY_BERRY = this.create("potted_kelpsy_berry", BlocksInvoker.createFlowerPotBlock(KELPSY_BERRY))
@JvmField
val POTTED_LANSAT_BERRY = this.create("potted_lansat_berry", BlocksInvoker.createFlowerPotBlock(LANSAT_BERRY))
@JvmField
val POTTED_LEPPA_BERRY = this.create("potted_leppa_berry", BlocksInvoker.createFlowerPotBlock(LEPPA_BERRY))
@JvmField
val POTTED_LIECHI_BERRY = this.create("potted_liechi_berry", BlocksInvoker.createFlowerPotBlock(LIECHI_BERRY))
@JvmField
val POTTED_LUM_BERRY = this.create("potted_lum_berry", BlocksInvoker.createFlowerPotBlock(LUM_BERRY))
@JvmField
val POTTED_MAGO_BERRY = this.create("potted_mago_berry", BlocksInvoker.createFlowerPotBlock(MAGO_BERRY))
@JvmField
val POTTED_MAGOST_BERRY = this.create("potted_magost_berry", BlocksInvoker.createFlowerPotBlock(MAGOST_BERRY))
@JvmField
val POTTED_MARANGA_BERRY = this.create("potted_maranga_berry", BlocksInvoker.createFlowerPotBlock(MARANGA_BERRY))
@JvmField
val POTTED_MICLE_BERRY = this.create("potted_micle_berry", BlocksInvoker.createFlowerPotBlock(MICLE_BERRY))
@JvmField
val POTTED_NANAB_BERRY = this.create("potted_nanab_berry", BlocksInvoker.createFlowerPotBlock(NANAB_BERRY))
@JvmField
val POTTED_NOMEL_BERRY = this.create("potted_nomel_berry", BlocksInvoker.createFlowerPotBlock(NOMEL_BERRY))
@JvmField
val POTTED_OCCA_BERRY = this.create("potted_occa_berry", BlocksInvoker.createFlowerPotBlock(OCCA_BERRY))
@JvmField
val POTTED_ORAN_BERRY = this.create("potted_oran_berry", BlocksInvoker.createFlowerPotBlock(ORAN_BERRY))
@JvmField
val POTTED_PAMTRE_BERRY = this.create("potted_pamtre_berry", BlocksInvoker.createFlowerPotBlock(PAMTRE_BERRY))
@JvmField
val POTTED_PASSHO_BERRY = this.create("potted_passho_berry", BlocksInvoker.createFlowerPotBlock(PASSHO_BERRY))
@JvmField
val POTTED_PAYAPA_BERRY = this.create("potted_payapa_berry", BlocksInvoker.createFlowerPotBlock(PAYAPA_BERRY))
@JvmField
val POTTED_PECHA_BERRY = this.create("potted_pecha_berry", BlocksInvoker.createFlowerPotBlock(PECHA_BERRY))
@JvmField
val POTTED_PERSIM_BERRY = this.create("potted_persim_berry", BlocksInvoker.createFlowerPotBlock(PERSIM_BERRY))
@JvmField
val POTTED_PETAYA_BERRY = this.create("potted_petaya_berry", BlocksInvoker.createFlowerPotBlock(PETAYA_BERRY))
@JvmField
val POTTED_PINAP_BERRY = this.create("potted_pinap_berry", BlocksInvoker.createFlowerPotBlock(PINAP_BERRY))
@JvmField
val POTTED_POMEG_BERRY = this.create("potted_pomeg_berry", BlocksInvoker.createFlowerPotBlock(POMEG_BERRY))
@JvmField
val POTTED_QUALOT_BERRY = this.create("potted_qualot_berry", BlocksInvoker.createFlowerPotBlock(QUALOT_BERRY))
@JvmField
val POTTED_RABUTA_BERRY = this.create("potted_rabuta_berry", BlocksInvoker.createFlowerPotBlock(RABUTA_BERRY))
@JvmField
val POTTED_RAWST_BERRY = this.create("potted_rawst_berry", BlocksInvoker.createFlowerPotBlock(RAWST_BERRY))
@JvmField
val POTTED_RAZZ_BERRY = this.create("potted_razz_berry", BlocksInvoker.createFlowerPotBlock(RAZZ_BERRY))
@JvmField
val POTTED_RINDO_BERRY = this.create("potted_rindo_berry", BlocksInvoker.createFlowerPotBlock(RINDO_BERRY))
@JvmField
val POTTED_ROSELI_BERRY = this.create("potted_roseli_berry", BlocksInvoker.createFlowerPotBlock(ROSELI_BERRY))
@JvmField
val POTTED_ROWAP_BERRY = this.create("potted_rowap_berry", BlocksInvoker.createFlowerPotBlock(ROWAP_BERRY))
@JvmField
val POTTED_SALAC_BERRY = this.create("potted_salac_berry", BlocksInvoker.createFlowerPotBlock(SALAC_BERRY))
@JvmField
val POTTED_SHUCA_BERRY = this.create("potted_shuca_berry", BlocksInvoker.createFlowerPotBlock(SHUCA_BERRY))
@JvmField
val POTTED_SITRUS_BERRY = this.create("potted_sitrus_berry", BlocksInvoker.createFlowerPotBlock(SITRUS_BERRY))
@JvmField
val POTTED_SPELON_BERRY = this.create("potted_spelon_berry", BlocksInvoker.createFlowerPotBlock(SPELON_BERRY))
@JvmField
val POTTED_STARF_BERRY = this.create("potted_starf_berry", BlocksInvoker.createFlowerPotBlock(STARF_BERRY))
@JvmField
val POTTED_TAMATO_BERRY = this.create("potted_tamato_berry", BlocksInvoker.createFlowerPotBlock(TAMATO_BERRY))
@JvmField
val POTTED_TANGA_BERRY = this.create("potted_tanga_berry", BlocksInvoker.createFlowerPotBlock(TANGA_BERRY))
@JvmField
val POTTED_TOUGA_BERRY = this.create("potted_touga_berry", BlocksInvoker.createFlowerPotBlock(TOUGA_BERRY))
@JvmField
val POTTED_WACAN_BERRY = this.create("potted_wacan_berry", BlocksInvoker.createFlowerPotBlock(WACAN_BERRY))
@JvmField
val POTTED_WATMEL_BERRY = this.create("potted_watmel_berry", BlocksInvoker.createFlowerPotBlock(WATMEL_BERRY))
@JvmField
val POTTED_WEPEAR_BERRY = this.create("potted_wepear_berry", BlocksInvoker.createFlowerPotBlock(WEPEAR_BERRY))
@JvmField
val POTTED_WIKI_BERRY = this.create("potted_wiki_berry", BlocksInvoker.createFlowerPotBlock(WIKI_BERRY))
@JvmField
val POTTED_YACHE_BERRY = this.create("potted_yache_berry", BlocksInvoker.createFlowerPotBlock(YACHE_BERRY))
init {
/**
* Makes all blocks in array flammable by adding them to FireBlock's flammableRegistry.
@@ -1032,4 +1345,48 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
}
fun typeGemClusters() = typeGemClusters.toMap()
private fun pottedPlant(name: String, flower: Block) = this.create(name, BlocksInvoker.createFlowerPotBlock(flower))
private fun mintLitter(name: String, color: MapColor) = this.create(
name,
MintLitterBlock(
BlockBehaviour.Properties.of()
.sound(CobblemonSounds.MINT_SOUNDS)
.mapColor(color)
.noOcclusion()
.noCollission()
.pushReaction(PushReaction.DESTROY)
)
)
private fun terrainSeed(name: String, type: TerrainSeedBlock.TerrainSeedType) = this.create(
name,
TerrainSeedBlock(
type,
BlockBehaviour.Properties.of()
.pushReaction(PushReaction.DESTROY)
.ignitedByLava()
.mapColor(MapColor.PLANT)
.offsetType(BlockBehaviour.OffsetType.XZ)
.noCollission()
.randomTicks()
.instabreak()
)
)
private fun terrainPlant(name: String, lightLevel: (BlockState) -> Int) = this.create(
name,
TerrainPlantBlock(
BlockBehaviour.Properties.of()
.mapColor(MapColor.PLANT)
.noCollission()
.instabreak()
.sound(SoundType.GRASS)
.offsetType(BlockBehaviour.OffsetType.XZ)
.ignitedByLava()
.pushReaction(PushReaction.DESTROY)
.lightLevel(lightLevel)
)
)
}
@@ -12,6 +12,7 @@ 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
@@ -168,12 +169,16 @@ interface CobblemonImplementation {
* TODO
*
* @param identifier
* @param reloader
* @param reloaderFactory
* @param type
* @param dependencies
*/
fun registerResourceReloader(identifier: ResourceLocation, reloader: PreparableReloadListener, type: PackType, dependencies: Collection<ResourceLocation>)
fun registerResourceReloader(
identifier: ResourceLocation,
type: PackType,
dependencies: Collection<ResourceLocation>,
reloaderFactory: (HolderLookup.Provider) -> PreparableReloadListener
)
/**
* TODO
*
@@ -472,18 +472,18 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
// Evolution items
@JvmField val LINK_CABLE = create("link_cable", LinkCableItem())
@JvmField val DRAGON_SCALE = noSettingsItem("dragon_scale")
@JvmField val METAL_COAT = noSettingsItem("metal_coat")
@JvmField val UPGRADE = noSettingsItem("upgrade")
@JvmField val DUBIOUS_DISC = noSettingsItem("dubious_disc")
@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 DEEP_SEA_SCALE = noSettingsItem("deep_sea_scale")
@JvmField val DEEP_SEA_TOOTH = noSettingsItem("deep_sea_tooth")
@JvmField val ELECTIRIZER = noSettingsItem("electirizer")
@JvmField val MAGMARIZER = noSettingsItem("magmarizer")
@JvmField val ELECTIRIZER = itemNameBlockItem("electirizer", CobblemonBlocks.ELECTIRIZER)
@JvmField val MAGMARIZER = itemNameBlockItem("magmarizer", CobblemonBlocks.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 = noSettingsItem("sachet")
@JvmField val SACHET = itemNameBlockItem("sachet", CobblemonBlocks.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,22 +492,20 @@ 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 = 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 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 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 = noSettingsItem("galarica_cuff")
@JvmField val GALARICA_WREATH = noSettingsItem("galarica_wreath")
@JvmField val GALARICA_CUFF = itemNameBlockItem("galarica_cuff", CobblemonBlocks.GALARICA_CUFF)
@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")
@@ -651,17 +649,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))
val CALCIUM = create("calcium", VitaminItem(Stats.SPECIAL_ATTACK, CobblemonBlocks.CALCIUM))
@JvmField
val CARBOS = create("carbos", VitaminItem(Stats.SPEED))
val CARBOS = create("carbos", VitaminItem(Stats.SPEED, CobblemonBlocks.CARBOS))
@JvmField
val HP_UP = create("hp_up", VitaminItem(Stats.HP))
val HP_UP = create("hp_up", VitaminItem(Stats.HP, CobblemonBlocks.HP_UP))
@JvmField
val IRON = create("iron", VitaminItem(Stats.DEFENCE))
val IRON = create("iron", VitaminItem(Stats.DEFENCE, CobblemonBlocks.IRON))
@JvmField
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK))
val PROTEIN = create("protein", VitaminItem(Stats.ATTACK, CobblemonBlocks.PROTEIN))
@JvmField
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE))
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE, CobblemonBlocks.ZINC))
@JvmField
val HEALTH_MOCHI = create("health_mochi", MochiItem(Stats.HP))
@JvmField
@@ -713,7 +711,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 = noSettingsItem("medicinal_brew")
val MEDICINAL_BREW = itemNameBlockItem("medicinal_brew", CobblemonBlocks.MEDICINAL_BREW)
@JvmField
val REMEDY = compostableItem("remedy", RemedyItem(RemedyItem.NORMAL), 0.65f)
@JvmField
@@ -721,7 +719,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))))
val MOOMOO_MILK = create("moomoo_milk", MoomooMilk(Item.Properties().craftRemainder(Items.GLASS_BOTTLE).stacksTo((16)), CobblemonBlocks.MOOMOO_MILK))
@JvmField
val POTION = create("potion", PotionItem(PotionType.POTION, CobblemonBlocks.POTION))
@@ -743,34 +741,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))
val PP_UP = create("pp_up", PPUpItem(1, CobblemonBlocks.PP_UP))
@JvmField
val PP_MAX = create("pp_max", PPUpItem(3))
val PP_MAX = create("pp_max", PPUpItem(3, CobblemonBlocks.PP_MAX))
@JvmField
val RED_MINT_SEEDS = mintSeed("red", MintType.RED.getCropBlock())
@JvmField
val RED_MINT_LEAF = mintLeaf("red", MintLeafItem(MintType.RED))
val RED_MINT_LEAF = compostableItemNameBlockItem("red_mint_leaf", CobblemonBlocks.RED_MINT_LEAF_LITTER, 0.50f)
@JvmField
val BLUE_MINT_SEEDS = mintSeed("blue", MintType.BLUE.getCropBlock())
@JvmField
val BLUE_MINT_LEAF = mintLeaf("blue", MintLeafItem(MintType.BLUE))
val BLUE_MINT_LEAF = compostableItemNameBlockItem("blue_mint_leaf", CobblemonBlocks.BLUE_MINT_LEAF_LITTER, 0.50f)
@JvmField
val CYAN_MINT_SEEDS = mintSeed("cyan", MintType.CYAN.getCropBlock())
@JvmField
val CYAN_MINT_LEAF = mintLeaf("cyan", MintLeafItem(MintType.CYAN))
val CYAN_MINT_LEAF = compostableItemNameBlockItem("cyan_mint_leaf", CobblemonBlocks.CYAN_MINT_LEAF_LITTER, 0.50f)
@JvmField
val PINK_MINT_SEEDS = mintSeed("pink", MintType.PINK.getCropBlock())
@JvmField
val PINK_MINT_LEAF = mintLeaf("pink", MintLeafItem(MintType.PINK))
val PINK_MINT_LEAF = compostableItemNameBlockItem("pink_mint_leaf", CobblemonBlocks.PINK_MINT_LEAF_LITTER, 0.50f)
@JvmField
val GREEN_MINT_SEEDS = mintSeed("green", MintType.GREEN.getCropBlock())
@JvmField
val GREEN_MINT_LEAF = mintLeaf("green", MintLeafItem(MintType.GREEN))
val GREEN_MINT_LEAF = compostableItemNameBlockItem("green_mint_leaf", CobblemonBlocks.GREEN_MINT_LEAF_LITTER, 0.50f)
@JvmField
val WHITE_MINT_SEEDS = mintSeed("white", MintType.WHITE.getCropBlock())
@JvmField
val WHITE_MINT_LEAF = mintLeaf("white", MintLeafItem(MintType.WHITE))
val WHITE_MINT_LEAF = compostableItemNameBlockItem("white_mint_leaf", CobblemonBlocks.WHITE_MINT_LEAF_LITTER, 0.50f)
val mints = mutableMapOf<String, MintItem>()
@@ -922,11 +920,18 @@ 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 = wearableItem("kings_rock")
@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 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
@@ -954,21 +959,17 @@ 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 COVERT_CLOAK = heldItem("covert_cloak")
@JvmField
val DESTINY_KNOT = heldItem("destiny_knot")
val DESTINY_KNOT = itemNameBlockItem("destiny_knot", CobblemonBlocks.DESTINY_KNOT)
@JvmField
val DRAGON_FANG = heldItem("dragon_fang")
@JvmField
val EJECT_BUTTON = heldItem("eject_button")
val EJECT_BUTTON = itemNameBlockItem("eject_button", CobblemonBlocks.EJECT_BUTTON)
@JvmField
val EJECT_PACK = heldItem("eject_pack")
@JvmField
val ELECTRIC_SEED = heldItem("electric_seed")
val ELECTRIC_SEED = itemNameBlockItem("electric_seed", CobblemonBlocks.ELECTRIC_SEED)
@JvmField
val EVERSTONE = heldItem("everstone")
@JvmField
@@ -978,13 +979,13 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val FAIRY_FEATHER = heldItem("fairy_feather")
@JvmField
val FLAME_ORB = heldItem("flame_orb")
val FLAME_ORB = itemNameBlockItem("flame_orb", CobblemonBlocks.FLAME_ORB)
@JvmField
val FLOAT_STONE = heldItem("float_stone")
@JvmField
val FOCUS_SASH = heldItem("focus_sash")
@JvmField
val GRASSY_SEED = heldItem("grassy_seed")
val GRASSY_SEED = itemNameBlockItem("grassy_seed", CobblemonBlocks.GRASSY_SEED)
@JvmField
val GRIP_CLAW = heldItem("grip_claw")
@JvmField
@@ -992,34 +993,32 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val HEAVY_DUTY_BOOTS = heldItem("heavy_duty_boots")
@JvmField
val IRON_BALL = heldItem("iron_ball")
val IRON_BALL = itemNameBlockItem("iron_ball", CobblemonBlocks.IRON_BALL)
@JvmField
val LAGGING_TAIL = heldItem("lagging_tail")
@JvmField
val LEFTOVERS = compostableHeldItem("leftovers", null, 0.50f)
@JvmField
val LIFE_ORB = heldItem("life_orb")
val LIFE_ORB = itemNameBlockItem("life_orb", CobblemonBlocks.LIFE_ORB)
@JvmField
val LIGHT_BALL = heldItem("light_ball")
val LIGHT_BALL = itemNameBlockItem("light_ball", CobblemonBlocks.LIGHT_BALL)
@JvmField
val LIGHT_CLAY = heldItem("light_clay")
@JvmField
val LOADED_DICE = heldItem("loaded_dice")
val LOADED_DICE = itemNameBlockItem("loaded_dice", CobblemonBlocks.LOADED_DICE)
@JvmField
val LUCKY_EGG = heldItem("lucky_egg")
@JvmField
val LUMINOUS_MOSS = heldItem("luminous_moss")
val LUMINOUS_MOSS = itemNameBlockItem("luminous_moss", CobblemonBlocks.LUMINOUS_MOSS)
@JvmField
val MAGNET = heldItem("magnet")
val MAGNET = itemNameBlockItem("magnet", CobblemonBlocks.MAGNET)
@JvmField
val METRONOME = heldItem("metronome")
val METRONOME = itemNameBlockItem("metronome", CobblemonBlocks.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")
@@ -1034,7 +1033,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val POWER_WEIGHT = heldItem("power_weight")
@JvmField
val PSYCHIC_SEED = heldItem("psychic_seed")
val PSYCHIC_SEED = itemNameBlockItem("psychic_seed", CobblemonBlocks.PSYCHIC_SEED)
@JvmField
val PROTECTIVE_PADS = heldItem("protective_pads")
@JvmField
@@ -1044,13 +1043,11 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val RED_CARD = heldItem("red_card")
@JvmField
val RING_TARGET = heldItem("ring_target")
val RING_TARGET = itemNameBlockItem("ring_target", CobblemonBlocks.RING_TARGET)
@JvmField
val ROOM_SERVICE = heldItem("room_service")
@JvmField
val SCOPE_LENS = heldItem("scope_lens")
@JvmField
val SHARP_BEAK = heldItem("sharp_beak")
val SCOPE_LENS = itemNameBlockItem("scope_lens", CobblemonBlocks.SCOPE_LENS)
@JvmField
val SHED_SHELL = heldItem("shed_shell")
@JvmField
@@ -1060,21 +1057,19 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val SILVER_POWDER = heldItem("silver_powder")
@JvmField
val SOFT_SAND = heldItem("soft_sand")
val SOFT_SAND = itemNameBlockItem("soft_sand", CobblemonBlocks.SOFT_SAND)
@JvmField
val SPELL_TAG = itemNameBlockItem("spell_tag", CobblemonBlocks.SPELL_TAG)
@JvmField
val SMOKE_BALL = heldItem("smoke_ball")
val SMOKE_BALL = itemNameBlockItem("smoke_ball", CobblemonBlocks.SMOKE_BALL)
@JvmField
val SOOTHE_BELL = heldItem("soothe_bell")
@JvmField
val STICKY_BARB = heldItem("sticky_barb")
@JvmField
val TERRAIN_EXTENDER = heldItem("terrain_extender")
val TERRAIN_EXTENDER = itemNameBlockItem("terrain_extender", CobblemonBlocks.TERRAIN_EXTENDER)
@JvmField
val THROAT_SPRAY = heldItem("throat_spray")
val THROAT_SPRAY = itemNameBlockItem("throat_spray", CobblemonBlocks.THROAT_SPRAY)
@JvmField
val TOXIC_ORB = heldItem("toxic_orb")
val TOXIC_ORB = itemNameBlockItem("toxic_orb", CobblemonBlocks.TOXIC_ORB)
@JvmField
val TWISTED_SPOON = heldItem("twisted_spoon")
@JvmField
@@ -1082,33 +1077,33 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val WEAKNESS_POLICY = itemNameBlockItem("weakness_policy", CobblemonBlocks.WEAKNESS_POLICY)
@JvmField
val WIDE_LENS = heldItem("wide_lens")
val WIDE_LENS = itemNameBlockItem("wide_lens", CobblemonBlocks.WIDE_LENS)
@JvmField
val ZOOM_LENS = heldItem("zoom_lens")
val ZOOM_LENS = itemNameBlockItem("zoom_lens", CobblemonBlocks.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 = heldItem("misty_seed")
val MISTY_SEED = itemNameBlockItem("misty_seed", CobblemonBlocks.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 = heldItem("bright_powder")
val BRIGHT_POWDER = glowItem("bright_powder", CobblemonBlocks.BRIGHT_POWDER)
@JvmField
val METAL_POWDER = heldItem("metal_powder")
val METAL_POWDER = itemNameBlockItem("metal_powder", CobblemonBlocks.METAL_POWDER)
@JvmField
val QUICK_POWDER = heldItem("quick_powder")
val QUICK_POWDER = itemNameBlockItem("quick_powder", CobblemonBlocks.QUICK_POWDER)
@JvmField
val DAMP_ROCK = heldItem("damp_rock")
val DAMP_ROCK = itemNameBlockItem("damp_rock", CobblemonBlocks.DAMP_ROCK)
@JvmField
val HEAT_ROCK = heldItem("heat_rock")
val HEAT_ROCK = itemNameBlockItem("heat_rock", CobblemonBlocks.HEAT_ROCK)
@JvmField
val SMOOTH_ROCK = heldItem("smooth_rock")
val SMOOTH_ROCK = itemNameBlockItem("smooth_rock", CobblemonBlocks.SMOOTH_ROCK)
@JvmField
val ICY_ROCK = heldItem("icy_rock")
val ICY_ROCK = itemNameBlockItem("icy_rock", CobblemonBlocks.ICY_ROCK)
// Mulch
@JvmField
@@ -1563,6 +1558,25 @@ 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)))
@@ -1599,7 +1613,16 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
private fun wearableItem(name: String, heldItemRemappedName: String? = null): CobblemonItem = create(
name,
WearableItem(name).also {
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 {
wearables.add(it)
if (heldItemRemappedName != null) {
CobblemonHeldItemManager.registerRemap(it, heldItemRemappedName)
@@ -1756,4 +1779,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
private fun foodItem(nutrition: Int, saturationModifier: Float): Item {
return CobblemonItem(Item.Properties().food(FoodProperties.Builder().nutrition(nutrition).saturationModifier(saturationModifier).build()))
}
private fun glowItem(name: String, block: Block): GlowItem = this.create(name, GlowItem(block, Item.Properties()))
}
@@ -425,6 +425,7 @@ 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()))
@@ -148,6 +148,9 @@ 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
@@ -657,5 +660,95 @@ 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)))
}
@@ -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.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,6 +21,7 @@ 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
@@ -124,6 +125,12 @@ 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
@@ -251,6 +258,14 @@ open class PokemonBattle(
fun end() {
ended = true
val result = when {
winners.isNotEmpty() && showdownMessages.any { "capture" in it } -> BattleEndResult.CAPTURE
winners.isNotEmpty() && forfeitedBy != null -> BattleEndResult.FORFEIT
winners.isNotEmpty() -> BattleEndResult.VICTORY
fled -> BattleEndResult.FLED
else -> BattleEndResult.STOPPED
}
endResult = result
val awardToFainted = Cobblemon.config.awardExperienceToFaintedPokemon
val awardOnLoss = Cobblemon.config.awardExperienceOnBattleLoss
this.actors.forEach { actor ->
@@ -332,6 +347,7 @@ open class PokemonBattle(
}
}
}
CobblemonEvents.BATTLE_ENDED.post(BattleEndedEvent(this, result, forfeitedBy.takeIf { result == BattleEndResult.FORFEIT }))
sendUpdate(BattleEndPacket())
BattleRegistry.closeBattle(this)
}
@@ -502,6 +518,7 @@ 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()
}
}
@@ -539,6 +556,7 @@ 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
@@ -0,0 +1,93 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.data
import com.cobblemon.mod.common.util.endsWith
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import net.minecraft.core.HolderLookup
import java.io.BufferedReader
import java.io.File
import java.util.concurrent.ExecutionException
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.ResourceManager
/**
* A [DataRegistry] that consumes JSON files.
* Every deserialized instance is attached to an [Identifier].
* For example a file under data/mymod/[resourcePath]/entry.json would be backed by the identifier modid:entry.
*
* @param T The type of the data consumed by this registry.
*
* @author Licious
* @since August 5th, 2022
*/
interface ServerJsonDataRegistry<T> : DataRegistry {
/**
* The [TypeToken] of type [T].
*/
val typeToken: TypeToken<T>
/**
* The folder location for the data this registry will consume.
*/
val resourcePath: String
override val type: PackType
get() = PackType.SERVER_DATA
override fun reload(manager: ResourceManager) {
//NO-OP, handled by reload(manager, registry)
}
fun reload(manager: ResourceManager, registry: HolderLookup.Provider) {
val gson = setupParser(registry)
val data = hashMapOf<ResourceLocation, T>()
manager.listResources(resourcePath) { path -> path.endsWith(JSON_EXTENSION) }.forEach { (identifier, resource) ->
if (identifier.namespace == "pixelmon") {
return@forEach
}
resource.open().use { stream ->
stream.bufferedReader().use { reader ->
val resolvedIdentifier = ResourceLocation.fromNamespaceAndPath(identifier.namespace, File(identifier.path).nameWithoutExtension)
data[resolvedIdentifier] = parse(reader, resolvedIdentifier, gson)
}
}
}
reload(data, registry)
}
fun parse(reader: BufferedReader, identifier: ResourceLocation, gson: Gson): T {
return try {
gson.fromJson(reader, typeToken.type)
} catch (exception: Exception) {
throw ExecutionException("Error loading JSON for data: $identifier", exception)
}
}
/**
* Allows the creation of [Gson] instances which have adapters that are registry-aware, e.g. [com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter]
*/
fun setupParser(registry: HolderLookup.Provider): Gson
/**
* Reloads this registry from the deserialized data.
*
* @param data A map of the data associating an instance to the respective identifier from the [ResourceManager].
*/
fun reload(data: Map<ResourceLocation, T>, registry: HolderLookup.Provider)
companion object {
const val JSON_EXTENSION = ".json"
}
}
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.events.battles.BattleFaintedEvent
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
import com.cobblemon.mod.common.api.events.battles.BattleFleeAttemptEvent
import com.cobblemon.mod.common.api.events.battles.BattleStartedEvent
import com.cobblemon.mod.common.api.events.battles.BattleEndedEvent
import com.cobblemon.mod.common.api.events.battles.BattleVictoryEvent
import com.cobblemon.mod.common.api.events.battles.instruction.FormeChangeEvent
import com.cobblemon.mod.common.api.events.battles.instruction.MegaEvolutionEvent
@@ -139,6 +140,9 @@ object CobblemonEvents {
@JvmField
val BATTLE_FAINTED = EventObservable<BattleFaintedEvent>()
@JvmField
val BATTLE_ENDED = EventObservable<BattleEndedEvent>()
// instructions
@JvmField
val MEGA_EVOLUTION = EventObservable<MegaEvolutionEvent>()
@@ -0,0 +1,69 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.battles
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.battles.model.BattleEndResult
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.util.asArrayValue
/**
* Event fired whenever a [PokemonBattle] ends, regardless of how it ended (victory, fleeing, being stopped, etc).
*
* [PokemonBattle.winners] and [PokemonBattle.losers] are only populated if the [result] has a winner,
* that is [BattleEndResult.VICTORY], [BattleEndResult.CAPTURE] or [BattleEndResult.FORFEIT].
*
* @param result How the battle ended.
* @param forfeitedBy The actor that forfeited, if the [result] is [BattleEndResult.FORFEIT].
*/
data class BattleEndedEvent(
override val battle: PokemonBattle,
val result: BattleEndResult,
val forfeitedBy: BattleActor?
) : BattleEvent {
/** Whether one side won the battle, which includes captures and forfeits. */
val hasWinner = battle.winners.isNotEmpty()
val wasVictory = result == BattleEndResult.VICTORY
val wasCapture = result == BattleEndResult.CAPTURE
val wasForfeit = result == BattleEndResult.FORFEIT
val wasFled = result == BattleEndResult.FLED
val wasStopped = result == BattleEndResult.STOPPED
/** Whether the battle was between players and wild Pokémon. */
val isPvW = battle.isPvW
/** Whether the battle was between players and NPCs. */
val isPvN = battle.isPvN
/** Whether the battle was between players. */
val isPvP = battle.isPvP
val context = mutableMapOf<String, MoValue>(
"battle" to battle.struct,
"result" to StringValue(result.name.lowercase()),
"has_winner" to DoubleValue(hasWinner),
"was_victory" to DoubleValue(wasVictory),
"was_capture" to DoubleValue(wasCapture),
"was_forfeit" to DoubleValue(wasForfeit),
"was_fled" to DoubleValue(wasFled),
"was_stopped" to DoubleValue(wasStopped),
"is_pvw" to DoubleValue(isPvW),
"is_pvn" to DoubleValue(isPvN),
"is_pvp" to DoubleValue(isPvP),
"winners" to battle.winners.asArrayValue { it.struct },
"losers" to battle.losers.asArrayValue { it.struct },
"players" to battle.actors.filter { it.type == ActorType.PLAYER }.asArrayValue { it.struct },
"npcs" to battle.actors.filter { it.type == ActorType.NPC }.asArrayValue { it.struct },
"wild_pokemon" to battle.actors.filter { it.type == ActorType.WILD }.asArrayValue { it.struct }
).apply {
forfeitedBy?.let { put("forfeited_by", it.struct) }
}
}
@@ -21,7 +21,8 @@ import com.cobblemon.mod.common.pokemon.Pokemon
*/
data class FriendshipUpdatedEvent(
val pokemon: Pokemon,
val newFriendshipInitial: Int
val newFriendshipInitial: Int,
val earned: Boolean = false
) {
var newFriendship: Int = newFriendshipInitial
set(value) {
@@ -40,7 +40,7 @@ object Fossils: JsonDataRegistry<Fossil> {
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter())
.create()
override val typeToken: TypeToken<Fossil> = TypeToken.get(Fossil::class.java)
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.gui
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import net.minecraft.client.gui.components.AbstractWidget
import net.minecraft.client.gui.components.Renderable
import net.minecraft.client.gui.components.events.GuiEventListener
import net.minecraft.client.gui.narration.NarrationElementOutput
import net.minecraft.network.chat.Component
@@ -55,6 +54,8 @@ abstract class ParentWidget(
}
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
if (!active) return false
return children.any {
it.mouseClicked(pMouseX, pMouseY, pButton)
}
@@ -0,0 +1,116 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.gui
import kotlin.math.max
import kotlin.math.min
interface TreeNode<T> {
val children: List<TreeNode<T>>
}
class TreeDrawingNode<T>(
val value: T,
val children: List<TreeDrawingNode<T>>,
) {
var x = 0
var y = 0
var offset = 0
}
fun <T> createDrawingTree(treeNode: TreeNode<T>): TreeDrawingNode<T> {
val root = setup(treeNode, 0, mutableListOf<Int>(), mutableListOf<Int>())
addMods(root, 0)
return root
}
@Suppress("UNCHECKED_CAST")
private fun <T> setup(
treeNode: TreeNode<T>,
depth: Int,
nexts: MutableList<Int>,
offset: MutableList<Int>,
): TreeDrawingNode<T> {
val children = treeNode.children.map { child ->
setup(child, depth + 1, nexts, offset)
}
val treeDrawingNode = TreeDrawingNode<T>(treeNode as T, children)
treeDrawingNode.y = depth
while (nexts.size <= depth) {
nexts.add(0)
}
while (offset.size <= depth) {
offset.add(0)
}
var place =
if (children.isEmpty()) {
nexts[depth]
} else if (children.size == 1) {
// children.first().x - 1
children.first().x
} else {
// (children[0].x + children[1].x) / 2
(children.first().x + children.last().x) / 2
}
offset[depth] = max(offset[depth], nexts[depth] - place)
if (children.isEmpty()) {
treeDrawingNode.x = place
} else {
treeDrawingNode.x = place + offset[depth]
}
nexts[depth] += 2
treeDrawingNode.offset = offset[depth]
return treeDrawingNode
}
private fun <T> addMods(
treeNode: TreeDrawingNode<T>,
modSum: Int,
) {
treeNode.x += modSum
val newModSum = modSum + treeNode.offset
treeNode.children.forEach { child ->
addMods(child, newModSum)
}
}
data class TreeDrawingBounds(val minX: Int, val maxX: Int, val minY: Int, val maxY: Int) {
val width = maxX - minX
val height = maxY - minY
val xCenter = (maxX + minX) / 2.0
val yCenter = (maxY + minY) / 2.0
}
fun <T> calculateBounds(treeDrawingNode: TreeDrawingNode<T>): TreeDrawingBounds {
var minX = treeDrawingNode.x
var maxX = treeDrawingNode.x
var minY = treeDrawingNode.y
var maxY = treeDrawingNode.y
for (child in treeDrawingNode.children) {
val childBounds = calculateBounds(child)
minX = min(minX, childBounds.minX)
maxX = max(maxX, childBounds.maxX)
minY = min(minY, childBounds.minY)
maxY = max(maxY, childBounds.maxY)
}
return TreeDrawingBounds(minX, maxX, minY, maxY)
}
@@ -10,7 +10,7 @@ package com.cobblemon.mod.common.api.interaction
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.data.ServerJsonDataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
@@ -26,6 +26,7 @@ import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.advancements.critereon.ItemPredicate
import net.minecraft.core.HolderLookup
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
@@ -36,28 +37,31 @@ import net.minecraft.world.level.block.Block
import net.minecraft.world.level.levelgen.structure.Structure
import net.minecraft.world.level.material.Fluid
object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
object PokemonInteractions : ServerJsonDataRegistry<PokemonInteractionSet> {
override val id = cobblemonResource("pokemon_interactions")
override val type = PackType.SERVER_DATA
override fun setupParser(registry: HolderLookup.Provider): Gson {
return GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter(registry))
.registerTypeAdapter(Requirement::class.java, CobblemonRequirementAdapter)
.registerTypeAdapter(InteractionEffect::class.java, InteractionEffectAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Structure::class.java).type, StructureLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory())
.setPrettyPrinting()
.create()
}
override val observable = SimpleObservable<PokemonInteractions>()
override val typeToken: TypeToken<PokemonInteractionSet> = TypeToken.get(PokemonInteractionSet::class.java)
override val resourcePath = "pokemon_interactions"
override val gson: Gson = GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(PokemonProperties::class.java, pokemonPropertiesShortAdapter)
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(Requirement::class.java, CobblemonRequirementAdapter)
.registerTypeAdapter(InteractionEffect::class.java, InteractionEffectAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Item::class.java).type, ItemLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Structure::class.java).type, StructureLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
.registerTypeAdapterFactory(LowerCaseEnumTypeAdapterFactory())
.setPrettyPrinting()
.create()
val interactions = mutableListOf<PokemonInteractionSet>()
@@ -65,7 +69,7 @@ object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
PokemonInteractionsSyncPacket(interactions).sendToPlayer(player)
}
override fun reload(data: Map<ResourceLocation, PokemonInteractionSet>) {
override fun reload(data: Map<ResourceLocation, PokemonInteractionSet>, registry: HolderLookup.Provider) {
interactions.clear()
val split = data.entries
.partition {
@@ -87,4 +91,4 @@ object PokemonInteractions : JsonDataRegistry<PokemonInteractionSet> {
.flatMap { it.interactions }
.firstOrNull(interactionCheck)
}
}
}
@@ -83,12 +83,6 @@ import net.minecraft.world.level.Level
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.dimension.DimensionType
import net.minecraft.world.level.levelgen.Heightmap
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.level.storage.loot.LootParams
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets
import net.minecraft.world.level.storage.loot.parameters.LootContextParams
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import java.util.function.Function
@@ -99,25 +93,31 @@ import java.util.function.Function
* @since October 2nd, 2023
*/
object MoLangFunctions {
fun ItemStack.asMoLangValue(registryAccess: RegistryAccess) = ObjectValue(this)
.addStandardFunctions()
.addFunctions(ItemStackMoLangFunctions.attach(Pair(this, registryAccess)))
fun ItemStack.asMoLangValue(registryAccess: RegistryAccess) =
ObjectValue(this)
.addStandardFunctions()
.addFunctions(ItemStackMoLangFunctions.attach(Pair(this, registryAccess)))
fun Holder<Biome>.asBiomeMoLangValue() = asMoLangValue(Registries.BIOME)
.addFunctions(BiomeMoLangFunctions.attach(this))
fun Holder<Biome>.asBiomeMoLangValue() =
asMoLangValue(Registries.BIOME)
.addFunctions(BiomeMoLangFunctions.attach(this))
fun Holder<Level>.asWorldMoLangValue() = asMoLangValue(Registries.DIMENSION)
.addFunctions(WorldMoLangFunctions.attach(this))
fun Holder<Level>.asWorldMoLangValue() =
asMoLangValue(Registries.DIMENSION)
.addFunctions(WorldMoLangFunctions.attach(this))
fun Holder<Block>.asBlockMoLangValue() = asMoLangValue(Registries.BLOCK)
.addFunctions(BlockMoLangFunctions.attach(this))
fun Holder<Block>.asBlockMoLangValue() =
asMoLangValue(Registries.BLOCK)
.addFunctions(BlockMoLangFunctions.attach(this))
fun MinecraftServer.asMoLangValue() = ObjectValue(this)
.addStandardFunctions()
.addFunctions(ServerMoLangFunctions.attach(this))
fun MinecraftServer.asMoLangValue() =
ObjectValue(this)
.addStandardFunctions()
.addFunctions(ServerMoLangFunctions.attach(this))
fun Holder<DimensionType>.asDimensionTypeMoLangValue() = asMoLangValue(Registries.DIMENSION_TYPE)
.addFunctions(DimensionTypeMoLangFunctions.attach(this))
fun Holder<DimensionType>.asDimensionTypeMoLangValue() =
asMoLangValue(Registries.DIMENSION_TYPE)
.addFunctions(DimensionTypeMoLangFunctions.attach(this))
fun Player.asMoLangValue(): ObjectValue<Player> {
if (this is ServerPlayer && uuid in Cobblemon.serverPlayerStructs) {
@@ -129,10 +129,11 @@ object MoLangFunctions {
}
}
val value = ObjectValue(
obj = this,
stringify = { it.effectiveName().string }
)
val value =
ObjectValue(
obj = this,
stringify = { it.effectiveName().string },
)
value.addFunctions(EntityMoLangFunctions.attach(this))
value.addFunctions(LivingEntityMoLangFunctions.attach(this))
@@ -153,57 +154,63 @@ object MoLangFunctions {
}
fun PartyStore.asMoLangValue(): ObjectValue<PartyStore> {
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
val value =
ObjectValue(
obj = this,
stringify = { it.toString() },
)
value.addFunctions(PokemonStoreMoLangFunctions.attach(this))
value.addFunctions(PartyMoLangFunctions.attach(this))
return value
}
fun DropEntry.asMoLangValue(): ObjectValue<DropEntry> {
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
val value =
ObjectValue(
obj = this,
stringify = { it.toString() },
)
value.addFunctions(DropEntryMoLangFunctions.attach(this))
return value
}
fun PokemonProperties.asMoLangValue(): ObjectValue<PokemonProperties> {
val value = ObjectValue(
obj = this,
stringify = { it.asString() }
)
val value =
ObjectValue(
obj = this,
stringify = { it.asString() },
)
value.addFunctions(PokemonPropertiesMoLangFunctions.attach(this))
return value
}
fun Evolution.asMoLangValue(): ObjectValue<Evolution> {
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
val value =
ObjectValue(
obj = this,
stringify = { it.toString() },
)
value.addFunctions(EvolutionMoLangFunctions.attach(this))
return value
}
fun PCStore.asMoLangValue(): ObjectValue<PCStore> {
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
val value =
ObjectValue(
obj = this,
stringify = { it.toString() },
)
value.addFunctions(PokemonStoreMoLangFunctions.attach(this))
value.addFunctions(PCMoLangFunctions.attach(this))
return value
}
fun NPCEntity.asMoLangValue(): ObjectValue<NPCEntity> {
val value = ObjectValue(
obj = this,
stringify = { it.name.string }
)
val value =
ObjectValue(
obj = this,
stringify = { it.name.string },
)
value.addFunctions(EntityMoLangFunctions.attach(this))
value.addFunctions(LivingEntityMoLangFunctions.attach(this))
value.addFunctions(NPCMoLangFunctions.attach(this))
@@ -211,10 +218,11 @@ object MoLangFunctions {
}
fun PokemonEntity.asMoLangValue(): ObjectValue<PokemonEntity> {
val value = ObjectValue(
obj = this,
stringify = { it.pokemon.uuid.toString() }
)
val value =
ObjectValue(
obj = this,
stringify = { it.pokemon.uuid.toString() },
)
value.addFunctions(EntityMoLangFunctions.attach(this))
value.addFunctions(LivingEntityMoLangFunctions.attach(this))
@@ -225,36 +233,40 @@ object MoLangFunctions {
}
fun PokemonBattle.asMoLangValue(): ObjectValue<PokemonBattle> {
val value = ObjectValue(
obj = this,
stringify = { it.battleId.toString() }
)
val value =
ObjectValue(
obj = this,
stringify = { it.battleId.toString() },
)
value.addFunctions(BattleMoLangFunctions.attach(this))
return value
}
fun BattleActor.asMoLangValue(): ObjectValue<BattleActor> {
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
val value =
ObjectValue(
obj = this,
stringify = { it.toString() },
)
value.addFunctions(BattleActorMoLangFunctions.attach(this))
return value
}
fun SpawnablePosition.asMoLangValue(): ObjectValue<SpawnablePosition> {
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
val value =
ObjectValue(
obj = this,
stringify = { it.toString() },
)
value.addFunctions(SpawnablePositionMoLangFunctions.attach(this))
return value
}
fun Vec3.asMoLangValue(): ObjectValue<Vec3> {
val value = ObjectValue(
obj = this
)
val value =
ObjectValue(
obj = this,
)
value.addFunction("x") { this.x }
value.addFunction("y") { this.y }
value.addFunction("z") { this.z }
@@ -265,45 +277,66 @@ object MoLangFunctions {
/**
* Different functions exist depending on the entity type, this tries to make the struct that's most specific to the type.
*/
fun Entity.asMostSpecificMoLangValue(): ObjectValue<out Entity> {
return when (this) {
is Player -> asMoLangValue()
is PokemonEntity -> struct
is NPCEntity -> struct
is ItemEntity -> ObjectValue(this).also { item ->
item.addStandardFunctions()
item.addFunctions(EntityMoLangFunctions.attach(this))
item.addFunctions(ItemStackMoLangFunctions.attach(Pair(this.item, this.registryAccess())))
fun Entity.asMostSpecificMoLangValue(): ObjectValue<out Entity> =
when (this) {
is Player -> {
asMoLangValue()
}
else -> ObjectValue(this).also { objectValue ->
objectValue.addStandardFunctions()
objectValue.addFunctions(EntityMoLangFunctions.attach(this))
if (this is LivingEntity) {
objectValue.addFunctions(LivingEntityMoLangFunctions.attach(this))
is PokemonEntity -> {
struct
}
is NPCEntity -> {
struct
}
is ItemEntity -> {
ObjectValue(this).also { item ->
item.addStandardFunctions()
item.addFunctions(EntityMoLangFunctions.attach(this))
item.addFunctions(ItemStackMoLangFunctions.attach(Pair(this.item, this.registryAccess())))
}
}
else -> {
ObjectValue(this).also { objectValue ->
objectValue.addStandardFunctions()
objectValue.addFunctions(EntityMoLangFunctions.attach(this))
if (this is LivingEntity) {
objectValue.addFunctions(LivingEntityMoLangFunctions.attach(this))
}
}
}
}
}
fun <T> Holder<T>.asMoLangValue(key: ResourceKey<Registry<T>>): ObjectValue<Holder<T>> {
val value = ObjectValue(
obj = this,
stringify = { it.unwrapKey().get().location().toString() }
)
value.functions["is_in"] = Function put@{ params: MoParams ->
val tag = TagKey.create(key, ResourceLocation.parse(params.getString(0).replace("#", "")))
return@put DoubleValue(if (value.obj.`is`(tag)) 1.0 else 0.0)
}
value.functions["is_of"] = Function put@{ params: MoParams ->
val identifier = ResourceLocation.parse(params.getString(0))
return@put DoubleValue(if (value.obj.`is`(identifier)) 1.0 else 0.0)
}
val value =
ObjectValue(
obj = this,
stringify = {
it
.unwrapKey()
.get()
.location()
.toString()
},
)
value.functions["is_in"] =
Function put@{ params: MoParams ->
val tag = TagKey.create(key, ResourceLocation.parse(params.getString(0).replace("#", "")))
return@put DoubleValue(if (value.obj.`is`(tag)) 1.0 else 0.0)
}
value.functions["is_of"] =
Function put@{ params: MoParams ->
val identifier = ResourceLocation.parse(params.getString(0))
return@put DoubleValue(if (value.obj.`is`(identifier)) 1.0 else 0.0)
}
return value
}
fun QueryStruct.addStandardFunctions(): QueryStruct {
addFunctions(GeneralMoLangFunctions.holder)
addFunctions(GeneralMoLangFunctions.functions)
return this
}
@@ -370,23 +403,17 @@ object MoLangFunctions {
return this
}
fun moLangFunctionMap(
vararg functions: Pair<String, (MoParams) -> MoValue>
): Map<String, (MoParams) -> MoValue> {
return functions.toMap()
}
fun moLangFunctionMap(vararg functions: Pair<String, (MoParams) -> MoValue>): Map<String, (MoParams) -> MoValue> = functions.toMap()
fun queryStructOf(
vararg functions: Pair<String, (MoParams) -> MoValue>
): QueryStruct {
return QueryStruct(
fun queryStructOf(vararg functions: Pair<String, (MoParams) -> MoValue>): QueryStruct =
QueryStruct(
hashMapOf<String, Function<MoParams, Any>>(
*functions.map { (name, func) ->
name to Function<MoParams, Any> { params -> func(params) }
}.toTypedArray()
)
*functions
.map { (name, func) ->
name to Function<MoParams, Any> { params -> func(params) }
}.toTypedArray(),
),
)
}
fun MoLangRuntime.setup(): MoLangRuntime {
environment.query.addStandardFunctions()
@@ -395,8 +422,14 @@ object MoLangFunctions {
fun writeMoValueToNBT(value: MoValue): Tag? {
return when (value) {
is DoubleValue -> DoubleTag.valueOf(value.value)
is StringValue -> StringTag.valueOf(value.value)
is DoubleValue -> {
DoubleTag.valueOf(value.value)
}
is StringValue -> {
StringTag.valueOf(value.value)
}
is ArrayStruct -> {
val list = value.map.values
val nbtList = ListTag()
@@ -413,14 +446,22 @@ object MoLangFunctions {
nbt
}
else -> null
else -> {
null
}
}
}
fun readMoValueFromNBT(nbt: Tag): MoValue {
return when (nbt) {
is DoubleTag -> DoubleValue(nbt.asDouble)
is StringTag -> StringValue(nbt.asString)
fun readMoValueFromNBT(nbt: Tag): MoValue =
when (nbt) {
is DoubleTag -> {
DoubleValue(nbt.asDouble)
}
is StringTag -> {
StringValue(nbt.asString)
}
is ListTag -> {
val array = ArrayStruct(hashMapOf())
var index = 0
@@ -441,11 +482,12 @@ object MoLangFunctions {
variable
}
else -> VariableStruct()
else -> {
VariableStruct()
}
}
}
}
// TODO: this one is unused. Should we move it to [MoLangFunctions] object?
fun Either<ResourceLocation, ExpressionLike>.runScript(runtime: MoLangRuntime) =
map({ CobblemonScripts.run(it, runtime) }, { it.resolve(runtime) })
map({ CobblemonScripts.run(it, runtime) }, { it.resolve(runtime) })
@@ -16,31 +16,32 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.util.asArrayValue
object BattleActorMoLangFunctions : AbstractMoLangFunctionHolder<BattleActor>() {
override fun BattleActor.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val actor = this
val map = mutableMapOf<String, (MoParams) -> Any>(
"is_npc" to { DoubleValue(actor.type == ActorType.NPC) },
"is_player" to { DoubleValue(actor.type == ActorType.PLAYER) },
"is_wild" to { DoubleValue(actor.type == ActorType.WILD) }
)
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["is_npc"] = { DoubleValue(actor.type == ActorType.NPC) }
functions["is_player"] = { DoubleValue(actor.type == ActorType.PLAYER) }
functions["is_wild"] = { DoubleValue(actor.type == ActorType.WILD) }
functions["battle_pokemon"] = { actor.pokemonList.asArrayValue { it.struct } }
when (actor) {
is NPCBattleActor -> {
map["npc"] = { actor.entity.struct }
functions["npc"] = { actor.entity.struct }
}
is PlayerBattleActor -> {
map["player"] = { actor.entity?.asMoLangValue() ?: DoubleValue.ZERO }
functions["player"] = { actor.entity?.asMoLangValue() ?: DoubleValue.ZERO }
}
is PokemonBattleActor -> {
map["pokemon"] = { actor.entity?.asMoLangValue() ?: DoubleValue.ZERO }
functions["pokemon"] = { actor.entity?.asMoLangValue() ?: DoubleValue.ZERO }
}
}
return map
return functions
}
}
@@ -16,17 +16,19 @@ import com.cobblemon.mod.common.util.getStringOrNull
object BattleMessageMoLangFunctions : AbstractMoLangFunctionHolder<BattleMessage>() {
override fun BattleMessage.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val battleMessage = this
return mutableMapOf(
"has_argument_at" to { params ->
val index = params.getInt(0)
val value = params.getStringOrNull(1) ?: ""
DoubleValue(value == battleMessage.argumentAt(index))
},
"has_argument" to { params ->
val argumentName = params.getString(0)
val value = params.getStringOrNull(1) ?: ""
DoubleValue(value == battleMessage.optionalArgument(argumentName))
}
)
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["has_argument_at"] = { params ->
val index = params.getInt(0)
val value = params.getStringOrNull(1) ?: ""
DoubleValue(value == battleMessage.argumentAt(index))
}
functions["has_argument"] = { params ->
val argumentName = params.getString(0)
val value = params.getStringOrNull(1) ?: ""
DoubleValue(value == battleMessage.optionalArgument(argumentName))
}
return functions
}
}
@@ -18,20 +18,22 @@ import java.util.*
object BattleMoLangFunctions : AbstractMoLangFunctionHolder<PokemonBattle>() {
override fun PokemonBattle.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val battle = this
return mutableMapOf(
"environment" to { battle.runtime.environment },
"battle_id" to { StringValue(battle.battleId.toString()) },
"battle_type" to { StringValue(battle.format.toString()) },
"is_pvn" to { DoubleValue(battle.isPvN) },
"is_pvp" to { DoubleValue(battle.isPvP) },
"is_pvw" to { DoubleValue(battle.isPvW) },
"stop" to { battle.stop() },
"actors" to { battle.actors.toList().asArrayValue { it.struct } },
"get_actor" to getActor@{ params ->
val uuid = UUID.fromString(params.getString(0))
val actor = battle.actors.find { it.uuid == uuid } ?: return@getActor DoubleValue.ZERO
actor.struct
}
)
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["environment"] = { battle.runtime.environment }
functions["battle_id"] = { StringValue(battle.battleId.toString()) }
functions["battle_type"] = { StringValue(battle.format.toString()) }
functions["is_pvn"] = { DoubleValue(battle.isPvN) }
functions["is_pvp"] = { DoubleValue(battle.isPvP) }
functions["is_pvw"] = { DoubleValue(battle.isPvW) }
functions["stop"] = { battle.stop() }
functions["actors"] = { battle.actors.toList().asArrayValue { it.struct } }
functions["get_actor"] = getActor@{ params ->
val uuid = UUID.fromString(params.getString(0))
val actor = battle.actors.find { it.uuid == uuid } ?: return@getActor DoubleValue.ZERO
actor.struct
}
return functions
}
}
@@ -12,8 +12,11 @@ import com.bedrockk.molang.runtime.MoParams
import net.minecraft.core.Holder
import net.minecraft.world.level.biome.Biome
object BiomeMoLangFunctions: AbstractMoLangFunctionHolder<Holder<Biome>>() {
object BiomeMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Biome>>() {
override fun Holder<Biome>.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
return mutableMapOf()
val biome = this
val functions = hashMapOf<String, (MoParams) -> Any>()
return functions
}
}
@@ -12,8 +12,11 @@ import com.bedrockk.molang.runtime.MoParams
import net.minecraft.core.Holder
import net.minecraft.world.level.block.Block
object BlockMoLangFunctions: AbstractMoLangFunctionHolder<Holder<Block>>() {
object BlockMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Block>>() {
override fun Holder<Block>.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
return mutableMapOf()
val block = this
val functions = hashMapOf<String, (MoParams) -> Any>()
return functions
}
}
@@ -14,6 +14,9 @@ import net.minecraft.world.level.dimension.DimensionType
object DimensionTypeMoLangFunctions : AbstractMoLangFunctionHolder<Holder<DimensionType>>() {
override fun Holder<DimensionType>.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
return mutableMapOf()
val dimension = this
val functions = hashMapOf<String, (MoParams) -> Any>()
return functions
}
}
@@ -18,15 +18,16 @@ import com.cobblemon.mod.common.util.getOrNull
object DropEntryMoLangFunctions : AbstractMoLangFunctionHolder<DropEntry>() {
override fun DropEntry.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val drop = this
val map = mutableMapOf<String, (MoParams) -> Any>()
val functions = hashMapOf<String, (MoParams) -> Any>()
map["percentage"] = { DoubleValue(drop.percentage.toDouble()) }
map["quantity"] = { DoubleValue(drop.quantity.toDouble()) }
map["max_selectable_times"] = { DoubleValue(drop.maxSelectableTimes.toDouble()) }
map["can_drop"] = { params ->
functions["percentage"] = { DoubleValue(drop.percentage.toDouble()) }
functions["quantity"] = { DoubleValue(drop.quantity.toDouble()) }
functions["max_selectable_times"] = { DoubleValue(drop.maxSelectableTimes.toDouble()) }
functions["can_drop"] = { params ->
val pokemon = params.getOrNull<ObjectValue<Pokemon>>(0)?.obj
DoubleValue(if (drop.canDrop(pokemon)) 1.0 else 0.0)
}
return map
return functions
}
}
@@ -56,258 +56,300 @@ import kotlin.math.sqrt
object EntityMoLangFunctions : AbstractMoLangFunctionHolder<Entity>() {
override fun Entity.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val thisEntity = this
val map = mutableMapOf<String, (MoParams) -> Any>(
"uuid" to { StringValue(thisEntity.uuid.toString()) },
"name" to { _ -> StringValue(thisEntity.effectiveName().string) },
"yaw" to { _ -> DoubleValue(thisEntity.yRot.toDouble()) },
"pitch" to { _ -> DoubleValue(thisEntity.xRot.toDouble()) },
"x" to { _ -> DoubleValue(thisEntity.x) },
"y" to { _ -> DoubleValue(thisEntity.y) },
"z" to { _ -> DoubleValue(thisEntity.z) },
"velocity_x" to { _ -> DoubleValue(thisEntity.deltaMovement.x) },
"velocity_y" to { _ -> DoubleValue(thisEntity.deltaMovement.y) },
"velocity_z" to { _ -> DoubleValue(thisEntity.deltaMovement.z) },
"width" to { DoubleValue(thisEntity.boundingBox.xsize) },
"height" to { DoubleValue(thisEntity.boundingBox.ysize) },
"this_size" to { DoubleValue(thisEntity.boundingBox.run { if (xsize > ysize) xsize else ysize }) },
"this_width" to { DoubleValue(thisEntity.boundingBox.xsize) },
"this_height" to { DoubleValue(thisEntity.boundingBox.ysize) },
"id_modulo" to { params -> DoubleValue(thisEntity.uuid.hashCode() % params.getDouble(0)) },
"horizontal_velocity" to { _ -> DoubleValue(thisEntity.deltaMovement.horizontalDistance()) },
"vertical_velocity" to { DoubleValue(thisEntity.deltaMovement.y) },
"is_on_ground" to { _ -> DoubleValue(thisEntity.onGround()) },
"world" to { _ -> thisEntity.level().worldRegistry.wrapAsHolder(thisEntity.level()).asWorldMoLangValue() },
"biome" to { _ -> thisEntity.level().getBiome(thisEntity.blockPosition()).asBiomeMoLangValue() },
"is_passenger" to { DoubleValue(thisEntity.isPassenger) },
"discard" to { thisEntity.discard() },
"is_sneaking" to { _ -> DoubleValue(thisEntity.isShiftKeyDown) },
"is_sprinting" to { _ -> DoubleValue(thisEntity.isSprinting) },
"is_in_water" to { _ -> DoubleValue(thisEntity.isUnderWater) },
"is_in_rain" to { _ -> DoubleValue(thisEntity.isInWaterOrRain && !thisEntity.isInWater) },
"is_touching_water_or_rain" to { _ -> DoubleValue(thisEntity.isInWaterRainOrBubble) },
"is_touching_water" to { _ -> DoubleValue(thisEntity.isInWater) },
"is_underwater" to { DoubleValue(thisEntity.getIsSubmerged()) },
"is_in_lava" to { _ -> DoubleValue(thisEntity.isInLava) },
"is_on_fire" to { _ -> DoubleValue(thisEntity.isOnFire) },
"is_invisible" to { _ -> DoubleValue(thisEntity.isInvisible) },
"is_riding" to { _ -> DoubleValue(thisEntity.isPassenger) },
"set_name" to { params ->
val name = params.getString(0)
thisEntity.customName = name.text()
DoubleValue.ONE
},
"damage" to { params ->
val amount = params.getDouble(0)
val source = DamageSource(
thisEntity.level().registryAccess().registryOrThrow(Registries.DAMAGE_TYPE)
.getHolder(DamageTypes.GENERIC).get()
val entity = this
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["uuid"] = { StringValue(entity.uuid.toString()) }
functions["name"] = { StringValue(entity.effectiveName().string) }
functions["yaw"] = { DoubleValue(entity.yRot.toDouble()) }
functions["pitch"] = { DoubleValue(entity.xRot.toDouble()) }
functions["x"] = { DoubleValue(entity.x) }
functions["y"] = { DoubleValue(entity.y) }
functions["z"] = { DoubleValue(entity.z) }
functions["velocity_x"] = { DoubleValue(entity.deltaMovement.x) }
functions["velocity_y"] = { DoubleValue(entity.deltaMovement.y) }
functions["velocity_z"] = { DoubleValue(entity.deltaMovement.z) }
functions["width"] = { DoubleValue(entity.boundingBox.xsize) }
functions["height"] = { DoubleValue(entity.boundingBox.ysize) }
functions["this_size"] = { DoubleValue(entity.boundingBox.run { if (xsize > ysize) xsize else ysize }) }
functions["this_width"] = { DoubleValue(entity.boundingBox.xsize) }
functions["this_height"] = { DoubleValue(entity.boundingBox.ysize) }
functions["id_modulo"] = { params -> DoubleValue(entity.uuid.hashCode() % params.getDouble(0)) }
functions["horizontal_velocity"] = { DoubleValue(entity.deltaMovement.horizontalDistance()) }
functions["vertical_velocity"] = { DoubleValue(entity.deltaMovement.y) }
functions["is_on_ground"] = { DoubleValue(entity.onGround()) }
functions["world"] = {
entity
.level()
.worldRegistry
.wrapAsHolder(entity.level())
.asWorldMoLangValue()
}
functions["biome"] = { entity.level().getBiome(entity.blockPosition()).asBiomeMoLangValue() }
functions["is_passenger"] = { DoubleValue(entity.isPassenger) }
functions["discard"] = { entity.discard() }
functions["is_sneaking"] = { DoubleValue(entity.isShiftKeyDown) }
functions["is_sprinting"] = { DoubleValue(entity.isSprinting) }
functions["is_in_water"] = { DoubleValue(entity.isUnderWater) }
functions["is_in_rain"] = { DoubleValue(entity.isInWaterOrRain && !entity.isInWater) }
functions["is_touching_water_or_rain"] = { DoubleValue(entity.isInWaterRainOrBubble) }
functions["is_touching_water"] = { DoubleValue(entity.isInWater) }
functions["is_underwater"] = { DoubleValue(entity.getIsSubmerged()) }
functions["is_in_lava"] = { DoubleValue(entity.isInLava) }
functions["is_on_fire"] = { DoubleValue(entity.isOnFire) }
functions["is_invisible"] = { DoubleValue(entity.isInvisible) }
functions["is_riding"] = { DoubleValue(entity.isPassenger) }
functions["set_name"] = { params ->
val name = params.getString(0)
entity.customName = name.text()
DoubleValue.ONE
}
functions["damage"] = { params ->
val amount = params.getDouble(0)
val source =
DamageSource(
entity
.level()
.registryAccess()
.registryOrThrow(Registries.DAMAGE_TYPE)
.getHolder(DamageTypes.GENERIC)
.get(),
)
thisEntity.hurt(source, amount.toFloat())
},
"distance_to_owner" to distanceToOwner@{
if (thisEntity !is TamableAnimal) {
return@distanceToOwner DoubleValue.ZERO
}
val owner = thisEntity.owner ?: return@distanceToOwner DoubleValue.ZERO
DoubleValue(thisEntity.distanceTo(owner))
},
"owner" to owner@{
if (thisEntity !is TamableAnimal) return@owner DoubleValue.ZERO
val owner = thisEntity.owner
owner?.asMostSpecificMoLangValue() ?: DoubleValue.ZERO
},
"delta_movement" to {
listOf(thisEntity.deltaMovement.x, thisEntity.deltaMovement.y, thisEntity.deltaMovement.z)
.asArrayValue(::DoubleValue)
},
"tags" to {
val tags = thisEntity.tags
val array = ArrayStruct(hashMapOf())
tags.forEachIndexed { index, tag -> array.setDirectly("$index", StringValue(tag)) }
array
},
"add_tag" to { params ->
val tag = params.getString(0)
thisEntity.addTag(tag)
DoubleValue.ONE
},
"remove_tag" to { params ->
val tag = params.getString(0)
thisEntity.removeTag(tag)
DoubleValue.ONE
},
"has_tag" to { params ->
val tag = params.getString(0)
DoubleValue(thisEntity.tags.contains(tag))
},
"distance_to_pos" to { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
DoubleValue(sqrt(thisEntity.distanceToSqr(Vec3(x, y, z))))
},
"type" to { _ ->
thisEntity.registryAccess().registry(Registries.ENTITY_TYPE).get().getKey(thisEntity.type)?.toString()?.let {
entity.hurt(source, amount.toFloat())
}
functions["distance_to_owner"] = distanceToOwner@{
if (entity !is TamableAnimal) {
return@distanceToOwner DoubleValue.ZERO
}
val owner = entity.owner ?: return@distanceToOwner DoubleValue.ZERO
DoubleValue(entity.distanceTo(owner))
}
functions["owner"] = owner@{
if (entity !is TamableAnimal) return@owner DoubleValue.ZERO
val owner = entity.owner
owner?.asMostSpecificMoLangValue() ?: DoubleValue.ZERO
}
functions["delta_movement"] = {
listOf(entity.deltaMovement.x, entity.deltaMovement.y, entity.deltaMovement.z)
.asArrayValue(::DoubleValue)
}
functions["tags"] = {
val tags = entity.tags
val array = ArrayStruct(hashMapOf())
tags.forEachIndexed { index, tag -> array.setDirectly("$index", StringValue(tag)) }
array
}
functions["add_tag"] = { params ->
val tag = params.getString(0)
entity.addTag(tag)
DoubleValue.ONE
}
functions["remove_tag"] = { params ->
val tag = params.getString(0)
entity.removeTag(tag)
DoubleValue.ONE
}
functions["has_tag"] = { params ->
val tag = params.getString(0)
DoubleValue(entity.tags.contains(tag))
}
functions["distance_to_pos"] = { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
DoubleValue(sqrt(entity.distanceToSqr(Vec3(x, y, z))))
}
functions["type"] = {
entity
.registryAccess()
.registry(Registries.ENTITY_TYPE)
.get()
.getKey(entity.type)
?.toString()
?.let {
StringValue(it)
} ?: DoubleValue.ZERO
},
"find_nearby_block" to findNearbyBlock@{ params ->
val input = params.getString(0)
val isTag = input.contains("#")
val type = input.replace("#", "").asIdentifierDefaultingNamespace(namespace = "minecraft")
val range = params.getDoubleOrNull(1) ?: 10
val blockPos = thisEntity.level().getBlockStatesWithPos(
AABB.ofSize(
thisEntity.position(),
range.toDouble(),
range.toDouble(),
range.toDouble()
)
)
.filter { blockPosPair ->
}
functions["find_nearby_block"] = findNearbyBlock@{ params ->
val input = params.getString(0)
val isTag = input.contains("#")
val type = input.replace("#", "").asIdentifierDefaultingNamespace(namespace = "minecraft")
val range = params.getDoubleOrNull(1) ?: 10
val blockPos =
entity
.level()
.getBlockStatesWithPos(
AABB.ofSize(
entity.position(),
range.toDouble(),
range.toDouble(),
range.toDouble(),
),
).filter { blockPosPair ->
blockPosPair.first.blockHolder.let {
if (isTag) it.`is`(
TagKey.create(
Registries.BLOCK,
type
if (isTag) {
it.`is`(
TagKey.create(
Registries.BLOCK,
type,
),
)
) else it.`is`(type)
} else {
it.`is`(type)
}
}
}
.minByOrNull { it.second.distSqr(thisEntity.blockPosition()) }
}.minByOrNull { it.second.distSqr(entity.blockPosition()) }
?.second
if (blockPos != null) {
return@findNearbyBlock ArrayStruct(
mapOf(
"0" to DoubleValue(blockPos.x),
"1" to DoubleValue(blockPos.y),
"2" to DoubleValue(blockPos.z)
)
)
}
return@findNearbyBlock DoubleValue.ZERO
},
"get_nearby_entities" to { params ->
val distance = params.getDouble(0)
val entities =
thisEntity.level().getEntities(thisEntity, AABB.ofSize(thisEntity.position(), distance, distance, distance))
if (blockPos != null) {
return@findNearbyBlock ArrayStruct(
mapOf(
"0" to DoubleValue(blockPos.x),
"1" to DoubleValue(blockPos.y),
"2" to DoubleValue(blockPos.z),
),
)
}
return@findNearbyBlock DoubleValue.ZERO
}
functions["get_nearby_entities"] = { params ->
val distance = params.getDouble(0)
val entities =
entity
.level()
.getEntities(entity, AABB.ofSize(entity.position(), distance, distance, distance))
entities
.filterIsInstance<Entity>()
.map { it.asMostSpecificMoLangValue() }
.asArrayValue()
},
"is_standing_on_blocks" to { params ->
val depth = params.getDouble(0).toInt()
val blockStrings: MutableSet<String> = mutableSetOf()
for (blockIndex in 1..<params.params.size) {
blockStrings.add(params.getString(blockIndex))
}
entities
.filterIsInstance<Entity>()
.map { it.asMostSpecificMoLangValue() }
.asArrayValue()
}
functions["is_standing_on_blocks"] = { params ->
val depth = params.getDouble(0).toInt()
val blockStrings: MutableSet<String> = mutableSetOf()
for (blockIndex in 1..<params.params.size) {
blockStrings.add(params.getString(blockIndex))
}
if (thisEntity.isStandingOn(blockStrings, depth)) DoubleValue.ONE else DoubleValue.ZERO
},
if (entity.isStandingOn(blockStrings, depth)) DoubleValue.ONE else DoubleValue.ZERO
}
// q.entity.spawn_bedrock_particles(effect, locator, [player])
// sends to everyone nearby or just to the player if they're set.
// Locator is necessary even if unused on non-posables.
"spawn_bedrock_particles" to { params ->
val particle = params.getString(0).asResource()
val locator = params.getString(1)
val player = params.getOrNull<MoValue>(2)?.let {
// q.entity.spawn_bedrock_particles(effect, locator, [player])
// sends to everyone nearby or just to the player if they're set.
// Locator is necessary even if unused on non-posables.
functions["spawn_bedrock_particles"] = { params ->
val particle = params.getString(0).asResource()
val locator = params.getString(1)
val player =
params.getOrNull<MoValue>(2)?.let {
when (it) {
is StringValue -> thisEntity.level().getPlayerByUUID(UUID.fromString(it.value))
is StringValue -> entity.level().getPlayerByUUID(UUID.fromString(it.value))
is ObjectValue<*> -> it.obj
else -> null
}
} as? ServerPlayer
val packet = SpawnSnowstormEntityParticlePacket(particle, thisEntity.id, listOf(locator))
if (player == null) {
packet.sendToPlayersAround(thisEntity.x, thisEntity.y, thisEntity.z, 64.0, thisEntity.level().dimension())
} else {
packet.sendToPlayer(player)
}
},
"make_intangible" to { params ->
val intangible = params.getBooleanOrNull(0) ?: true
thisEntity.noPhysics = intangible
DoubleValue.ONE
val packet = SpawnSnowstormEntityParticlePacket(particle, entity.id, listOf(locator))
if (player == null) {
packet.sendToPlayersAround(
entity.x,
entity.y,
entity.z,
64.0,
entity.level().dimension(),
)
} else {
packet.sendToPlayer(player)
}
)
}
functions["make_intangible"] = { params ->
val intangible = params.getBooleanOrNull(0) ?: true
entity.noPhysics = intangible
DoubleValue.ONE
}
if (thisEntity is PosableEntity) {
map["play_animation"] = playAnimation@{ params ->
val animation = params.getString(0)
val packet = PlayPosableAnimationPacket(thisEntity.id, setOf(animation), emptyList())
val target = params.getStringOrNull(1)
if (target != null) {
val targetPlayer = if (target.asUUID != null) {
thisEntity.level().getPlayerByUUID(target.asUUID!!) as ServerPlayer
} else if (thisEntity.level() is ServerLevel) {
thisEntity.level().server!!.playerList.getPlayerByName(target)
when (entity) {
is PosableEntity -> {
functions["play_animation"] = playAnimation@{ params ->
val animation = params.getString(0)
val packet = PlayPosableAnimationPacket(entity.id, setOf(animation), emptyList())
val target = params.getStringOrNull(1)
if (target != null) {
val targetPlayer =
if (target.asUUID != null) {
entity.level().getPlayerByUUID(target.asUUID!!) as ServerPlayer
} else if (entity.level() is ServerLevel) {
entity
.level()
.server!!
.playerList
.getPlayerByName(target)
} else {
null
}
if (targetPlayer != null) {
packet.sendToPlayer(targetPlayer)
return@playAnimation DoubleValue.ONE
} else {
return@playAnimation DoubleValue.ZERO
}
} else {
null
}
if (targetPlayer != null) {
packet.sendToPlayer(targetPlayer)
packet.sendToPlayersAround(
entity.x,
entity.y,
entity.z,
64.0,
entity.level().dimension(),
)
return@playAnimation DoubleValue.ONE
} else {
return@playAnimation DoubleValue.ZERO
}
} else {
packet.sendToPlayersAround(thisEntity.x, thisEntity.y, thisEntity.z, 64.0, thisEntity.level().dimension())
return@playAnimation DoubleValue.ONE
}
functions["face"] = { params ->
ObjectValue(ReferenceDialogueFaceProvider(entity.id, params.getBooleanOrNull(0) != false))
}
}
map["face"] = { params ->
ObjectValue(ReferenceDialogueFaceProvider(thisEntity.id, params.getBooleanOrNull(0) != false))
is Schedulable -> {
functions["run_molang_after"] = { params ->
val expression = params.getString(0).asExpressionLike()
val delayInSeconds = params.getDouble(1).toFloat()
val runtime = MoLangRuntime()
runtime.environment.cloneFrom(params.environment)
entity.after(delayInSeconds) {
runtime.resolve(expression)
}
}
}
}
if (thisEntity is Schedulable) {
map["run_molang_after"] = { params ->
val expression = params.getString(0).asExpressionLike()
val delayInSeconds = params.getDouble(1).toFloat()
val runtime = MoLangRuntime()
runtime.environment.cloneFrom(params.environment)
thisEntity.after(delayInSeconds) {
runtime.resolve(expression)
is MoLangScriptingEntity -> {
functions["add_callback"] = { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
val allowDuplicates = params.getBooleanOrNull(2) ?: false
entity.callbacks.addCallback(type, callback, allowDuplicates)
}
functions["remove_callback"] = { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
entity.callbacks.removeCallback(type, callback)
}
functions["has_callback"] = { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
DoubleValue(callback in (entity.callbacks[type] ?: emptySet()))
}
functions["clear_callbacks"] = { params ->
val type = params.getStringOrNull(0)?.asIdentifierDefaultingNamespace()
if (type != null) {
entity.callbacks[type]?.clear()
} else {
entity.callbacks.clear()
}
DoubleValue.ONE
}
}
}
if (thisEntity is MoLangScriptingEntity) {
map["add_callback"] = { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
val allowDuplicates = params.getBooleanOrNull(2) ?: false
thisEntity.callbacks.addCallback(type, callback, allowDuplicates)
}
map["remove_callback"] = { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
thisEntity.callbacks.removeCallback(type, callback)
}
map["has_callback"] = { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace()
val callback = params.getString(1).asIdentifierDefaultingNamespace()
DoubleValue(callback in (thisEntity.callbacks[type] ?: emptySet()))
}
map["clear_callbacks"] = { params ->
val type = params.getStringOrNull(0)?.asIdentifierDefaultingNamespace()
if (type != null) {
thisEntity.callbacks[type]?.clear()
} else {
thisEntity.callbacks.clear()
}
DoubleValue.ONE
}
}
return map
return functions
}
}
@@ -19,26 +19,26 @@ import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
object EvolutionMoLangFunctions : AbstractMoLangFunctionHolder<Evolution>() {
override fun Evolution.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val evolution = this
val map = hashMapOf<String, (MoParams) -> Any>()
val functions = hashMapOf<String, (MoParams) -> Any>()
map["result"] = { evolution.result.asMoLangValue() }
map["is_optional"] = { DoubleValue(evolution.optional) }
map["consumes_held_item"] = { DoubleValue(evolution.consumeHeldItem) }
functions["result"] = { evolution.result.asMoLangValue() }
functions["is_optional"] = { DoubleValue(evolution.optional) }
functions["consumes_held_item"] = { DoubleValue(evolution.consumeHeldItem) }
when (evolution) {
is LevelUpEvolution -> {
map["is_level_up"] = { DoubleValue.ONE }
functions["is_level_up"] = { DoubleValue.ONE }
}
is TradeEvolution -> {
map["is_trade"] = { DoubleValue.ONE }
functions["is_trade"] = { DoubleValue.ONE }
}
is ItemInteractionEvolution -> {
map["is_item"] = { DoubleValue.ONE }
functions["is_item"] = { DoubleValue.ONE }
}
}
return map
return functions
}
}
@@ -42,101 +42,104 @@ import net.minecraft.world.item.ItemStack
import java.text.ParseException
object GeneralMoLangFunctions {
val holder: HashMap<String, (MoParams) -> Any> = hashMapOf(
"print" to { params ->
val functions: MutableMap<String, (MoParams) -> Any> = mutableMapOf()
init {
functions["print"] = { params ->
val message = params.get<MoValue>(0).asString()
LOGGER.info(message)
},
"delete_variable" to deleteVar@{ params ->
}
functions["delete_variable"] = { params ->
val struct = params.get<VariableStruct>(0)
val variable = params.getString(1)
struct.map.remove(variable)
return@deleteVar DoubleValue.ONE
},
"delete_variables" to { params ->
DoubleValue.ONE
}
functions["delete_variables"] = { params ->
val struct = params.get<VariableStruct>(0)
struct.map.clear()
DoubleValue.ONE
},
"get_variable" to { params ->
}
functions["get_variable"] = { params ->
val struct = params.get<VariableStruct>(0)
val variable = params.getString(1)
struct.map[variable] ?: DoubleValue.ZERO
},
"set_variable" to { params ->
}
functions["set_variable"] = { params ->
val struct = params.get<VariableStruct>(0)
val variable = params.getString(1)
val value = params.get<MoValue>(2)
struct.map[variable] = value
value
},
"set_query" to { params ->
}
functions["set_query"] = { params ->
val variable = params.getString(0)
val value = params.get<MoValue>(1)
params.environment.query.addFunction(variable) { value }
value
},
"replace" to { params ->
}
functions["replace"] = { params ->
val text = params.getString(0)
val search = params.getString(1)
val replace = params.getString(2)
StringValue(text.replace(search, replace))
},
"is_included" to { params ->
}
functions["is_included"] = { params ->
val text = params.getString(0)
val search = params.getString(1)
DoubleValue(text.contains(search))
},
"to_lower" to { params ->
}
functions["to_lower"] = { params ->
StringValue(params.getString(0).lowercase())
},
"to_upper" to { params ->
}
functions["to_upper"] = { params ->
StringValue(params.getString(0).uppercase())
},
"string_length" to { params ->
}
functions["string_length"] = { params ->
DoubleValue(params.getString(0).length)
},
"split_string" to { params ->
}
functions["split_string"] = { params ->
val text = params.getString(0)
val delimiter = params.getString(1)
val parts = text.split(delimiter).map { StringValue(it) }
val struct = ArrayStruct(hashMapOf())
parts.forEachIndexed { index, moValue -> struct.setDirectly("$index", moValue) }
struct
},
"is_blank" to { params ->
}
functions["is_blank"] = { params ->
val arg = params.get<MoValue>(0)
DoubleValue((arg is StringValue && (arg.value.isBlank() || arg.value.toDoubleOrNull() == 0.0)) || (arg is DoubleValue && arg.value == 0.0))
},
"run_command" to { params ->
DoubleValue(
(arg is StringValue && (arg.value.isBlank() || arg.value.toDoubleOrNull() == 0.0)) ||
(arg is DoubleValue && arg.value == 0.0),
)
}
functions["run_command"] = { params ->
val command = params.getString(0)
try {
server()!!.commands.performPrefixedCommand(server()!!.createCommandSourceStack(), command)
} catch (_: NullPointerException) {
DoubleValue.ZERO
}
},
"is_int" to { params -> DoubleValue(params.get<MoValue>(0).asString().isInt()) },
"is_number" to { params ->
}
functions["is_int"] = { params -> DoubleValue(params.get<MoValue>(0).asString().isInt()) }
functions["is_number"] = { params ->
DoubleValue(
params.get<MoValue>(0).asString().toDoubleOrNull() != null
params.get<MoValue>(0).asString().toDoubleOrNull() != null,
)
},
"to_number" to { params ->
}
functions["to_number"] = { params ->
DoubleValue(
params.get<MoValue>(0).asString().toDoubleOrNull() ?: 0.0
params.get<MoValue>(0).asString().toDoubleOrNull() ?: 0.0,
)
},
"to_int" to { params ->
}
functions["to_int"] = { params ->
DoubleValue(
params.get<MoValue>(0).asString().toIntOrNull() ?: 0
params.get<MoValue>(0).asString().toIntOrNull() ?: 0,
)
},
"to_string" to { params -> StringValue(params.get<MoValue>(0).asString()) },
"do_effect_walks" to { _ ->
DoubleValue(Cobblemon.config.walkingInBattleAnimations)
},
"random" to { params ->
}
functions["to_string"] = { params -> StringValue(params.get<MoValue>(0).asString()) }
functions["do_effect_walks"] = { DoubleValue(Cobblemon.config.walkingInBattleAnimations) }
functions["random"] = { params ->
val options = mutableListOf<MoValue>()
var index = 0
while (params.contains(index)) {
@@ -144,35 +147,35 @@ object GeneralMoLangFunctions {
index++
}
options.random() // Can throw an exception if they specified no args. They'd be idiots though.
},
"curve" to { params ->
}
functions["curve"] = { params ->
val curveName = params.getString(0)
val curve =
WaveFunctions.functions[curveName] ?: throw IllegalArgumentException("Unknown curve: $curveName")
ObjectValue(curve)
},
"array" to { params ->
}
functions["array"] = { params ->
val values = params.params
val array = ArrayStruct(hashMapOf())
values.forEachIndexed { index, moValue -> array.setDirectly("$index", moValue) }
array
},
"length" to { params ->
}
functions["length"] = { params ->
val array = (params.getOrNull<MoValue>(0) as? VariableStruct)
if (array == null) {
DoubleValue.ZERO
} else {
DoubleValue(array.map.size.toDouble())
}
},
"append" to { params ->
}
functions["append"] = { params ->
val array = params.get<ArrayStruct>(0)
val value = params.get<MoValue>(1)
val nextIndex = array.map.size
array.setDirectly("$nextIndex", value)
array
},
"insert" to { params ->
}
functions["insert"] = { params ->
val array = params.get<ArrayStruct>(0)
val index = params.getInt(1)
val value = params.get<MoValue>(2)
@@ -187,36 +190,40 @@ object GeneralMoLangFunctions {
}
array.map[index.toString()] = value
array
},
"delete" to { params ->
}
functions["delete"] = { params ->
val array = params.get<ArrayStruct>(0)
val index = params.getInt(1)
if (index in 0 until array.map.size) {
array.map.remove(index.toString())
// Re-index the array to keep keys sequential in numerical order
val newMap = hashMapOf<String, MoValue>()
array.map.keys.mapNotNull { it.toIntOrNull() }
array.map.keys
.mapNotNull { it.toIntOrNull() }
.sorted()
.forEachIndexed { i, k -> newMap[i.toString()] = array.map[k.toString()]!! }
array.map.clear()
array.map.putAll(newMap)
}
array
},
"run_script" to { params ->
}
functions["run_script"] = { params ->
val runtime = MoLangRuntime()
runtime.environment.query = params.environment.query
runtime.environment.variable = params.environment.variable
val args = params.params.subList(1, params.params.size)
runtime.environment.context = ContextStruct(
(params.environment.context?.map
?: emptyMap()) + args.mapIndexed { index, value -> "arg_${index + 1}" to value }.toMap()
)
runtime.environment.context =
ContextStruct(
(
params.environment.context?.map
?: emptyMap()
) + args.mapIndexed { index, value -> "arg_${index + 1}" to value }.toMap(),
)
val script = params.getString(0).asIdentifierDefaultingNamespace()
// store the args in the
CobblemonScripts.run(script, runtime) ?: DoubleValue.ZERO
},
"run_molang" to { params ->
}
functions["run_molang"] = { params ->
val runtime = MoLangRuntime()
runtime.environment.query = params.environment.query
runtime.environment.variable = params.environment.variable
@@ -224,17 +231,16 @@ object GeneralMoLangFunctions {
val expression = params.getString(0).asExpressionLike()
val delayInSeconds = params.getDoubleOrNull(1)?.toFloat() ?: 0.0f
fun evaluate(): MoValue {
return try {
fun evaluate(): MoValue =
try {
runtime.resolve(expression)
} catch (ex: Exception) {
LOGGER.warn(
"Failed to evaluate MoLang expression${if (delayInSeconds > 0.0f) " (delayed)" else ""}: $expression",
ex
ex,
)
DoubleValue.ZERO
}
}
if (delayInSeconds > 0.0f) {
val tracker =
@@ -246,55 +252,55 @@ object GeneralMoLangFunctions {
} else {
evaluate()
}
},
"system_time_millis" to { _ ->
DoubleValue(System.currentTimeMillis())
},
}
functions["system_time_millis"] = { DoubleValue(System.currentTimeMillis()) }
// the rest of the world use dd/MM/yyyy grow up america (this comment was generated by copilot)
"date_local_time" to { _ ->
functions["date_local_time"] = {
val time = System.currentTimeMillis()
val date = java.util.Date(time)
val formatted = java.text.SimpleDateFormat("dd/MM/yyyy").format(date)
StringValue(formatted)
},
"date_of" to { params ->
}
functions["date_of"] = { params ->
val time = params.getDouble(0).toLong()
val date = java.util.Date(time)
val formatted = java.text.SimpleDateFormat("dd/MM/yyyy").format(date)
StringValue(formatted)
},
"date_is_after" to { params ->
}
functions["date_is_after"] = { params ->
val dateA = params.getString(0)
val dateB = params.getString(1)
val format = java.text.SimpleDateFormat("dd/MM/yyyy")
val a = try {
format.parse(dateA)
} catch (_: ParseException) {
throw RuntimeException("Cannot parse date ${dateA}, expected it to be in dd/MM/yyyy format, other provided")
}
val b = try {
format.parse(dateB)
} catch (_: ParseException) {
throw RuntimeException("Cannot parse date ${dateB}, expected it to be in dd/MM/yyyy format, other provided")
}
val a =
try {
format.parse(dateA)
} catch (_: ParseException) {
throw RuntimeException("Cannot parse date $dateA, expected it to be in dd/MM/yyyy format, other provided")
}
val b =
try {
format.parse(dateB)
} catch (_: ParseException) {
throw RuntimeException("Cannot parse date $dateB, expected it to be in dd/MM/yyyy format, other provided")
}
DoubleValue(a.after(b))
},
"get_move_from_id" to { params ->
}
functions["get_move_from_id"] = { params ->
val moveId = params.getString(0)
val moveTemplate = Moves.getByName(moveId)
moveTemplate?.struct ?: DoubleValue.ZERO
},
"create_simple_party_provider" to { _ ->
}
functions["create_simple_party_provider"] = {
val partyProvider = SimplePartyProvider()
partyProvider.struct
},
"create_pickup_item" to { params ->
}
functions["create_pickup_item"] = { params ->
val item = params.getOrNull<MoValue>(0)?.asString()?.let(ObtainableItemCondition::parseFromString)
val pickupPriority = params.getIntOrNull(1) ?: 0
val pickupItem = ObtainableItem(item = item, pickupPriority = pickupPriority)
pickupItem.struct
},
"create_itemstack" to { params ->
}
functions["create_itemstack"] = { params ->
val itemId = params.getString(0).asIdentifierDefaultingNamespace()
try {
val item = BuiltInRegistries.ITEM.get(itemId)
@@ -305,7 +311,7 @@ object GeneralMoLangFunctions {
LOGGER.error("Cannot create item stack from identifier: $itemId, item with that ID does not exist. Possible typo?")
DoubleValue.ZERO
}
},
"file" to { MoLangLoadedFilesCache.struct }
)
}
functions["file"] = { MoLangLoadedFilesCache.struct }
}
}
@@ -26,43 +26,44 @@ object ItemStackMoLangFunctions : AbstractMoLangFunctionHolder<Pair<ItemStack, R
val registryAccess = this.second
val itemRegistry = registryAccess.registryOrThrow(Registries.ITEM)
val holder = itemRegistry.wrapAsHolder(stack.item)
val functions = mutableMapOf<String, (MoParams) -> Any>()
val map = hashMapOf<String, (MoParams) -> Any>()
map["item"] = { _ -> holder.asMoLangValue(Registries.ITEM) }
map["count"] = { _ -> DoubleValue(stack.count.toDouble()) }
map["damage_value"] = { _ -> DoubleValue(stack.damageValue) }
map["max_damage"] = { _ -> DoubleValue(stack.maxDamage) }
map["is_empty"] = { _ -> DoubleValue(stack.isEmpty) }
map["shrink"] = { params -> stack.shrink(params.getInt(0)) }
map["grow"] = { params -> stack.grow(params.getInt(0)) }
map["is_of"] = { params ->
functions["item"] = { holder.asMoLangValue(Registries.ITEM) }
functions["count"] = { DoubleValue(stack.count.toDouble()) }
functions["damage_value"] = { DoubleValue(stack.damageValue) }
functions["max_damage"] = { DoubleValue(stack.maxDamage) }
functions["is_empty"] = { DoubleValue(stack.isEmpty) }
functions["shrink"] = { params -> stack.shrink(params.getInt(0)) }
functions["grow"] = { params -> stack.grow(params.getInt(0)) }
functions["is_of"] = { params ->
DoubleValue(
holder.`is`(
params.getString(0).asIdentifierDefaultingNamespace()
)
params.getString(0).asIdentifierDefaultingNamespace(),
),
)
}
map["is_in"] = { params ->
functions["is_in"] = { params ->
DoubleValue(
holder.`is`(
TagKey.create(
Registries.ITEM,
params.getString(0).replace("#", "").asIdentifierDefaultingNamespace()
)
)
params.getString(0).replace("#", "").asIdentifierDefaultingNamespace(),
),
),
)
}
map["is_food"] = { _ -> DoubleValue(stack.has(DataComponents.FOOD)) }
map["is_enchanted"] = { _ -> DoubleValue(stack.hasFoil()) }
map["has_enchantment"] = put@{ params ->
functions["is_food"] = { DoubleValue(stack.has(DataComponents.FOOD)) }
functions["is_enchanted"] = { DoubleValue(stack.hasFoil()) }
functions["has_enchantment"] = hasEnchantment@{ params ->
val enchantmentId = params.getString(0).asIdentifierDefaultingNamespace()
val minLevel = params.getIntOrNull(1) ?: 1
val enchantmentRegistry = registryAccess.registryOrThrow(Registries.ENCHANTMENT)
val holder =
enchantmentRegistry.getHolder(enchantmentId).orElse(null) ?: return@put DoubleValue.ZERO
enchantmentRegistry.getHolder(enchantmentId).orElse(null) ?: return@hasEnchantment DoubleValue.ZERO
val level = EnchantmentHelper.getItemEnchantmentLevel(holder, stack)
return@put DoubleValue(level >= minLevel)
DoubleValue(level >= minLevel)
}
return map
return functions
}
}
@@ -53,26 +53,27 @@ import java.util.*
object LivingEntityMoLangFunctions : AbstractMoLangFunctionHolder<LivingEntity>() {
override fun LivingEntity.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val entity = this
val map = mutableMapOf<String, (MoParams) -> Any>()
map["is_player"] = { _ -> DoubleValue(entity is Player) }
map["is_npc"] = { _ -> DoubleValue(entity is NPCEntity) }
map["is_mob"] = { _ -> DoubleValue(entity is Mob) }
map["is_pokemon"] = { _ -> DoubleValue(entity is PokemonEntity) }
map["is_animal"] = { _ -> DoubleValue(entity is Animal) }
map["is_tamable"] = { _ -> DoubleValue(entity is TamableAnimal) }
map["is_tamed"] = { _ -> DoubleValue(entity is TamableAnimal && entity.isTame) }
map["is_hostile"] = { _ -> DoubleValue(entity is Monster) }
map["is_baby"] = { _ -> DoubleValue(entity.isBaby) }
map["is_adult"] = { _ -> DoubleValue(!entity.isBaby) }
map["remove_effect"] = put@{ params ->
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["is_player"] = { DoubleValue(entity is Player) }
functions["is_npc"] = { DoubleValue(entity is NPCEntity) }
functions["is_mob"] = { DoubleValue(entity is Mob) }
functions["is_pokemon"] = { DoubleValue(entity is PokemonEntity) }
functions["is_animal"] = { DoubleValue(entity is Animal) }
functions["is_tamable"] = { DoubleValue(entity is TamableAnimal) }
functions["is_tamed"] = { DoubleValue(entity is TamableAnimal && entity.isTame) }
functions["is_hostile"] = { DoubleValue(entity is Monster) }
functions["is_baby"] = { DoubleValue(entity.isBaby) }
functions["is_adult"] = { DoubleValue(!entity.isBaby) }
functions["remove_effect"] = removeEffect@{ params ->
val effectId = params.getString(0).asIdentifierDefaultingNamespace()
val effectHolder = BuiltInRegistries.MOB_EFFECT.getHolder(effectId).orElse(null)
if (effectHolder != null) {
entity.removeEffect(effectHolder)
return@put DoubleValue.ONE
return@removeEffect DoubleValue.ONE
}
}
map["add_effect"] = put@{ params ->
functions["add_effect"] = addEffect@{ params ->
val effectId = params.getString(0).asIdentifierDefaultingNamespace()
val duration = params.getInt(1)
val amplifier = params.getIntOrNull(2) ?: 0
@@ -81,36 +82,37 @@ object LivingEntityMoLangFunctions : AbstractMoLangFunctionHolder<LivingEntity>(
val effectHolder = BuiltInRegistries.MOB_EFFECT.getHolder(effectId).orElse(null)
if (effectHolder != null) {
entity.addEffect(MobEffectInstance(effectHolder, duration, amplifier, ambient, visible))
return@put DoubleValue.ONE
return@addEffect DoubleValue.ONE
}
}
map["has_effect"] = put@{ params ->
functions["has_effect"] = { params ->
val effectId = params.getString(0).asIdentifierDefaultingNamespace()
val effectHolder = BuiltInRegistries.MOB_EFFECT.getHolder(effectId).orElse(null)
return@put DoubleValue(if (effectHolder != null) entity.hasEffect(effectHolder) else false)
DoubleValue(effectHolder != null && entity.hasEffect(effectHolder))
}
map["heal"] = { params ->
functions["heal"] = { params ->
val amount = params.getDouble(0)
entity.heal(amount.toFloat())
}
map["is_looking_at"] = put@{ params ->
functions["is_looking_at"] = isLookingAt@{ params ->
val targetEntity = params.get<MoValue>(0)
val maxDistance = params.getDoubleOrNull(1)?.toFloat() ?: 2.0F
val entity = if (targetEntity is ObjectValue<*> && targetEntity.obj is Entity) {
targetEntity.obj as Entity
} else {
return@put DoubleValue.ZERO
}
val target =
if (targetEntity is ObjectValue<*> && targetEntity.obj is Entity) {
targetEntity.obj as Entity
} else {
return@isLookingAt DoubleValue.ZERO
}
return@put DoubleValue(entity.isLookingAt(entity, maxDistance))
DoubleValue(entity.isLookingAt(target, maxDistance))
}
map["is_living_entity"] = { DoubleValue.ONE }
map["is_flying"] = { _ -> DoubleValue(entity.isFallFlying) }
map["is_sleeping"] = { _ -> DoubleValue(entity.isSleeping) }
map["health"] = { _ -> DoubleValue(entity.health) }
map["max_health"] = { _ -> DoubleValue(entity.maxHealth) }
map["look_at_position"] = { params ->
functions["is_living_entity"] = { DoubleValue.ONE }
functions["is_flying"] = { DoubleValue(entity.isFallFlying) }
functions["is_sleeping"] = { DoubleValue(entity.isSleeping) }
functions["health"] = { DoubleValue(entity.health) }
functions["max_health"] = { DoubleValue(entity.maxHealth) }
functions["look_at_position"] = { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
@@ -120,159 +122,174 @@ object LivingEntityMoLangFunctions : AbstractMoLangFunctionHolder<LivingEntity>(
brain.setMemoryWithExpiry(
MemoryModuleType.LOOK_TARGET,
CobblemonBlockPosTracker(Vec3(x, y, z), flags.toSet()),
duration.toLong()
duration.toLong(),
)
}
map["get_wander_control_memory"] = put@{
val value = entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
?: CobblemonWanderControl()
functions["get_wander_control_memory"] = {
val value =
entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
?: CobblemonWanderControl()
if (
!entity.brain.checkMemory(CobblemonMemories.WANDER_CONTROL, MemoryStatus.VALUE_PRESENT)
&& entity.brain.checkMemory(CobblemonMemories.WANDER_CONTROL, MemoryStatus.REGISTERED)
!entity.brain.checkMemory(CobblemonMemories.WANDER_CONTROL, MemoryStatus.VALUE_PRESENT) &&
entity.brain.checkMemory(CobblemonMemories.WANDER_CONTROL, MemoryStatus.REGISTERED)
) {
entity.brain.setMemory(CobblemonMemories.WANDER_CONTROL, value)
}
return@put value.struct
value.struct
}
map["get_position_memory"] = put@{ params ->
functions["get_position_memory"] = getPosMemory@{ params ->
val id = params.getString(0).asIdentifierDefaultingNamespace()
val memoryType = BuiltInRegistries.MEMORY_MODULE_TYPE.get(id)
if (entity.brain.checkMemory(memoryType, MemoryStatus.VALUE_PRESENT)) {
return@put when (val memory = entity.brain.getMemory(memoryType).get()) {
is Vec3i -> VariableStruct(
mapOf(
"x" to DoubleValue(memory.x),
"y" to DoubleValue(memory.y),
"z" to DoubleValue(memory.z)
return@getPosMemory when (val memory = entity.brain.getMemory(memoryType).get()) {
is Vec3i -> {
VariableStruct(
mapOf(
"x" to DoubleValue(memory.x),
"y" to DoubleValue(memory.y),
"z" to DoubleValue(memory.z),
),
)
)
}
is Position -> VariableStruct(
mapOf(
"x" to DoubleValue(memory.x()),
"y" to DoubleValue(memory.y()),
"z" to DoubleValue(memory.z())
is Position -> {
VariableStruct(
mapOf(
"x" to DoubleValue(memory.x()),
"y" to DoubleValue(memory.y()),
"z" to DoubleValue(memory.z()),
),
)
)
}
else -> DoubleValue.ZERO
else -> {
DoubleValue.ZERO
}
}
} else {
return@put DoubleValue.ZERO
}
DoubleValue.ZERO
}
map["set_uuid_memory"] = put@{ params ->
val memory = params.getString(0).asIdentifierDefaultingNamespace()
.let(BuiltInRegistries.MEMORY_MODULE_TYPE::get) as MemoryModuleType<UUID>
val uuid = params.getString(1).asUUID ?: return@put DoubleValue.ZERO
functions["set_uuid_memory"] = setUuidMemory@{ params ->
val memory =
params
.getString(0)
.asIdentifierDefaultingNamespace()
.let(BuiltInRegistries.MEMORY_MODULE_TYPE::get) as MemoryModuleType<UUID>
val uuid = params.getString(1).asUUID ?: return@setUuidMemory DoubleValue.ZERO
val expiry = params.getIntOrNull(2) ?: -1
if (expiry != -1) {
entity.brain.setMemoryWithExpiry(memory, uuid, expiry.toLong())
} else {
entity.brain.setMemory(memory, uuid)
}
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["erase_memory"] = put@{ params ->
val memories = params.params.map { it.asString().asIdentifierDefaultingNamespace() }
.map(BuiltInRegistries.MEMORY_MODULE_TYPE::get)
functions["erase_memory"] = { params ->
val memories =
params.params
.map { it.asString().asIdentifierDefaultingNamespace() }
.map(BuiltInRegistries.MEMORY_MODULE_TYPE::get)
memories.forEach { entity.brain.eraseMemory(it) }
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["has_memory_value"] = put@{ params ->
functions["has_memory_value"] = hasMemoryValue@{ params ->
for (param in params.params) {
if (!entity.hasMemoryFromString(param.asString())) {
return@put DoubleValue.ZERO
return@hasMemoryValue DoubleValue.ZERO
}
}
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["lacks_memory_value"] = put@{ params ->
functions["lacks_memory_value"] = lacksMemoryValue@{ params ->
for (param in params.params) {
if (entity.hasMemoryFromString(param.asString())) {
return@put DoubleValue.ZERO
return@lacksMemoryValue DoubleValue.ZERO
}
}
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["is_doing_activity"] = put@{ params ->
functions["is_doing_activity"] = isDoingActivity@{ params ->
for (param in params.params) {
if (entity.isDoingActivityFromString(param.asString())) {
return@put DoubleValue.ONE
return@isDoingActivity DoubleValue.ONE
}
}
return@put DoubleValue.ZERO
DoubleValue.ZERO
}
map["can_see"] = put@{ params ->
functions["can_see"] = canSee@{ params ->
val target = params.get<MoValue>(0)
val range = params.getDoubleOrNull(1) ?: 32.0
if (target is ObjectValue<*>) {
val targetEntity = target.obj as? LivingEntity ?: return@put DoubleValue.ZERO
val targetEntity = target.obj as? LivingEntity ?: return@canSee DoubleValue.ZERO
val vector = targetEntity.eyePosition.subtract(entity.eyePosition).normalize()
val trace = entity.traceEntityCollision(
maxDistance = range.toFloat(),
stepDistance = 0.1F,
direction = vector,
entityClass = targetEntity.javaClass,
collideBlock = ClipContext.Fluid.NONE
)
return@put DoubleValue(targetEntity in (trace?.entities ?: emptyList()))
} else {
return@put DoubleValue.ZERO
val trace =
entity.traceEntityCollision(
maxDistance = range.toFloat(),
stepDistance = 0.1F,
direction = vector,
entityClass = targetEntity.javaClass,
collideBlock = ClipContext.Fluid.NONE,
)
return@canSee DoubleValue(targetEntity in (trace?.entities ?: emptyList()))
}
DoubleValue.ZERO
}
when (entity) {
is PathfinderMob -> {
functions["walk_to"] = { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val speedMultiplier = params.getDoubleOrNull(3) ?: 0.35
if (entity.brain.checkMemory(MemoryModuleType.WALK_TARGET, MemoryStatus.REGISTERED)) {
entity.brain.setMemory(
MemoryModuleType.WALK_TARGET,
WalkTarget(Vec3(x, y, z), speedMultiplier.toFloat(), 1),
)
entity.brain.setMemory(
MemoryModuleType.LOOK_TARGET,
BlockPosTracker(Vec3(x, y + entity.eyeHeight, z)),
)
} else {
entity.navigation.moveTo(x, y, z, speedMultiplier)
entity.lookControl.setLookAt(Vec3(x, y + entity.eyeHeight, z))
}
}
functions["has_walk_target"] = {
DoubleValue(entity.brain.getMemorySafely(MemoryModuleType.WALK_TARGET).isPresent || entity.isPathFinding)
}
functions["set_can_float"] = { params ->
val canFloat = params.getBooleanOrNull(0) != false
entity.navigation.setCanFloat(canFloat)
DoubleValue.ONE
}
functions["get_pathfinding_malus"] = getPfMalus@{ params ->
val type = PathType.entries.find { it.name == params.getString(0).uppercase() }
if (type != null) {
return@getPfMalus DoubleValue(entity.getPathfindingMalus(type))
}
Cobblemon.LOGGER.error("Cannot get pathfinding type, unknown value: ${params.getString(0)}")
DoubleValue.ZERO
}
functions["set_pathfinding_malus"] = setPfMalus@{ params ->
val type = PathType.entries.find { it.name == params.getString(0).uppercase() }
val malus = params.getDouble(1).toFloat()
if (type != null) {
entity.setPathfindingMalus(type, malus)
return@setPfMalus DoubleValue.ONE
}
Cobblemon.LOGGER.error("Cannot set pathfinding type, unknown value: ${params.getString(0)}")
DoubleValue.ZERO
}
}
}
if (entity is PathfinderMob) {
map["walk_to"] = { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val speedMultiplier = params.getDoubleOrNull(3) ?: 0.35
if (entity.brain.checkMemory(MemoryModuleType.WALK_TARGET, MemoryStatus.REGISTERED)) {
entity.brain.setMemory(
MemoryModuleType.WALK_TARGET,
WalkTarget(Vec3(x, y, z), speedMultiplier.toFloat(), 1)
)
entity.brain.setMemory(
MemoryModuleType.LOOK_TARGET,
BlockPosTracker(Vec3(x, y + entity.eyeHeight, z))
)
} else {
entity.navigation.moveTo(x, y, z, speedMultiplier)
entity.lookControl.setLookAt(Vec3(x, y + entity.eyeHeight, z))
}
}
map["has_walk_target"] = { _ ->
DoubleValue(entity.brain.getMemorySafely(MemoryModuleType.WALK_TARGET).isPresent || entity.isPathFinding)
}
map["set_can_float"] = { params ->
val canFloat = params.getBooleanOrNull(0) != false
entity.navigation.setCanFloat(canFloat)
DoubleValue.ONE
}
map["get_pathfinding_malus"] = put@{ params ->
val type = PathType.entries.find { it.name == params.getString(0).uppercase() }
if (type != null) {
return@put DoubleValue(entity.getPathfindingMalus(type))
}
Cobblemon.LOGGER.error("Unknown pathfinding type: ${params.getString(0)}")
return@put DoubleValue.ZERO
}
map["set_pathfinding_malus"] = put@{ params ->
val type = PathType.entries.find { it.name == params.getString(0).uppercase() }
val malus = params.getDouble(1).toFloat()
if (type != null) {
entity.setPathfindingMalus(type, malus)
return@put DoubleValue.ONE
}
Cobblemon.LOGGER.error("Unknown pathfinding type: ${params.getString(0)}")
return@put DoubleValue.ZERO
}
}
return map
return functions
}
}
@@ -50,18 +50,23 @@ import kotlin.collections.any
object NPCMoLangFunctions : AbstractMoLangFunctionHolder<NPCEntity>() {
override fun NPCEntity.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val npc = this
val map = hashMapOf<String, (MoParams) -> Any>()
map["class"] = { StringValue(npc.npc.id.toString()) }
map["name"] = { StringValue(npc.name.string) }
map["level"] = { DoubleValue(npc.level) }
map["has_aspect"] = { params -> DoubleValue(npc.aspects.contains(params.getString(0))) }
map["in_battle"] = { DoubleValue(npc.isInBattle()) }
map["battles"] = {
ArrayStruct(npc.battleIds.mapNotNull { BattleRegistry.getBattle(it)?.struct }
.mapIndexed { index, value -> "$index" to value }.toMap())
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["class"] = { StringValue(npc.npc.id.toString()) }
functions["name"] = { StringValue(npc.name.string) }
functions["level"] = { DoubleValue(npc.level) }
functions["has_aspect"] = { params -> DoubleValue(npc.aspects.contains(params.getString(0))) }
functions["in_battle"] = { DoubleValue(npc.isInBattle()) }
functions["battles"] = {
ArrayStruct(
npc.battleIds
.mapNotNull { BattleRegistry.getBattle(it)?.struct }
.mapIndexed { index, value -> "$index" to value }
.toMap(),
)
}
map["stop_battles"] = { _ -> npc.battleIds.forEach { BattleRegistry.getBattle(it)?.stop() } }
map["run_script_on_client"] = { params ->
functions["stop_battles"] = { npc.battleIds.forEach { BattleRegistry.getBattle(it)?.stop() } }
functions["run_script_on_client"] = { params ->
val world = npc.level()
if (world is ServerLevel) {
val script = params.getString(0)
@@ -70,141 +75,142 @@ object NPCMoLangFunctions : AbstractMoLangFunctionHolder<NPCEntity>() {
}
Unit
}
map["run_script"] = { params ->
functions["run_script"] = { params ->
val script = params.getString(0).asIdentifierDefaultingNamespace()
val runtime = MoLangRuntime()
runtime.environment.cloneFrom(params.environment)
CobblemonScripts.run(script, runtime) ?: DoubleValue(0)
}
map["set_movable"] = put@{ params ->
functions["set_movable"] = { params ->
val movable = params.getBooleanOrNull(0) != false
npc.isMovable = movable
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["set_invulnerable"] = put@{ params ->
functions["set_invulnerable"] = { params ->
val invulnerable = params.getBooleanOrNull(0) != false
npc.isInvulnerable = invulnerable
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["set_leashable"] = put@{ params ->
functions["set_leashable"] = { params ->
val leashable = params.getBooleanOrNull(0) != false
npc.isLeashable = leashable
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["set_allow_projectile_hits"] = put@{ params ->
functions["set_allow_projectile_hits"] = { params ->
val allowProjectileHits = params.getBooleanOrNull(0) != false
npc.allowProjectileHits = allowProjectileHits
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["set_name_tag_visible"] = put@{ params ->
functions["set_name_tag_visible"] = { params ->
val nameTagVisible = params.getBooleanOrNull(0) != false
npc.hideNameTag = !nameTagVisible
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["unset_interaction"] = put@{
functions["unset_interaction"] = {
npc.interaction = null
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["set_dialogue_interaction"] = put@{ params ->
functions["set_dialogue_interaction"] = { params ->
val dialogue = params.getString(0).asIdentifierDefaultingNamespace()
npc.interaction = DialogueNPCInteractionConfiguration().also {
it.dialogue = dialogue
}
return@put DoubleValue.ONE
npc.interaction =
DialogueNPCInteractionConfiguration().also {
it.dialogue = dialogue
}
DoubleValue.ONE
}
map["set_script_interaction"] = put@{ params ->
functions["set_script_interaction"] = { params ->
val script = params.getString(0).asIdentifierDefaultingNamespace()
npc.interaction = ScriptNPCInteractionConfiguration().also {
it.script = script
}
return@put DoubleValue.ONE
npc.interaction =
ScriptNPCInteractionConfiguration().also {
it.script = script
}
DoubleValue.ONE
}
map["set_player_texture"] = put@{ params ->
functions["set_player_texture"] = setPlayerTexture@{ params ->
val username = params.getString(0)
// Re-applying it would be unnecessarily laggy.
if (username == npc.data.map["player_texture_username"]?.asString()) {
return@put DoubleValue.ZERO
return@setPlayerTexture DoubleValue.ZERO
}
npc.loadTextureFromGameProfileName(username)
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["unset_player_texture"] = put@{
functions["unset_player_texture"] = {
npc.unloadTexture()
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["set_resource_identifier"] = put@{ params ->
functions["set_resource_identifier"] = { params ->
val identifier = params.getStringOrNull(0)?.asIdentifierDefaultingNamespace()
npc.forcedResourceIdentifier = identifier
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["unset_resource_identifier"] = put@{
functions["unset_resource_identifier"] = {
npc.forcedResourceIdentifier = null
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["set_class"] = put@{ params ->
functions["set_class"] = setClass@{ params ->
val identifier = params.getString(0).asIdentifierDefaultingNamespace()
val npcClass = NPCClasses.getByIdentifier(identifier)
if (npcClass != null) {
npc.npc = npcClass
return@put DoubleValue.ONE
} else {
Cobblemon.LOGGER.error("Unknown NPC class: $identifier")
return@put DoubleValue.ZERO
return@setClass DoubleValue.ONE
}
Cobblemon.LOGGER.error("Unknown NPC class: $identifier")
DoubleValue.ZERO
}
map["set_render_scale"] = put@{ params ->
functions["set_render_scale"] = { params ->
val scale = params.getDouble(0)
npc.renderScale = scale.toFloat()
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["render_scale"] = { _ -> DoubleValue(npc.renderScale) }
map["set_hitbox_scale"] = put@{ params ->
functions["render_scale"] = { DoubleValue(npc.renderScale) }
functions["set_hitbox_scale"] = { params ->
val scale = params.getDouble(0).toFloat()
npc.hitboxScale = scale
npc.refreshDimensions()
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["hitbox_scale"] = { _ -> DoubleValue(npc.hitboxScale) }
map["set_hitbox"] = put@{ params ->
functions["hitbox_scale"] = { DoubleValue(npc.hitboxScale) }
functions["set_hitbox"] = setHitbox@{ params ->
if (params.params.isEmpty()) {
npc.hitbox = null
return@put DoubleValue.ONE
return@setHitbox DoubleValue.ONE
}
val width = params.getDouble(0).toFloat()
val height = params.getDouble(1).toFloat()
val eyeHeight = params.getDoubleOrNull(2)?.toFloat() ?: (height * 0.85F)
npc.hitbox = EntityDimensions.scalable(width, height).withEyeHeight(eyeHeight)
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["unset_hitbox"] = put@{
functions["unset_hitbox"] = {
npc.hitbox = null
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["aspects"] = put@{
functions["aspects"] = {
val aspects = npc.aspects
return@put aspects.asArrayValue { StringValue(it) }
aspects.asArrayValue { StringValue(it) }
}
map["add_aspect"] = put@{ params ->
functions["add_aspect"] = { params ->
val aspects = params.params.map { it.asString() }
npc.appliedAspects.addAll(aspects)
npc.updateAspects()
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["remove_aspect"] = put@{ params ->
functions["remove_aspect"] = { params ->
val aspects = params.params.map { it.asString() }
npc.appliedAspects.removeAll(aspects.toSet())
npc.updateAspects()
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["party"] = put@{
val party = npc.party ?: return@put DoubleValue.ZERO
return@put party.asMoLangValue()
functions["party"] = party@{
val party = npc.party ?: return@party DoubleValue.ZERO
party.asMoLangValue()
}
map["create_npc_party"] = put@{
functions["create_npc_party"] = {
val party = NPCPartyStore(npc)
return@put party.asMoLangValue()
party.asMoLangValue()
}
map["set_npc_party"] = { params ->
functions["set_npc_party"] = { params ->
val party = params.get<MoValue>(0)
if (party is ObjectValue<*>) {
npc.party = party.obj as NPCPartyStore
@@ -213,18 +219,19 @@ object NPCMoLangFunctions : AbstractMoLangFunctionHolder<NPCEntity>() {
}
DoubleValue.ONE
}
map["run_action_effect"] = put@{ params ->
functions["run_action_effect"] = runActionEffect@{ params ->
val runtime = MoLangRuntime().setup()
runtime.environment.cloneFrom(params.environment)
runtime.withNPCValue(value = npc)
val actionEffect = ActionEffects.actionEffects[params.getString(0).asIdentifierDefaultingNamespace()]
if (actionEffect != null) {
val context = ActionEffectContext(
actionEffect = actionEffect,
providers = mutableListOf(NPCProvider(npc)),
runtime = runtime,
level = npc.level()
)
val context =
ActionEffectContext(
actionEffect = actionEffect,
providers = mutableListOf(NPCProvider(npc)),
runtime = runtime,
level = npc.level(),
)
npc.actionEffect = context
npc.brain.setMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT, context)
npc.brain.setActiveActivityIfPossible(CobblemonActivities.ACTION_EFFECT)
@@ -236,37 +243,40 @@ object NPCMoLangFunctions : AbstractMoLangFunctionHolder<NPCEntity>() {
}
}
return@put DoubleValue.ONE
return@runActionEffect DoubleValue.ONE
}
return@put DoubleValue.ZERO
DoubleValue.ZERO
}
map["put_pokemon_in_healer"] = put@{ params ->
val healer = npc.level()
.getBlockEntity(params.get<ArrayStruct>(0).asBlockPos(), CobblemonBlockEntities.HEALING_MACHINE)
.orElse(null) ?: return@put DoubleValue.ZERO
val party = npc.party ?: return@put DoubleValue.ZERO
functions["put_pokemon_in_healer"] = putPokeInHealer@{ params ->
val healer =
npc
.level()
.getBlockEntity(params.get<ArrayStruct>(0).asBlockPos(), CobblemonBlockEntities.HEALING_MACHINE)
.orElse(null) ?: return@putPokeInHealer DoubleValue.ZERO
val party = npc.party ?: return@putPokeInHealer DoubleValue.ZERO
if (healer.canHeal(party)) {
healer.activate(npc.uuid, party)
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
return@putPokeInHealer DoubleValue.ONE
}
DoubleValue.ZERO
}
map["look_at_position"] = { params ->
functions["look_at_position"] = { params ->
val pos = Vec3(params.getDouble(0), params.getDouble(1), params.getDouble(2))
npc.lookAt(EntityAnchorArgument.Anchor.EYES, pos)
}
map["environment"] = { _ -> npc.runtime.environment }
map["party"] = { npc.party?.struct ?: DoubleValue.ZERO }
map["has_party"] = { DoubleValue(npc.party != null) }
map["is_npc"] = { DoubleValue.ONE }
map["can_battle"] =
{ DoubleValue(npc.party?.any { it.currentHealth > 0 } == true || npc.npc.party?.isStatic == false) }
map["set_battle_theme"] = put@{ params ->
functions["environment"] = { npc.runtime.environment }
functions["party"] = { npc.party?.struct ?: DoubleValue.ZERO }
functions["has_party"] = { DoubleValue(npc.party != null) }
functions["is_npc"] = { DoubleValue.ONE }
functions["can_battle"] = {
DoubleValue(npc.party?.any { it.currentHealth > 0 } == true || npc.npc.party?.isStatic == false)
}
functions["set_battle_theme"] = { params ->
val soundId = params.getString(0).asResource()
npc.npc.battleTheme = soundId
return@put DoubleValue.ONE
DoubleValue.ONE
}
return map
return functions
}
}
@@ -23,40 +23,40 @@ import com.cobblemon.mod.common.util.getBooleanOrNull
object PCMoLangFunctions : AbstractMoLangFunctionHolder<PCStore>() {
override fun PCStore.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val pc = this
val map = mutableMapOf<String, (MoParams) -> Any>()
val functions = hashMapOf<String, (MoParams) -> Any>()
map["get_pokemon"] = getPokemon@{ params ->
functions["get_pokemon"] = getPokemon@{ params ->
val box = params.getInt(0)
val slot = params.getInt(1)
val pokemon = pc[PCPosition(box, slot)] ?: return@getPokemon DoubleValue.ZERO
return@getPokemon pokemon.struct
pokemon.struct
}
map["set_pokemon"] = { params ->
functions["set_pokemon"] = { params ->
val box = params.getInt(0)
val slot = params.getInt(1)
val pokemon = params.get<ObjectValue<Pokemon>>(2).obj
pc[PCPosition(box, slot)] = pokemon
DoubleValue.ONE
}
map["resize"] = { params ->
functions["resize"] = { params ->
val newSize = params.getInt(0)
val lockNewSize = params.getBooleanOrNull(1) == true
pc.resize(newSize, lockNewSize)
DoubleValue.ONE
}
map["get_box_count"] = { DoubleValue(pc.boxes.size.toDouble()) }
map["has_unlocked_wallpaper"] = { params ->
functions["get_box_count"] = { DoubleValue(pc.boxes.size.toDouble()) }
functions["has_unlocked_wallpaper"] = { params ->
val wallpaper = params.getString(0).asIdentifierDefaultingNamespace()
DoubleValue(pc.unlockedWallpapers.contains(wallpaper))
}
map["get_unlocked_wallpapers"] = { pc.unlockedWallpapers.asArrayValue { StringValue(it.toString()) } }
map["unlock_wallpaper"] = unlockWallpaper@{
functions["get_unlocked_wallpapers"] = { pc.unlockedWallpapers.asArrayValue { StringValue(it.toString()) } }
functions["unlock_wallpaper"] = unlockWallpaper@{
val wallpaper = it.getString(0).asIdentifierDefaultingNamespace()
val playSound = it.getBooleanOrNull(1) != false
CobblemonUnlockableWallpapers.unlockableWallpapers[wallpaper] ?: return@unlockWallpaper DoubleValue.ZERO
return@unlockWallpaper DoubleValue(pc.unlockWallpaper(wallpaper, playSound))
DoubleValue(pc.unlockWallpaper(wallpaper, playSound))
}
return map
return functions
}
}
@@ -14,21 +14,23 @@ import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.pokemon.Pokemon
object PartyMoLangFunctions: AbstractMoLangFunctionHolder<PartyStore>() {
object PartyMoLangFunctions : AbstractMoLangFunctionHolder<PartyStore>() {
override fun PartyStore.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val party = this
val map = hashMapOf<String, (MoParams) -> Any>()
map["get_pokemon"] = getPokemon@{ params ->
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["get_pokemon"] = getPokemon@{ params ->
val index = params.getInt(0)
val pokemon = party.get(index) ?: return@getPokemon DoubleValue.ZERO
pokemon.struct
}
map["set_pokemon"] = { params ->
functions["set_pokemon"] = { params ->
val index = params.getInt(0)
val pokemon = params.get<ObjectValue<Pokemon>>(1).obj
party.set(index, pokemon)
DoubleValue.ONE
}
return map
return functions
}
}
@@ -17,12 +17,12 @@ import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.api.dialogue.PlayerDialogueFaceProvider
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.api.tms.TechnicalMachines
import com.cobblemon.mod.common.battles.BattleBuilder
import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.entity.npc.NPCEntity
@@ -52,355 +52,389 @@ import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundSource
import net.minecraft.stats.Stats
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.GameType
import kotlin.collections.iterator
object PlayerMoLangFunctions : AbstractMoLangFunctionHolder<Player>() {
override fun Player.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val player = this
val map = mutableMapOf<String, (MoParams) -> Any>()
map["username"] = { _ -> StringValue(player.gameProfile.name) }
map["uuid"] = { _ -> StringValue(player.gameProfile.id.toString()) }
map["main_held_item"] = { _ -> player.mainHandItem.asMoLangValue(player.registryAccess()) }
map["off_held_item"] = { _ -> player.offhandItem.asMoLangValue(player.registryAccess()) }
map["inventory"] = put@{ _ ->
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["username"] = { StringValue(player.gameProfile.name) }
functions["uuid"] = { StringValue(player.gameProfile.id.toString()) }
functions["main_held_item"] = { player.mainHandItem.asMoLangValue(player.registryAccess()) }
functions["off_held_item"] = { player.offhandItem.asMoLangValue(player.registryAccess()) }
functions["inventory"] = {
val inventory = player.inventory
val items = ArrayStruct(hashMapOf())
for (i in 0 until inventory.containerSize) {
items.setDirectly("$i", inventory.getItem(i).asMoLangValue(player.registryAccess()))
}
return@put items
items
}
map["has_inventory_space"] = put@{ _ ->
functions["has_inventory_space"] = {
val inventory = player.inventory
return@put if (inventory.getFreeSlot() != -1) DoubleValue.ONE else DoubleValue.ZERO
if (inventory.freeSlot != -1) DoubleValue.ONE else DoubleValue.ZERO
}
map["set_inventory_slot"] = put@{ params ->
functions["set_inventory_slot"] = setInvSlot@{ params ->
val slot = params.getInt(0)
val inventory = player.inventory
if (slot !in 0 until inventory.containerSize) return@put DoubleValue.ZERO
if (slot !in 0 until inventory.containerSize) return@setInvSlot DoubleValue.ZERO
val value = params.getOrNull<MoValue>(1) ?: return@put DoubleValue.ZERO
val value = params.getOrNull<MoValue>(1) ?: return@setInvSlot DoubleValue.ZERO
val stack: ItemStack? = when (value) {
is ObjectValue<*> -> value.obj as? ItemStack
is StringValue -> {
val id = ResourceLocation.parse(value.value)
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(id)
?: return@put DoubleValue.ZERO
ItemStack(item)
val stack: ItemStack? =
when (value) {
is ObjectValue<*> -> {
value.obj as? ItemStack
}
is StringValue -> {
val id = ResourceLocation.parse(value.value)
val item =
player.registryAccess().registryOrThrow(Registries.ITEM).get(id)
?: return@setInvSlot DoubleValue.ZERO
ItemStack(item)
}
else -> {
null
}
}
else -> null
}
if (stack == null) return@put DoubleValue.ZERO
if (stack == null) return@setInvSlot DoubleValue.ZERO
inventory.setItem(slot, stack)
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["give_item"] = put@{ params ->
functions["give_item"] = giveItem@{ params ->
val inventory = player.inventory
val freeSlot = inventory.getFreeSlot()
val value = params.getOrNull<MoValue>(0) ?: return@put DoubleValue.ZERO
val freeSlot = inventory.freeSlot
val value = params.getOrNull<MoValue>(0) ?: return@giveItem DoubleValue.ZERO
val dropIfFull = params.getBooleanOrNull(1) ?: false
val stack: ItemStack? = when (value) {
is ObjectValue<*> -> value.obj as? ItemStack
is StringValue -> {
val id = ResourceLocation.parse(value.value)
val item = player.registryAccess().registryOrThrow(Registries.ITEM).get(id)
?: return@put DoubleValue.ZERO
ItemStack(item)
val stack: ItemStack? =
when (value) {
is ObjectValue<*> -> {
value.obj as? ItemStack
}
is StringValue -> {
val id = ResourceLocation.parse(value.value)
val item =
player.registryAccess().registryOrThrow(Registries.ITEM).get(id)
?: return@giveItem DoubleValue.ZERO
ItemStack(item)
}
else -> {
null
}
}
else -> null
}
if (stack == null) return@put DoubleValue.ZERO
if (stack == null) return@giveItem DoubleValue.ZERO
if (freeSlot == -1) {
if (dropIfFull) {
player.drop(stack, false)
return@put DoubleValue.ONE
return@giveItem DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
return@giveItem DoubleValue.ZERO
}
}
inventory.setItem(freeSlot, stack)
return@put DoubleValue.ONE
DoubleValue.ONE
}
map["face"] = { params ->
functions["face"] = { params ->
ObjectValue(
PlayerDialogueFaceProvider(
player.uuid,
params.getBooleanOrNull(0) != false
)
params.getBooleanOrNull(0) != false,
),
)
}
map["swing_hand"] = { _ -> player.swing(player.usedItemHand) }
map["food_level"] = { _ -> DoubleValue(player.foodData.foodLevel) }
map["saturation_level"] = { _ -> DoubleValue(player.foodData.saturationLevel) }
map["tell"] = { params ->
functions["swing_hand"] = { player.swing(player.usedItemHand) }
functions["food_level"] = { DoubleValue(player.foodData.foodLevel) }
functions["saturation_level"] = { DoubleValue(player.foodData.saturationLevel) }
functions["tell"] = { params ->
val message = params.getString(0).text()
val overlay = params.getBooleanOrNull(1) == true
player.displayClientMessage(message, overlay)
}
map["teleport"] = { params ->
functions["teleport"] = { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val playParticleOptionss = params.getBooleanOrNull(3) ?: false
player.randomTeleport(x, y, z, playParticleOptionss)
val playParticleOptions = params.getBooleanOrNull(3) ?: false
player.randomTeleport(x, y, z, playParticleOptions)
}
map["heal"] = { params ->
functions["heal"] = { params ->
val amount = params.getDoubleOrNull(0) ?: player.maxHealth
player.heal(amount.toFloat())
}
map["environment"] = {
functions["environment"] = {
val environment = MoLangEnvironment()
environment.query = player.asMoLangValue()
environment
}
map["is_player"] = { DoubleValue.ONE }
map["riding_pokemon"] = put@{
functions["is_player"] = { DoubleValue.ONE }
functions["riding_pokemon"] = ridingPokemon@{
val vehicle = player.vehicle
if (vehicle is PokemonEntity) {
return@put vehicle.struct
} else {
return@put DoubleValue.ZERO
return@ridingPokemon vehicle.struct
}
DoubleValue.ZERO
}
if (player is ServerPlayer) {
map["seen_credits"] = { _ ->
DoubleValue(player.seenCredits)
}
map["is_in_dialogue"] = { _ ->
DoubleValue(player.isInDialogue)
}
map["active_dialogue"] = put@{ _ ->
if (player.isInDialogue) {
return@put player.activeDialogue?.toMoLangStruct() ?: DoubleValue.ZERO
} else {
when (player) {
is ServerPlayer -> {
functions["seen_credits"] = { DoubleValue(player.seenCredits) }
functions["is_in_dialogue"] = { DoubleValue(player.isInDialogue) }
functions["active_dialogue"] = activeDialogue@{
if (player.isInDialogue) {
return@activeDialogue player.activeDialogue?.toMoLangStruct() ?: DoubleValue.ZERO
}
DoubleValue.ZERO
}
}
map["is_spectator"] = { DoubleValue(player.isSpectator) }
map["is_creative"] = { DoubleValue(player.isCreative) }
map["is_survival"] = { DoubleValue(player.gameMode.isSurvival) }
map["is_adventure"] = { DoubleValue(player.gameMode.gameModeForPlayer == GameType.ADVENTURE) }
map["run_command"] = { params ->
val command = params.getString(0)
player.server.commands.performPrefixedCommand(player.createCommandSourceStack(), command)
}
map["set_battle_theme"] = put@{ params ->
val soundId = params.getString(0).asResource()
Cobblemon.playerDataManager.getGenericData(player).battleTheme = soundId
return@put DoubleValue.ONE
}
map["battle_music"] = put@{ params ->
val soundId = params.getString(0).asResource()
val volume = params.getDoubleOrNull(1)?.toFloat() ?: 1.0f
val pitch = params.getDoubleOrNull(2)?.toFloat() ?: 1.0f
val restart = params.getBooleanOrNull(3) ?: true
functions["is_spectator"] = { DoubleValue(player.isSpectator) }
functions["is_creative"] = { DoubleValue(player.isCreative) }
functions["is_survival"] = { DoubleValue(player.gameMode.isSurvival) }
functions["is_adventure"] = { DoubleValue(player.gameMode.gameModeForPlayer == GameType.ADVENTURE) }
functions["run_command"] = { params ->
val command = params.getString(0)
player.server.commands.performPrefixedCommand(player.createCommandSourceStack(), command)
}
functions["set_battle_theme"] = { params ->
val soundId = params.getString(0).asResource()
Cobblemon.playerDataManager.getGenericData(player).battleTheme = soundId
DoubleValue.ONE
}
functions["battle_music"] = battleMusic@{ params ->
val soundId = params.getString(0).asResource()
val volume = params.getDoubleOrNull(1)?.toFloat() ?: 1.0f
val pitch = params.getDoubleOrNull(2)?.toFloat() ?: 1.0f
val restart = params.getBooleanOrNull(3) ?: true
if (soundId != null) {
val packet = BattleMusicPacket(soundId, volume, pitch, restart)
if (soundId != null) {
val packet = BattleMusicPacket(soundId, volume, pitch, restart)
packet.sendToPlayer(player)
return@battleMusic DoubleValue.ONE
}
DoubleValue.ZERO
}
functions["stop_battle_music"] = {
val packet = BattleMusicPacket(null)
packet.sendToPlayer(player)
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
DoubleValue.ONE
}
}
map["stop_battle_music"] = put@{ _ ->
val packet = BattleMusicPacket(null)
packet.sendToPlayer(player)
return@put DoubleValue.ONE
}
map["play_sound_on_server"] = { params ->
val sound = params.getString(0).asResource()
val soundSource = SoundSource.valueOf(params.getString(1).uppercase())
val volume = params.getDoubleOrNull(2)?.toFloat() ?: 1.0f
val pitch = params.getDoubleOrNull(3)?.toFloat() ?: 1.0f
functions["play_sound_on_server"] = { params ->
val sound = params.getString(0).asResource()
val soundSource = SoundSource.valueOf(params.getString(1).uppercase())
val volume = params.getDoubleOrNull(2)?.toFloat() ?: 1.0f
val pitch = params.getDoubleOrNull(3)?.toFloat() ?: 1.0f
val packet =
UnvalidatedPlaySoundS2CPacket(sound, soundSource, player.x, player.y, player.z, volume, pitch)
packet.sendToPlayer(player)
}
map["is_party_at_full_health"] = { _ ->
DoubleValue(player.party().none(Pokemon::canBeHealed))
}
map["can_heal_at_healer"] = put@{ params ->
val pos = params.get<ArrayStruct>(0).asBlockPos()
val healer = player.level().getBlockEntity(pos, CobblemonBlockEntities.HEALING_MACHINE).orElse(null)
?: return@put DoubleValue.ZERO
val party = player.party()
return@put DoubleValue(healer.canHeal(party))
}
map["put_pokemon_in_healer"] = put@{ params ->
val healer = player.level()
.getBlockEntity(params.get<ArrayStruct>(0).asBlockPos(), CobblemonBlockEntities.HEALING_MACHINE)
.orElse(null) ?: return@put DoubleValue.ZERO
val party = player.party()
if (healer.canHeal(party)) {
healer.activate(player.uuid, party)
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
val packet =
UnvalidatedPlaySoundS2CPacket(
sound,
soundSource,
player.x,
player.y,
player.z,
volume,
pitch,
)
packet.sendToPlayer(player)
}
}
map["party"] = { player.party().struct }
map["pc"] = { player.pc().struct }
map["has_permission"] = { params ->
DoubleValue(
Cobblemon.permissionValidator.hasPermission(
player,
params.getString(0),
params.getIntOrNull(1) ?: 4
functions["is_party_at_full_health"] = { DoubleValue(player.party().none(Pokemon::canBeHealed)) }
functions["can_heal_at_healer"] = canHealAtHealer@{ params ->
val pos = params.get<ArrayStruct>(0).asBlockPos()
val healer =
player.level().getBlockEntity(pos, CobblemonBlockEntities.HEALING_MACHINE).orElse(null)
?: return@canHealAtHealer DoubleValue.ZERO
val party = player.party()
DoubleValue(healer.canHeal(party))
}
functions["put_pokemon_in_healer"] = putPokemonAtHealer@{ params ->
val healer =
player
.level()
.getBlockEntity(
params.get<ArrayStruct>(0).asBlockPos(),
CobblemonBlockEntities.HEALING_MACHINE,
).orElse(null) ?: return@putPokemonAtHealer DoubleValue.ZERO
val party = player.party()
if (healer.canHeal(party)) {
healer.activate(player.uuid, party)
return@putPokemonAtHealer DoubleValue.ONE
}
DoubleValue.ZERO
}
functions["party"] = { player.party().struct }
functions["pc"] = { player.pc().struct }
functions["has_permission"] = { params ->
DoubleValue(
Cobblemon.permissionValidator.hasPermission(
player,
params.getString(0),
params.getIntOrNull(1) ?: 4,
),
)
)
}
map["data"] = { params -> Cobblemon.molangData.load(player.uuid, params.getStringOrNull(0)) }
map["save_data"] = { params -> Cobblemon.molangData.save(player.uuid, params.getStringOrNull(0)) }
map["in_battle"] = { DoubleValue(player.isInBattle()) }
map["battle"] = { player.getBattleState()?.first?.struct ?: DoubleValue.ZERO }
map["get_npc_data"] = put@{ params ->
val npcId = ((params.get<MoValue>(0) as? ObjectValue<*>)?.obj as? NPCEntity)?.stringUUID
?: params.getString(0)
val data = Cobblemon.molangData.load(player.uuid, params.getStringOrNull(1))
if (data.map.containsKey(npcId)) {
return@put data.map[npcId]!!
} else {
}
functions["data"] = { params -> Cobblemon.molangData.load(player.uuid, params.getStringOrNull(0)) }
functions["save_data"] = { params -> Cobblemon.molangData.save(player.uuid, params.getStringOrNull(0)) }
functions["in_battle"] = { DoubleValue(player.isInBattle()) }
functions["battle"] = { player.getBattleState()?.first?.struct ?: DoubleValue.ZERO }
functions["get_npc_data"] = getNpcData@{ params ->
val npcId =
((params.get<MoValue>(0) as? ObjectValue<*>)?.obj as? NPCEntity)?.stringUUID
?: params.getString(0)
val data = Cobblemon.molangData.load(player.uuid, params.getStringOrNull(1))
if (data.map.containsKey(npcId)) {
return@getNpcData data.map[npcId]!!
}
val vars = VariableStruct()
data.map[npcId] = vars
return@put vars
vars
}
}
map["get_npc_variable"] = put@{ params ->
val npcId = ((params.get<MoValue>(0) as? ObjectValue<*>)?.obj as? NPCEntity)?.stringUUID
?: params.getString(0)
val variable = params.getString(1)
val data = Cobblemon.molangData.load(player.uuid, params.getStringOrNull(2))
if (data.map.containsKey(npcId)) {
return@put (data.map[npcId] as VariableStruct).map[variable] ?: DoubleValue.ZERO
} else {
return@put DoubleValue.ZERO
}
}
map["set_npc_variable"] = put@{ params ->
val npcId = ((params.get<MoValue>(0) as? ObjectValue<*>)?.obj as? NPCEntity)?.stringUUID
?: params.getString(0)
val variable = params.getString(1)
val value = params.get<MoValue>(2)
val saveAfterwards = params.getBooleanOrNull(3) != false
val path = params.getStringOrNull(4)
val data = Cobblemon.molangData.load(player.uuid, path)
val npcData = data.map.getOrPut(npcId) { VariableStruct() } as VariableStruct
npcData.map[variable] = value
if (saveAfterwards) {
Cobblemon.molangData.save(player.uuid, path)
}
return@put DoubleValue.ONE
}
map["pokedex"] = { player.pokedex().struct }
map["has_advancement"] = put@{ params ->
val requiredAdvancement = ResourceLocation.parse(params.getString(0))
for (entry in player.advancements.progress) {
if (entry.key.id == requiredAdvancement && entry.value.isDone) {
return@put DoubleValue.ONE
functions["get_npc_variable"] = getNpcVariable@{ params ->
val npcId =
((params.get<MoValue>(0) as? ObjectValue<*>)?.obj as? NPCEntity)?.stringUUID
?: params.getString(0)
val variable = params.getString(1)
val data = Cobblemon.molangData.load(player.uuid, params.getStringOrNull(2))
if (data.map.containsKey(npcId)) {
return@getNpcVariable (data.map[npcId] as VariableStruct).map[variable] ?: DoubleValue.ZERO
}
DoubleValue.ZERO
}
return@put DoubleValue.ZERO
}
map["has_tm_move_unlocked"] = put@{ params ->
val move = Moves.getByName(params.getString(0)) ?: return@put DoubleValue.ZERO
val tm = TechnicalMachines.moveToTM[move] ?: return@put DoubleValue.ZERO
DoubleValue(if (player.tmList().learnedTMs.contains(tm.id)) 1.0 else 0.0)
}
map["unlock_tm_move"] = put@{ params ->
val move = Moves.getByName(params.getString(0)) ?: return@put DoubleValue.ZERO
val tm = TechnicalMachines.moveToTM[move] ?: return@put DoubleValue.ZERO
player.tmList().syncTMFromMove(move)
DoubleValue(if (player.tmList().learnedTMs.contains(tm.id)) 1.0 else 0.0)
}
map["lock_tm_move"] = put@{ params ->
val move = Moves.getByName(params.getString(0)) ?: return@put DoubleValue.ZERO
val tm = TechnicalMachines.moveToTM[move] ?: return@put DoubleValue.ZERO
player.tmList().syncTMLockFromMove(move)
DoubleValue(if (player.tmList().learnedTMs.contains(tm.id)) 0.0 else 1.0)
}
map["start_battle"] = put@{ params ->
val opponentValue = params.get<MoValue>(0)
val opponent = if (opponentValue is ObjectValue<*>) {
opponentValue.obj as ServerPlayer
} else {
val paramString = opponentValue.asString()
val playerUUID = paramString.asUUID
if (playerUUID != null) {
server()?.playerList?.getPlayer(playerUUID) ?: return@put DoubleValue.ZERO
} else {
server()?.playerList?.getPlayerByName(paramString) ?: return@put DoubleValue.ZERO
functions["set_npc_variable"] = { params ->
val npcId =
((params.get<MoValue>(0) as? ObjectValue<*>)?.obj as? NPCEntity)?.stringUUID
?: params.getString(0)
val variable = params.getString(1)
val value = params.get<MoValue>(2)
val saveAfterwards = params.getBooleanOrNull(3) != false
val path = params.getStringOrNull(4)
val data = Cobblemon.molangData.load(player.uuid, path)
val npcData = data.map.getOrPut(npcId) { VariableStruct() } as VariableStruct
npcData.map[variable] = value
if (saveAfterwards) {
Cobblemon.molangData.save(player.uuid, path)
}
DoubleValue.ONE
}
val format = params.getStringOrNull(1)
?.let(BattleFormat::fromFormatIdentifier)
?: BattleFormat.GEN_9_SINGLES
functions["pokedex"] = { player.pokedex().struct }
functions["has_advancement"] = hasAdvancement@{ params ->
val requiredAdvancement = ResourceLocation.parse(params.getString(0))
player.advancements.progress.forEach { (holder, progress) ->
if (holder.id == requiredAdvancement && progress.isDone) {
return@hasAdvancement DoubleValue.ONE
}
}
DoubleValue.ZERO
}
functions["has_tm_move_unlocked"] = hasTmMoveUnlocked@{ params ->
val move = Moves.getByName(params.getString(0)) ?: return@hasTmMoveUnlocked DoubleValue.ZERO
val tm = TechnicalMachines.moveToTM[move] ?: return@hasTmMoveUnlocked DoubleValue.ZERO
DoubleValue(if (player.tmList().learnedTMs.contains(tm.id)) 1.0 else 0.0)
}
functions["unlock_tm_move"] = unlockTmMove@{ params ->
val move = Moves.getByName(params.getString(0)) ?: return@unlockTmMove DoubleValue.ZERO
val tm = TechnicalMachines.moveToTM[move] ?: return@unlockTmMove DoubleValue.ZERO
player.tmList().syncTMFromMove(move)
DoubleValue(if (player.tmList().learnedTMs.contains(tm.id)) 1.0 else 0.0)
}
functions["lock_tm_move"] = lockTmMove@{ params ->
val move = Moves.getByName(params.getString(0)) ?: return@lockTmMove DoubleValue.ZERO
val tm = TechnicalMachines.moveToTM[move] ?: return@lockTmMove DoubleValue.ZERO
player.tmList().syncTMLockFromMove(move)
DoubleValue(if (player.tmList().learnedTMs.contains(tm.id)) 0.0 else 1.0)
}
functions["start_battle"] = startBattle@{ params ->
val opponentValue = params.get<MoValue>(0)
val opponent =
if (opponentValue is ObjectValue<*>) {
opponentValue.obj as ServerPlayer
} else {
val paramString = opponentValue.asString()
val playerUUID = paramString.asUUID
if (playerUUID != null) {
server()?.playerList?.getPlayer(playerUUID) ?: return@startBattle DoubleValue.ZERO
} else {
server()?.playerList?.getPlayerByName(paramString)
?: return@startBattle DoubleValue.ZERO
}
}
val format =
params
.getStringOrNull(1)
?.let(BattleFormat::fromFormatIdentifier)
?: BattleFormat.GEN_9_SINGLES
val setLevel = params.getIntOrNull(2) ?: -1
format.adjustLevel = setLevel
val setLevel = params.getIntOrNull(2) ?: -1
format.adjustLevel = setLevel
val rules = params.getStringOrNull(5)
?.split(",")
?.toSet()
?: emptySet()
val rules =
params
.getStringOrNull(5)
?.split(",")
?.toSet()
?: emptySet()
val modifiedBattleFormat = BattleFormat.setBattleRules(
battleFormat = format,
rules = rules
)
val modifiedBattleFormat =
BattleFormat.setBattleRules(
battleFormat = format,
rules = rules,
)
val cloneParties = (setLevel != -1) || (params.getBooleanOrNull(3) ?: false)
val healFirst = params.getBooleanOrNull(4) ?: false
val cloneParties = (setLevel != -1) || (params.getBooleanOrNull(3) ?: false)
val healFirst = params.getBooleanOrNull(4) ?: false
val battleStartResult = BattleBuilder.pvp1v1(
player1 = player,
player2 = opponent,
battleFormat = modifiedBattleFormat,
cloneParties = cloneParties,
healFirst = healFirst
)
var returnValue: MoValue = DoubleValue.ZERO
battleStartResult.ifSuccessful { returnValue = it.struct }
return@put returnValue
val battleStartResult =
BattleBuilder.pvp1v1(
player1 = player,
player2 = opponent,
battleFormat = modifiedBattleFormat,
cloneParties = cloneParties,
healFirst = healFirst,
)
var returnValue: MoValue = DoubleValue.ZERO
battleStartResult.ifSuccessful { returnValue = it.struct }
returnValue
}
functions["get_custom_stat"] = getCustomStat@{ params ->
val statName = params.getString(0)
val resourceLocation = ResourceLocation.tryParse(statName) ?: return@getCustomStat DoubleValue.ZERO
val statResourceLocation =
BuiltInRegistries.CUSTOM_STAT.get(resourceLocation) ?: return@getCustomStat DoubleValue.ZERO
val exists = Stats.CUSTOM.contains(statResourceLocation)
if (!exists) return@getCustomStat DoubleValue.ZERO
val stat = Stats.CUSTOM.get(statResourceLocation)
val value = player.stats.getValue(stat)
DoubleValue(value)
}
functions["get_starter_uuid"] = {
Cobblemon.playerDataManager.getGenericData(player).starterUUID?.let {
StringValue(
it.toString(),
)
} ?: DoubleValue.ZERO
}
functions["has_chosen_starter"] = {
DoubleValue(Cobblemon.playerDataManager.getGenericData(player).starterSelected)
}
}
map["get_custom_stat"] = put@{ params ->
val statName = params.getString(0)
val resourceLocation = ResourceLocation.tryParse(statName) ?: return@put DoubleValue.ZERO
val statResourceLocation =
BuiltInRegistries.CUSTOM_STAT.get(resourceLocation) ?: return@put DoubleValue.ZERO
val exists = net.minecraft.stats.Stats.CUSTOM.contains(statResourceLocation)
if (!exists) return@put DoubleValue.ZERO
val stat = net.minecraft.stats.Stats.CUSTOM.get(statResourceLocation)
val value = player.stats.getValue(stat)
DoubleValue(value)
}
map["get_starter_uuid"] = { _ ->
Cobblemon.playerDataManager.getGenericData(player).starterUUID?.let {
StringValue(
it.toString()
)
} ?: DoubleValue.ZERO
}
map["has_chosen_starter"] = { _ -> DoubleValue(Cobblemon.playerDataManager.getGenericData(player).starterSelected) }
}
return map
return functions
}
}
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
import com.cobblemon.mod.common.api.pokedex.CaughtCount
import com.cobblemon.mod.common.api.pokedex.CaughtPercent
import com.cobblemon.mod.common.api.pokedex.Dexes
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.PokedexManager
import com.cobblemon.mod.common.api.pokedex.SeenCount
@@ -27,57 +28,90 @@ import com.cobblemon.mod.common.util.getStringOrNull
object PokedexMoLangFunctions : AbstractMoLangFunctionHolder<AbstractPokedexManager>() {
override fun AbstractPokedexManager.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val pokedex = this
val map = mutableMapOf<String, (MoParams) -> Any>()
map["get_species_record"] = { params ->
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["get_species_record"] = { params ->
val speciesId = params.getString(0).asIdentifierDefaultingNamespace()
pokedex.speciesRecords[speciesId]?.struct ?: QueryStruct(hashMapOf())
}
map["has_seen"] = put@{ params ->
functions["has_seen"] = hasSeen@{ params ->
val speciesId = params.getString(0).asIdentifierDefaultingNamespace()
val formName = params.getStringOrNull(1)
val formName =
params.getStringOrNull(1)
?: return@hasSeen DoubleValue(
pokedex.getHighestKnowledgeForSpecies(speciesId).ordinal >= PokedexEntryProgress.SEEN.ordinal,
)
if (formName == null) {
return@put DoubleValue(pokedex.getHighestKnowledgeForSpecies(speciesId).ordinal >= PokedexEntryProgress.SEEN.ordinal)
} else {
return@put DoubleValue(
(pokedex.getSpeciesRecord(speciesId)?.getFormRecord(formName)?.knowledge?.ordinal
?: 0) >= PokedexEntryProgress.SEEN.ordinal
)
}
DoubleValue(
(
pokedex
.getSpeciesRecord(speciesId)
?.getFormRecord(formName)
?.knowledge
?.ordinal
?: 0
) >= PokedexEntryProgress.SEEN.ordinal,
)
}
map["has_caught"] = put@{ params ->
functions["has_caught"] = hasCaught@{ params ->
val speciesId = params.getString(0).asIdentifierDefaultingNamespace()
val formName = params.getStringOrNull(1)
if (formName == null) {
return@put DoubleValue(pokedex.getHighestKnowledgeForSpecies(speciesId) == PokedexEntryProgress.OWNED)
} else {
return@put DoubleValue(
pokedex.getSpeciesRecord(speciesId)
?.getFormRecord(formName)?.knowledge == PokedexEntryProgress.OWNED
val formName =
params.getStringOrNull(1) ?: return@hasCaught DoubleValue(
pokedex.getHighestKnowledgeForSpecies(
speciesId,
) == PokedexEntryProgress.OWNED,
)
}
DoubleValue(
pokedex
.getSpeciesRecord(speciesId)
?.getFormRecord(formName)
?.knowledge == PokedexEntryProgress.OWNED,
)
}
map["caught_count"] = { DoubleValue(pokedex.getGlobalCalculatedValue(CaughtCount)) }
map["seen_count"] = { DoubleValue(pokedex.getGlobalCalculatedValue(SeenCount)) }
map["caught_percent"] = { DoubleValue(pokedex.getGlobalCalculatedValue(CaughtPercent)) }
map["seen_percent"] = { DoubleValue(pokedex.getGlobalCalculatedValue(SeenPercent)) }
functions["caught_count"] = { DoubleValue(pokedex.getGlobalCalculatedValue(CaughtCount)) }
functions["seen_count"] = { DoubleValue(pokedex.getGlobalCalculatedValue(SeenCount)) }
functions["caught_percent"] = { DoubleValue(pokedex.getGlobalCalculatedValue(CaughtPercent)) }
functions["seen_percent"] = { DoubleValue(pokedex.getGlobalCalculatedValue(SeenPercent)) }
if (pokedex is PokedexManager) {
map["player_id"] = { StringValue(pokedex.uuid.toString()) }
map["see"] = put@{ params ->
val pokemon = params.get<ObjectValue<Pokemon>>(0).obj
pokedex.encounter(pokemon)
return@put DoubleValue.ONE
}
map["catch"] = put@{ params ->
val pokemon = params.get<ObjectValue<Pokemon>>(0).obj
pokedex.obtain(pokemon)
return@put DoubleValue.ONE
functions["dex_caught_count"] = { params ->
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, CaughtCount))
else DoubleValue.ZERO
}
functions["dex_seen_count"] = { params ->
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, SeenCount))
else DoubleValue.ZERO
}
functions["dex_caught_percent"] = { params ->
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, CaughtPercent).toDouble())
else DoubleValue.ZERO
}
functions["dex_seen_percent"] = { params ->
val dexId = params.getString(0).asIdentifierDefaultingNamespace()
if (Dexes.dexEntryMap.containsKey(dexId)) DoubleValue(pokedex.getDexCalculatedValue(dexId, SeenPercent).toDouble())
else DoubleValue.ZERO
}
when (pokedex) {
is PokedexManager -> {
functions["player_id"] = { StringValue(pokedex.uuid.toString()) }
functions["see"] = { params ->
val pokemon = params.get<ObjectValue<Pokemon>>(0).obj
pokedex.encounter(pokemon)
DoubleValue.ONE
}
functions["catch"] = { params ->
val pokemon = params.get<ObjectValue<Pokemon>>(0).obj
pokedex.obtain(pokemon)
DoubleValue.ONE
}
}
}
return map
return functions
}
}
@@ -32,100 +32,113 @@ import com.cobblemon.mod.common.util.withQueryValue
object PokemonEntityMoLangFunctions : AbstractMoLangFunctionHolder<PokemonEntity>() {
override fun PokemonEntity.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val pokemonEntity = this
val map = mutableMapOf<String, (MoParams) -> Any>()
map["is_busy"] = { DoubleValue(pokemonEntity.isBusy) }
map["in_battle"] = { DoubleValue(pokemonEntity.isBattling) }
map["is_moving"] = { DoubleValue((pokemonEntity.moveControl as? PokemonMoveControl)?.hasWanted() == true) }
map["is_flying"] = { DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.FLYING)) }
map["get_riding_state"] = { params ->
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["is_busy"] = { DoubleValue(pokemonEntity.isBusy) }
functions["in_battle"] = { DoubleValue(pokemonEntity.isBattling) }
functions["is_moving"] =
{ DoubleValue((pokemonEntity.moveControl as? PokemonMoveControl)?.hasWanted() == true) }
functions["is_flying"] = { DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.FLYING)) }
functions["get_riding_state"] = { params ->
val name = params.getStringOrNull(0)
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
return@ifRidingAvailableSupply moValue.functions[name]?.apply(MoParams.EMPTY) as? MoValue ?: moValue
}
}
map["is_gliding"] = {
functions["is_gliding"] = {
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
return@ifRidingAvailableSupply moValue.functions["gliding"]?.apply(MoParams.EMPTY) as? MoValue
?: DoubleValue.ZERO
}
}
map["is_sprinting"] = {
functions["is_sprinting"] = {
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
return@ifRidingAvailableSupply moValue.functions["sprinting"]?.apply(MoParams.EMPTY) as? MoValue
?: DoubleValue.ZERO
}
}
map["is_drifting"] = {
functions["is_drifting"] = {
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
return@ifRidingAvailableSupply moValue.functions["drifting"]?.apply(MoParams.EMPTY) as? MoValue
?: DoubleValue.ZERO
}
}
map["is_powered_drifting"] = {
functions["is_powered_drifting"] = {
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
return@ifRidingAvailableSupply moValue.functions["powered_drifting"]?.apply(MoParams.EMPTY) as? MoValue
?: DoubleValue.ZERO
}
}
map["in_air"] = {
functions["in_air"] = {
pokemonEntity.ifRidingAvailableSupply(DoubleValue.ZERO) { behaviour, settings, state ->
val moValue = behaviour.asMoLangValue(settings, state, pokemonEntity)
return@ifRidingAvailableSupply moValue.functions["in_air"]?.apply(MoParams.EMPTY) as? MoValue
?: DoubleValue.ZERO
}
}
map["is_wild"] = { DoubleValue(pokemonEntity.ownerUUID == null) }
map["is_in_party"] = { DoubleValue(pokemonEntity.pokemon.storeCoordinates.get()?.store is PartyStore) }
map["is_ridden"] = { DoubleValue(pokemonEntity.hasControllingPassenger()) }
map["has_aspect"] = { DoubleValue(it.getString(0) in pokemonEntity.aspects) }
map["is_pokemon"] = { DoubleValue.ONE }
map["is_holding_item"] = {
DoubleValue(!pokemonEntity.entityData.get(PokemonEntity.SHOWN_HELD_ITEM).let {
it.isEmpty || it.`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS) || it.`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS)
})
functions["is_wild"] = { DoubleValue(pokemonEntity.ownerUUID == null) }
functions["is_in_party"] = {
DoubleValue(
pokemonEntity.pokemon.storeCoordinates
.get()
?.store is PartyStore,
)
}
map["riding_style"] = {
StringValue(pokemonEntity.ifRidingAvailableSupply("") { behaviour, settings, state ->
behaviour.getRidingStyle(settings, state).name
})
functions["is_ridden"] = { DoubleValue(pokemonEntity.hasControllingPassenger()) }
functions["has_aspect"] = { DoubleValue(it.getString(0) in pokemonEntity.aspects) }
functions["is_pokemon"] = { DoubleValue.ONE }
functions["is_holding_item"] = {
DoubleValue(
!pokemonEntity.entityData.get(PokemonEntity.SHOWN_HELD_ITEM).let {
it.isEmpty || it.`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS) || it.`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS)
},
)
}
map["is_wearing_hat"] = {
functions["riding_style"] = {
StringValue(
pokemonEntity.ifRidingAvailableSupply("") { behaviour, settings, state ->
behaviour.getRidingStyle(settings, state).name
},
)
}
functions["is_wearing_hat"] = {
DoubleValue(
pokemonEntity.entityData
.get(PokemonEntity.SHOWN_HELD_ITEM)
.`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS)
.`is`(CobblemonItemTags.WEARABLE_HAT_ITEMS),
)
}
map["is_wearing_face"] = {
functions["is_wearing_face"] = {
DoubleValue(
pokemonEntity.entityData
.get(PokemonEntity.SHOWN_HELD_ITEM)
.`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS)
.`is`(CobblemonItemTags.WEARABLE_FACE_ITEMS),
)
}
map["is_pastured"] = {
functions["is_pastured"] = {
DoubleValue((pokemonEntity.tethering != null))
}
map["pasture_conflict_enabled"] = {
functions["pasture_conflict_enabled"] = {
DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT))
}
map["run_action_effect"] = runActionEffect@{ params ->
functions["run_action_effect"] = runActionEffect@{ params ->
val runtime = MoLangRuntime().setup()
runtime.environment.cloneFrom(params.environment)
runtime.withQueryValue("entity", pokemonEntity.struct)
val actionEffect = ActionEffects.actionEffects[params.getString(0).asIdentifierDefaultingNamespace()]
if (actionEffect != null) {
val context = ActionEffectContext(
actionEffect = actionEffect,
providers = mutableListOf(TargetsProvider(pokemonEntity)),
runtime = runtime,
level = pokemonEntity.level()
)
val context =
ActionEffectContext(
actionEffect = actionEffect,
providers = mutableListOf(TargetsProvider(pokemonEntity)),
runtime = runtime,
level = pokemonEntity.level(),
)
// This was taken from NPC run_action_effect but this variable isn't used, so I just commented it out.
// pokemonEntity.actionEffect = context
pokemonEntity.brain.setMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT, context)
@@ -135,14 +148,16 @@ object PokemonEntityMoLangFunctions : AbstractMoLangFunctionHolder<PokemonEntity
pokemonEntity.brain.getMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT).orElse(null)
if (pokemonActionEffect == context && pokemonEntity.brain.isActive(CobblemonActivities.ACTION_EFFECT)) {
pokemonEntity.brain.eraseMemory(CobblemonMemories.ACTIVE_ACTION_EFFECT)
//pokemonEntity.actionEffect = null
// pokemonEntity.actionEffect = null
}
}
return@runActionEffect DoubleValue.ONE
}
return@runActionEffect DoubleValue.ZERO
DoubleValue.ZERO
}
return map
return functions
}
}
@@ -33,218 +33,214 @@ import com.cobblemon.mod.common.util.getDoubleOrNull
import com.cobblemon.mod.common.util.getIntOrNull
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.util.toProperties
import kotlin.random.Random
import net.minecraft.resources.ResourceLocation
import kotlin.random.Random
object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
override fun Pokemon.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val pokemon = this
val map = mutableMapOf<String, (MoParams) -> Any>()
map["id"] = { StringValue(pokemon.uuid.toString()) }
map["nickname"] = { StringValue(pokemon.nickname.toString()) }
map["level"] = { DoubleValue(pokemon.level.toDouble()) }
map["max_hp"] = { DoubleValue(pokemon.maxHealth.toDouble()) }
map["current_hp"] = { DoubleValue(pokemon.currentHealth.toDouble()) }
map["friendship"] = { DoubleValue(pokemon.friendship.toDouble()) }
map["max_fullness"] = { DoubleValue(pokemon.getMaxFullness().toDouble()) }
map["fullness"] = { DoubleValue(pokemon.currentFullness.toDouble()) }
map["lose_fullness"] = { params ->
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["id"] = { StringValue(pokemon.uuid.toString()) }
functions["nickname"] = { StringValue(pokemon.nickname.toString()) }
functions["level"] = { DoubleValue(pokemon.level.toDouble()) }
functions["max_hp"] = { DoubleValue(pokemon.maxHealth.toDouble()) }
functions["current_hp"] = { DoubleValue(pokemon.currentHealth.toDouble()) }
functions["friendship"] = { DoubleValue(pokemon.friendship.toDouble()) }
functions["max_fullness"] = { DoubleValue(pokemon.getMaxFullness().toDouble()) }
functions["fullness"] = { DoubleValue(pokemon.currentFullness.toDouble()) }
functions["lose_fullness"] = { params ->
val amount = params.getDouble(0)
pokemon.loseFullness(amount.toInt())
}
map["feed_pokemon"] = { params ->
functions["feed_pokemon"] = { params ->
val amount = params.getDouble(0)
val playSound = params.getBooleanOrNull(1) ?: true
pokemon.feedPokemon(amount.toInt(), playSound)
}
map["behaviour"] = { pokemon.form.behaviour.struct }
map["behavior"] = { pokemon.form.behaviour.struct } // Inferior
map["pokeball"] = { StringValue(pokemon.caughtBall.toString()) }
map["ability"] = { StringValue(pokemon.ability.name) }
map["has_learned"] = put@{ params ->
functions["behaviour"] = { pokemon.form.behaviour.struct }
functions["behavior"] = { pokemon.form.behaviour.struct } // Inferior
functions["pokeball"] = { StringValue(pokemon.caughtBall.toString()) }
functions["ability"] = { StringValue(pokemon.ability.name) }
functions["has_learned"] = hasLearned@{ params ->
val moveName = params.getString(0)
val move = pokemon.allAccessibleMoves.find { it.name == moveName }
if (move != null) {
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
if (move != null) return@hasLearned DoubleValue.ONE
DoubleValue.ZERO
}
map["moveset"] = {
functions["moveset"] = {
val struct = QueryStruct(hashMapOf())
for ((index, move) in pokemon.moveSet.withIndex()) {
struct.addFunction(index.toString()) { move.struct }
}
struct
}
map["evs"] = {
functions["evs"] = {
val struct = QueryStruct(hashMapOf())
for (stat in Stats.PERMANENT) {
struct.addFunction(stat.showdownId) { DoubleValue(pokemon.evs.getOrDefault(stat).toDouble()) }
}
struct
}
map["ivs"] = {
functions["ivs"] = {
val struct = QueryStruct(hashMapOf())
for (stat in Stats.PERMANENT) {
struct.addFunction(stat.showdownId) { DoubleValue(pokemon.ivs.getOrDefault(stat).toDouble()) }
}
struct
}
map["hyper_trained_ivs"] = {
functions["hyper_trained_ivs"] = {
val struct = QueryStruct(hashMapOf())
for (stat in Stats.PERMANENT) {
struct.addFunction(stat.showdownId) { DoubleValue(pokemon.ivs.hyperTrainedIVs[stat] ?: -1.0) }
}
struct
}
map["ride_boosts"] = {
functions["ride_boosts"] = {
val struct = QueryStruct(hashMapOf())
for (stat in RidingStat.entries) {
struct.addFunction(stat.name.lowercase()) { DoubleValue(pokemon.getRideBoost(stat)) }
}
struct
}
map["natdex_number"] = {
functions["natdex_number"] = {
DoubleValue(pokemon.species.nationalPokedexNumber.toDouble())
}
map["types"] = {
pokemon.form.types.map { it.showdownId }.asArrayValue(::StringValue)
functions["types"] = {
pokemon.form.types
.map { it.showdownId }
.asArrayValue(::StringValue)
}
map["gender_ratio"] = {
functions["gender_ratio"] = {
DoubleValue(pokemon.form.maleRatio.toDouble())
}
map["ev_yield"] = {
functions["ev_yield"] = {
val struct = QueryStruct(hashMapOf())
for (stat in Stats.PERMANENT) {
struct.addFunction(stat.showdownId) { DoubleValue(pokemon.form.evYield[stat]?.toDouble()) }
}
struct
}
map["base_stats"] = {
functions["base_stats"] = {
val struct = QueryStruct(hashMapOf())
for (stat in Stats.PERMANENT) {
struct.addFunction(stat.showdownId) { DoubleValue(pokemon.form.baseStats[stat]?.toDouble()) }
}
struct
}
map["catch_rate"] = {
functions["catch_rate"] = {
DoubleValue(pokemon.form.catchRate.toDouble())
}
map["base_experience_yield"] = {
functions["base_experience_yield"] = {
DoubleValue(pokemon.form.baseExperienceYield.toDouble())
}
map["drops"] = {
functions["drops"] = {
val struct = QueryStruct(hashMapOf())
for ((index, drop) in pokemon.form.drops.entries.withIndex()) {
for ((index, drop) in pokemon.form.drops.entries
.withIndex()) {
struct.addFunction(index.toString()) { drop }
}
struct
}
map["tm_learnset"] = {
functions["tm_learnset"] = {
val struct = QueryStruct(hashMapOf())
for ((index, move) in pokemon.form.moves.tmMoves.withIndex()) {
for ((index, move) in pokemon.form.moves.tmMoves
.withIndex()) {
struct.addFunction(index.toString()) { move.struct }
}
struct
}
map["egg_learnset"] = {
functions["egg_learnset"] = {
val struct = QueryStruct(hashMapOf())
for ((index, move) in pokemon.form.moves.eggMoves.withIndex()) {
for ((index, move) in pokemon.form.moves.eggMoves
.withIndex()) {
struct.addFunction(index.toString()) { move.struct }
}
struct
}
map["tutor_learnset"] = {
functions["tutor_learnset"] = {
val struct = QueryStruct(hashMapOf())
for ((index, move) in pokemon.form.moves.tutorMoves.withIndex()) {
for ((index, move) in pokemon.form.moves.tutorMoves
.withIndex()) {
struct.addFunction(index.toString()) { move.struct }
}
struct
}
map["level_learnset"] = {
functions["level_learnset"] = {
val struct = QueryStruct(hashMapOf())
for ((index, move) in pokemon.form.moves.levelUpMoves) {
struct.addFunction(index.toString()) { move }
}
struct
}
map["ability_pool"] = {
functions["ability_pool"] = {
val struct = QueryStruct(hashMapOf())
for ((index, ability) in pokemon.form.abilities.withIndex()) {
struct.addFunction(index.toString()) { StringValue(ability.toString()) }
}
struct
}
map["egg_groups"] = {
functions["egg_groups"] = {
val struct = QueryStruct(hashMapOf())
for ((index, group) in pokemon.form.eggGroups.withIndex()) {
struct.addFunction(index.toString()) { StringValue(group.toString()) }
}
struct
}
map["egg_cycles"] = {
functions["egg_cycles"] = {
DoubleValue(pokemon.species.eggCycles.toDouble())
}
map["labels"] = {
functions["labels"] = {
val struct = QueryStruct(hashMapOf())
for ((index, label) in pokemon.form.labels.withIndex()) {
struct.addFunction(index.toString()) { StringValue(label) }
}
struct
}
map["aspects"] = {
functions["aspects"] = {
val aspects = pokemon.aspects
aspects.asArrayValue { StringValue(it) }
}
map["form_aspects"] = {
functions["form_aspects"] = {
val aspects = pokemon.form.aspects
aspects.asArrayValue { StringValue(it) }
}
map["form_name"] = {
functions["form_name"] = {
StringValue(pokemon.form.name)
}
// Yes, this is the lazy call for a single hardcoded pre-evolution.
// Lol. Lmao, even.
// TO-DO: Subscribe to [PokemonSpecies.observable].
// Gito: surely this is just a typo, right? not touching the function name just in case
map["prevolution"] = preEvolution@{
val preEvolution = pokemon.species.preEvolution ?: return@preEvolution DoubleValue.ZERO
return@preEvolution preEvolution
}
map["nature"] = { StringValue(pokemon.nature.toString()) }
map["is_wild"] = { DoubleValue(pokemon.entity?.let { it.ownerUUID == null } == true) }
map["is_shiny"] = { DoubleValue(pokemon.shiny) }
map["is_in_party"] = { DoubleValue(pokemon.storeCoordinates.get()?.store is PartyStore) }
map["species"] = { pokemon.species.struct }
map["form"] = { StringValue(pokemon.form.name) }
map["weight"] = { DoubleValue(pokemon.species.weight.toDouble()) }
map["matches"] = { params -> DoubleValue(params.getString(0).toProperties().matches(pokemon)) }
map["apply"] = { params ->
functions["pre_evolution"] = { pokemon.species.preEvolution ?: DoubleValue.ZERO }
functions["nature"] = { StringValue(pokemon.nature.name.toString()) }
functions["is_wild"] = { DoubleValue(pokemon.entity?.let { it.ownerUUID == null } == true) }
functions["is_shiny"] = { DoubleValue(pokemon.shiny) }
functions["is_in_party"] = { DoubleValue(pokemon.storeCoordinates.get()?.store is PartyStore) }
functions["species"] = { pokemon.species.struct }
functions["form"] = { StringValue(pokemon.form.name) }
functions["weight"] = { DoubleValue(pokemon.species.weight.toDouble()) }
functions["matches"] = { params -> DoubleValue(params.getString(0).toProperties().matches(pokemon)) }
functions["apply"] = { params ->
params.getString(0).toProperties().apply(pokemon)
DoubleValue.ONE
}
map["owner"] = { pokemon.getOwnerPlayer()?.asMoLangValue() ?: DoubleValue.ZERO }
map["held_item"] = { pokemon.heldItem().asMoLangValue(server()!!.registryAccess()) }
map["remove_held_item"] = { _ ->
pokemon.removeHeldItem()
}
map["add_aspects"] = { params ->
functions["owner"] = { pokemon.getOwnerPlayer()?.asMoLangValue() ?: DoubleValue.ZERO }
functions["held_item"] = { pokemon.heldItem().asMoLangValue(server()!!.registryAccess()) }
functions["remove_held_item"] = { pokemon.removeHeldItem() }
functions["add_aspects"] = { params ->
for (aspect in params.params) pokemon.forcedAspects += aspect.asString()
pokemon.updateAspects()
}
map["remove_aspects"] = { params ->
functions["remove_aspects"] = { params ->
for (aspect in params.params) pokemon.forcedAspects -= aspect.asString()
pokemon.updateAspects()
}
map["cosmetic_item"] = {
functions["cosmetic_item"] = {
pokemon.cosmeticItem().asMoLangValue(server()!!.registryAccess())
}
map["remove_cosmetic_item"] = { _ ->
pokemon.removeCosmeticItem()
}
map["marks"] = { _ -> pokemon.marks.asArrayValue { StringValue(it.identifier.toString()) } }
map["has_mark"] = { params ->
functions["remove_cosmetic_item"] = { pokemon.removeCosmeticItem() }
functions["marks"] = { pokemon.marks.asArrayValue { StringValue(it.identifier.toString()) } }
functions["has_mark"] = { params ->
var hasMark = false
val identifier = params.getString(0).asIdentifierDefaultingNamespace()
val mark = Marks.getByIdentifier(identifier)
@@ -254,7 +250,7 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
DoubleValue(hasMark)
}
map["remove_marks"] = { params ->
functions["remove_marks"] = { params ->
var removedMark = false
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
@@ -267,7 +263,7 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
DoubleValue(removedMark)
}
map["add_marks"] = { params ->
functions["add_marks"] = { params ->
var appliedMark = false
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
@@ -280,7 +276,7 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
DoubleValue(appliedMark)
}
map["add_marks_with_chance"] = { params ->
functions["add_marks_with_chance"] = { params ->
var appliedMark = false
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
@@ -298,17 +294,15 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
DoubleValue(appliedMark)
}
map["add_potential_marks"] = { params ->
functions["add_potential_marks"] = { params ->
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
val mark = Marks.getByIdentifier(identifier)
if (mark != null) pokemon.addPotentialMark(mark)
}
}
map["apply_potential_marks"] = put@{
return@put DoubleValue(pokemon.applyPotentialMarks())
}
map["hyper_train_iv"] = hyperTrainIv@{ params ->
functions["apply_potential_marks"] = { DoubleValue(pokemon.applyPotentialMarks()) }
functions["hyper_train_iv"] = hyperTrainIv@{ params ->
val statId = params.getString(0)
val stat = Stats.getStat(statId)
val value = params.getIntOrNull(1) ?: IVs.MAX_VALUE
@@ -321,12 +315,12 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
return@hyperTrainIv DoubleValue.ZERO
}
}
map["add_exp"] = { params ->
functions["add_exp"] = { params ->
val exp = params.getDouble(0).toInt()
pokemon.addExperience(SidemodExperienceSource("molang"), exp)
DoubleValue.ONE
}
map["set_iv"] = setIv@{ params ->
functions["set_iv"] = setIv@{ params ->
val statId = params.getString(0)
val stat = Stats.getStat(statId)
val value = params.getIntOrNull(1)?.coerceIn(0, IVs.MAX_VALUE) ?: IVs.MAX_VALUE
@@ -338,7 +332,7 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
return@setIv DoubleValue.ZERO
}
}
map["set_ev"] = setEv@{ params ->
functions["set_ev"] = setEv@{ params ->
val statId = params.getString(0)
val stat = Stats.getStat(statId)
val value = (params.getIntOrNull(1) ?: 0).coerceIn(0, 255)
@@ -350,49 +344,54 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
return@setEv DoubleValue.ZERO
}
}
map["set_ride_boost"] = setRideBoost@{ params ->
functions["set_ride_boost"] = setRideBoost@{ params ->
val statName = params.getString(0).uppercase()
val stat = RidingStat.entries.find { it.name.equals(statName, ignoreCase = true) }
?: return@setRideBoost DoubleValue.ZERO
val stat =
RidingStat.entries.find { it.name.equals(statName, ignoreCase = true) }
?: return@setRideBoost DoubleValue.ZERO
val value = params.getDoubleOrNull(1)?.toFloat() ?: return@setRideBoost DoubleValue.ZERO
pokemon.setRideBoost(stat, value)
return@setRideBoost DoubleValue.ONE
DoubleValue.ONE
}
map["add_ride_boost"] = addRideBoost@{ params ->
functions["add_ride_boost"] = addRideBoost@{ params ->
val statName = params.getString(0).uppercase()
val stat = RidingStat.entries.find { it.name.equals(statName, ignoreCase = true) }
?: return@addRideBoost DoubleValue.ZERO
val stat =
RidingStat.entries.find { it.name.equals(statName, ignoreCase = true) }
?: return@addRideBoost DoubleValue.ZERO
val value = params.getDoubleOrNull(1)?.toFloat() ?: return@addRideBoost DoubleValue.ZERO
return@addRideBoost DoubleValue(if (pokemon.addRideBoost(stat, value)) 1.0 else 0.0)
DoubleValue(if (pokemon.addRideBoost(stat, value)) 1.0 else 0.0)
}
map["initialize_moveset"] = put@{ params ->
functions["initialize_moveset"] = initializeMoveset@{ params ->
val param = params.get<MoValue>(0) ?: StringValue(pokemon.form.defaultWildMovesetBuilder.toString())
if (param is DoubleValue) {
pokemon.initializeMoveset(param == DoubleValue.ONE)
} else {
val movesetBuilderId = ResourceLocation.parse(param.asString())
val movesetBuilder = CobblemonMovesetBuilders.movesetBuilders[movesetBuilderId]
?: run {
Cobblemon.LOGGER.error("Tried initializing moveset from moveset builder ${param.asString()} but it does not exist.")
return@put DoubleValue.ZERO
}
val movesetBuilder =
CobblemonMovesetBuilders.movesetBuilders[movesetBuilderId]
?: run {
Cobblemon.LOGGER.error(
"Tried initializing moveset from moveset builder ${param.asString()} but it does not exist.",
)
return@initializeMoveset DoubleValue.ZERO
}
pokemon.initializeMovesetFrom(movesetBuilder)
}
DoubleValue.ONE
}
map["validate_moveset"] = { params ->
functions["validate_moveset"] = { params ->
val includeLegacy = params.getBooleanOrNull(0) ?: true
pokemon.validateMoveset(includeLegacy)
DoubleValue.ONE
}
map["teach_learnable_moves"] = { params ->
functions["teach_learnable_moves"] = { params ->
val includeLegacy = params.getBooleanOrNull(0) ?: true
pokemon.teachLearnableMoves(includeLegacy)
DoubleValue.ONE
}
map["teach_move"] = teachMove@{ params ->
functions["teach_move"] = teachMove@{ params ->
val moveName = params.getString(0)
val moveTemplate = Moves.getByName(moveName) ?: return@teachMove DoubleValue.ZERO
val bypass = params.getBooleanOrNull(1) ?: false
@@ -402,8 +401,9 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
return@teachMove DoubleValue.ZERO
}
val alreadyKnows = pokemon.moveSet.getMoves().any { it.template == moveTemplate } ||
pokemon.benchedMoves.any { it.moveTemplate == moveTemplate }
val alreadyKnows =
pokemon.moveSet.getMoves().any { it.template == moveTemplate } ||
pokemon.benchedMoves.any { it.moveTemplate == moveTemplate }
if (alreadyKnows) {
return@teachMove DoubleValue.ZERO
}
@@ -416,42 +416,43 @@ object PokemonMoLangFunctions : AbstractMoLangFunctionHolder<Pokemon>() {
DoubleValue.ONE
}
map["can_learn_move"] = canLearnMove@{ params ->
functions["can_learn_move"] = canLearnMove@{ params ->
val moveName = params.getString(0)
val moveTemplate = Moves.getByName(moveName) ?: return@canLearnMove DoubleValue.ZERO
val includeLegacy = params.getBooleanOrNull(1) ?: true
val canLearn = if (includeLegacy) {
LearnsetQuery.ANY.canLearn(moveTemplate, pokemon.form.moves)
} else {
LearnsetQuery.LEGAL.canLearn(moveTemplate, pokemon.form.moves)
}
return@canLearnMove DoubleValue(if (canLearn) 1.0 else 0.0)
val canLearn =
if (includeLegacy) {
LearnsetQuery.ANY.canLearn(moveTemplate, pokemon.form.moves)
} else {
LearnsetQuery.LEGAL.canLearn(moveTemplate, pokemon.form.moves)
}
DoubleValue(if (canLearn) 1.0 else 0.0)
}
map["unlearn_move"] = unlearnMove@{ params ->
functions["unlearn_move"] = unlearnMove@{ params ->
val moveName = params.getString(0)
val moveTemplate = Moves.getByName(moveName)
if (moveTemplate != null) {
pokemon.unlearnMove(moveTemplate)
return@unlearnMove DoubleValue.ONE
} else {
return@unlearnMove DoubleValue.ZERO
}
DoubleValue.ZERO
}
map["can_evolve"] = { _ ->
DoubleValue(pokemon.evolutions.any())
}
map["force_evolve"] = forceEvolve@{ params ->
functions["can_evolve"] = { DoubleValue(pokemon.evolutions.any()) }
functions["force_evolve"] = forceEvolve@{ params ->
val idx = params.getInt(0)
val evolution = if (idx >= 0) pokemon.evolutions.elementAtOrNull(idx) else return@forceEvolve DoubleValue.ZERO
val evolution =
if (idx >= 0) pokemon.evolutions.elementAtOrNull(idx) else return@forceEvolve DoubleValue.ZERO
evolution?.forceEvolve(pokemon)
StringValue(evolution.toString())
}
map["entity"] = { pokemon.entity?.asMoLangValue() ?: DoubleValue.ZERO }
map["is_alpha"] = { DoubleValue(pokemon.isAlpha) }
map["size_category"] = { _ -> StringValue(pokemon.getSizeCategory().name) }
functions["entity"] = { pokemon.entity?.asMoLangValue() ?: DoubleValue.ZERO }
functions["is_alpha"] = { DoubleValue(pokemon.isAlpha) }
functions["size_category"] = { StringValue(pokemon.getSizeCategory().name) }
return map
return functions
}
}
@@ -20,52 +20,50 @@ import com.cobblemon.mod.common.util.getStringOrNull
object PokemonPropertiesMoLangFunctions : AbstractMoLangFunctionHolder<PokemonProperties>() {
override fun PokemonProperties.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val props = this
val map = mutableMapOf<String, (MoParams) -> Any>()
val functions = hashMapOf<String, (MoParams) -> Any>()
map["level"] = { DoubleValue(props.level?.toDouble() ?: 0) }
map["set_level"] = { params ->
functions["level"] = { DoubleValue(props.level?.toDouble() ?: 0) }
functions["set_level"] = { params ->
props.level = params.getIntOrNull(0)
DoubleValue.ONE
}
map["shiny"] = { DoubleValue(props.shiny) }
map["set_shiny"] = { params ->
functions["shiny"] = { DoubleValue(props.shiny) }
functions["set_shiny"] = { params ->
props.shiny = params.getBooleanOrNull(0)
DoubleValue.ONE
}
map["species"] = { props.species?.let { StringValue(it) } ?: DoubleValue.ZERO }
map["set_species"] = { params ->
functions["species"] = { props.species?.let { StringValue(it) } ?: DoubleValue.ZERO }
functions["set_species"] = { params ->
props.species = params.getStringOrNull(0)
DoubleValue.ONE
}
map["gender"] = { props.gender?.let { StringValue(it.name) } ?: DoubleValue.ZERO }
map["set_gender"] = { params ->
functions["gender"] = { props.gender?.let { StringValue(it.name) } ?: DoubleValue.ZERO }
functions["set_gender"] = { params ->
props.gender = params.getStringOrNull(0)?.let { Gender.valueOf(it) }
DoubleValue.ONE
}
map["form"] = { props.form?.let { StringValue(it) } ?: DoubleValue.ZERO }
map["ivs"] = ivs@{
functions["form"] = { props.form?.let { StringValue(it) } ?: DoubleValue.ZERO }
functions["ivs"] = ivs@{
val ivs = props.ivs ?: return@ivs DoubleValue.ZERO
return@ivs ivs.struct
ivs.struct
}
map["evs"] = evs@{
functions["evs"] = evs@{
val evs = props.evs ?: return@evs DoubleValue.ZERO
return@evs evs.struct
evs.struct
}
map["friendship"] = { DoubleValue(props.friendship?.toDouble() ?: DoubleValue.ZERO) }
map["set_friendship"] = { params ->
functions["friendship"] = { DoubleValue(props.friendship?.toDouble() ?: DoubleValue.ZERO) }
functions["set_friendship"] = { params ->
props.friendship = params.getIntOrNull(0)
DoubleValue.ONE
}
map["create"] = {
val pokemon = props.create()
pokemon.struct
}
map["to_string"] = { params ->
functions["create"] = { (props.create()).struct }
functions["to_string"] = { params ->
val separator = params.getStringOrNull(0) ?: " "
StringValue(props.asString(separator = separator))
}
return map
return functions
}
}
@@ -22,83 +22,84 @@ import com.cobblemon.mod.common.util.asUUID
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.toProperties
object PokemonStoreMoLangFunctions: AbstractMoLangFunctionHolder<PokemonStore<*>>() {
object PokemonStoreMoLangFunctions : AbstractMoLangFunctionHolder<PokemonStore<*>>() {
override fun PokemonStore<*>.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val store = this
val map = mutableMapOf<String, (MoParams) -> Any>()
map["uuid"] = { StringValue(store.uuid.toString()) }
map["add"] = { params ->
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["uuid"] = { StringValue(store.uuid.toString()) }
functions["add"] = { params ->
val pokemon = params.get<ObjectValue<Pokemon>>(0)
DoubleValue(store.add(pokemon.obj))
}
map["add_by_properties"] = { params ->
functions["add_by_properties"] = { params ->
val props = params.getString(0).toProperties()
val player = (store as? PlayerPartyStore)?.playerUUID?.getPlayer() // Only really know for sure when it's a player party store
val player =
(store as? PlayerPartyStore)?.playerUUID?.getPlayer() // Only really know for sure when it's a player party store
val pokemon = props.create(player = player)
DoubleValue(store.add(pokemon))
}
map["find_by_properties"] = { params ->
functions["find_by_properties"] = { params ->
val props = params.getString(0).toProperties()
val pokemon = store.find { props.matches(it) }
pokemon?.asStruct() ?: DoubleValue.ZERO
}
map["find_all_by_properties"] = { params ->
functions["find_all_by_properties"] = { params ->
val props = params.getString(0).toProperties()
val pokemon = store.filter { props.matches(it) }
ArrayStruct(pokemon.mapIndexed { index, value -> "$index" to value.asStruct() }.toMap())
}
map["find_by_id"] = { params ->
functions["find_by_id"] = { params ->
val id = params.getString(0).asUUID
val pokemon = store.find { it.uuid == id }
pokemon?.asStruct() ?: DoubleValue.ZERO
}
map["remove_by_id"] = removeById@{ params ->
functions["remove_by_id"] = removeById@{ params ->
val id = params.getString(0).asUUID
val pokemon = store.find { it.uuid == id } ?: return@removeById DoubleValue.ZERO
return@removeById DoubleValue(store.remove(pokemon))
DoubleValue(store.remove(pokemon))
}
map["average_level"] = averageLevel@{ _ ->
functions["average_level"] = averageLevel@{
var numberOfPokemon = 0
var totalLevel = 0
for (pokemon in store) {
totalLevel += pokemon.level
numberOfPokemon++
}
if (numberOfPokemon == 0) {
return@averageLevel DoubleValue.ZERO
}
return@averageLevel DoubleValue(totalLevel.toDouble() / numberOfPokemon)
if (numberOfPokemon == 0) return@averageLevel DoubleValue.ZERO
DoubleValue(totalLevel.toDouble() / numberOfPokemon)
}
map["count"] = { _ -> DoubleValue(store.count()) }
map["count_by_properties"] = { params ->
functions["count"] = { DoubleValue(store.count()) }
functions["count_by_properties"] = { params ->
val props = params.getString(0).toProperties()
DoubleValue(store.count { props.matches(it) })
}
map["highest_level"] = {
functions["highest_level"] = {
val highest = store.maxOfOrNull { it.level } ?: 0
DoubleValue(highest)
}
map["lowest_level"] = {
functions["lowest_level"] = {
val lowest = store.minOfOrNull { it.level } ?: 0
DoubleValue(lowest)
}
map["heal"] = {
functions["heal"] = {
for (pokemon in store) {
pokemon.heal()
}
DoubleValue.ONE
}
map["healing_remainder_percent"] = { _ ->
functions["healing_remainder_percent"] = {
var totalPercent = 0.0f
for (pokemon in store) {
totalPercent += (1.0f - (pokemon.currentHealth.toFloat() / pokemon.maxHealth))
}
DoubleValue(totalPercent)
}
map["has_usable_pokemon"] = { _ -> DoubleValue(store.any { !it.isFainted() }) }
map["pokemon"] = {
store.map { it.asStruct() }.asArrayValue()
}
return map
functions["has_usable_pokemon"] = { DoubleValue(store.any { !it.isFainted() }) }
functions["pokemon"] = { store.map { it.asStruct() }.asArrayValue() }
return functions
}
}
@@ -25,62 +25,63 @@ import java.util.*
object ServerMoLangFunctions : AbstractMoLangFunctionHolder<MinecraftServer>() {
override fun MinecraftServer.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val server = this
val map = mutableMapOf<String, (MoParams) -> Any>()
val functions = hashMapOf<String, (MoParams) -> Any>()
map["get_world"] = getWorld@{ params ->
val world = server.getLevel(
ResourceKey.create(
Registries.DIMENSION,
params.getString(0).asIdentifierDefaultingNamespace(namespace = "minecraft")
)
) ?: return@getWorld DoubleValue.ZERO
functions["get_world"] = getWorld@{ params ->
val world =
server.getLevel(
ResourceKey.create(
Registries.DIMENSION,
params.getString(0).asIdentifierDefaultingNamespace(namespace = "minecraft"),
),
) ?: return@getWorld DoubleValue.ZERO
return@getWorld world
world
.registryAccess()
.registryOrThrow(Registries.DIMENSION)
.wrapAsHolder(world)
.asWorldMoLangValue()
}
map["broadcast"] = { params ->
functions["broadcast"] = { params ->
val message = params.getString(0)
server.playerList.broadcastSystemMessage(message.text(), params.getBooleanOrNull(1) == true)
DoubleValue.ONE
}
map["get_player_by_uuid"] = getPlayerByUuid@{ params ->
functions["get_player_by_uuid"] = getPlayerByUuid@{ params ->
val uuid = UUID.fromString(params.getString(0))
val player = server.playerList.getPlayer(uuid) ?: return@getPlayerByUuid DoubleValue.ZERO
return@getPlayerByUuid player.asMoLangValue()
player.asMoLangValue()
}
map["get_player_by_username"] = getPlayerByUsername@{ params ->
functions["get_player_by_username"] = getPlayerByUsername@{ params ->
val username = params.getString(0)
val player = server.playerList.getPlayerByName(username) ?: return@getPlayerByUsername DoubleValue.ZERO
return@getPlayerByUsername player.asMoLangValue()
player.asMoLangValue()
}
map["data"] = { params ->
functions["data"] = { params ->
Cobblemon.molangData.load(
UUID(0L, 0L),
params.getStringOrNull(0)
params.getStringOrNull(0),
)
}
map["save_data"] = { params ->
functions["save_data"] = { params ->
Cobblemon.molangData.save(
UUID(0L, 0L),
params.getStringOrNull(index = 0)
params.getStringOrNull(index = 0),
)
DoubleValue.ONE
}
// Maybe later...
// map.put("stop") { params ->
// functions.put("stop") { params ->
// Cobblemon.LOGGER.warn("Server is being stopped from a MoLang script.")
// server.stopServer()
// }
return map
return functions
}
}
@@ -20,27 +20,34 @@ import net.minecraft.server.level.ServerPlayer
object SpawnablePositionMoLangFunctions : AbstractMoLangFunctionHolder<SpawnablePosition>() {
override fun SpawnablePosition.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val spawningContext = this
val map = mutableMapOf<String, (MoParams) -> Any>()
val worldValue = spawningContext.world.registryAccess().registryOrThrow(Registries.DIMENSION)
.wrapAsHolder(spawningContext.world).asWorldMoLangValue()
val biomeValue = spawningContext.biomeHolder.asBiomeMoLangValue()
map["biome"] = { _ -> biomeValue }
map["world"] = { _ -> worldValue }
map["light"] = { _ -> DoubleValue(spawningContext.light.toDouble()) }
map["x"] = { _ -> DoubleValue(spawningContext.position.x.toDouble()) }
map["y"] = { _ -> DoubleValue(spawningContext.position.y.toDouble()) }
map["z"] = { _ -> DoubleValue(spawningContext.position.z.toDouble()) }
map["moon_phase"] = { _ -> DoubleValue(spawningContext.moonPhase.toDouble()) }
map["can_see_sky"] = { _ -> DoubleValue(spawningContext.canSeeSky) }
map["sky_light"] = { _ -> DoubleValue(spawningContext.skyLight.toDouble()) }
map["player"] = put@{ _ ->
val causeEntity = spawningContext.cause.entity ?: return@put DoubleValue.ZERO
if (causeEntity is ServerPlayer) {
return@put causeEntity.asMoLangValue()
} else {
return@put DoubleValue.ZERO
}
val worldValue =
spawningContext
.world
.registryAccess()
.registryOrThrow(Registries.DIMENSION)
.wrapAsHolder(spawningContext.world)
.asWorldMoLangValue()
val biomeValue =
spawningContext
.biomeHolder
.asBiomeMoLangValue()
val functions = hashMapOf<String, (MoParams) -> Any>()
functions["biome"] = { biomeValue }
functions["world"] = { worldValue }
functions["light"] = { DoubleValue(spawningContext.light.toDouble()) }
functions["x"] = { DoubleValue(spawningContext.position.x.toDouble()) }
functions["y"] = { DoubleValue(spawningContext.position.y.toDouble()) }
functions["z"] = { DoubleValue(spawningContext.position.z.toDouble()) }
functions["moon_phase"] = { DoubleValue(spawningContext.moonPhase.toDouble()) }
functions["can_see_sky"] = { DoubleValue(spawningContext.canSeeSky) }
functions["sky_light"] = { DoubleValue(spawningContext.skyLight.toDouble()) }
functions["player"] = player@{
val causeEntity = spawningContext.cause.entity ?: return@player DoubleValue.ZERO
if (causeEntity is ServerPlayer) return@player causeEntity.asMoLangValue()
DoubleValue.ZERO
}
return map
return functions
}
}
@@ -17,23 +17,23 @@ import com.cobblemon.mod.common.util.asArrayValue
object SpeciesMoLangFunctions : AbstractMoLangFunctionHolder<Species>() {
override fun Species.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val species = this
val map = mutableMapOf<String, (MoParams) -> Any>(
"identifier" to { StringValue(species.resourceIdentifier.toString()) },
"name" to { StringValue(species.name) },
"primary_type" to { StringValue(species.primaryType.showdownId) },
"secondary_type" to { StringValue(species.secondaryType?.showdownId ?: "null") },
"experience_group" to { StringValue(species.experienceGroup.name) },
"height" to { DoubleValue(species.height) },
"weight" to { DoubleValue(species.weight) },
"base_scale" to { DoubleValue(species.baseScale) },
"hitbox_width" to { DoubleValue(species.hitbox.width) },
"hitbox_height" to { DoubleValue(species.hitbox.height) },
"hitbox_fixed" to { DoubleValue(species.hitbox.fixed) },
"catch_rate" to { DoubleValue(species.catchRate) },
"labels" to { species.labels.asArrayValue { StringValue(it) } },
"has_label" to { params -> DoubleValue(species.labels.contains(params.getString(0))) }
)
val functions = hashMapOf<String, (MoParams) -> Any>()
return map
functions["identifier"] = { StringValue(species.resourceIdentifier.toString()) }
functions["name"] = { StringValue(species.name) }
functions["primary_type"] = { StringValue(species.primaryType.showdownId) }
functions["secondary_type"] = { StringValue(species.secondaryType?.showdownId ?: "null") }
functions["experience_group"] = { StringValue(species.experienceGroup.name) }
functions["height"] = { DoubleValue(species.height) }
functions["weight"] = { DoubleValue(species.weight) }
functions["base_scale"] = { DoubleValue(species.baseScale) }
functions["hitbox_width"] = { DoubleValue(species.hitbox.width) }
functions["hitbox_height"] = { DoubleValue(species.hitbox.height) }
functions["hitbox_fixed"] = { DoubleValue(species.hitbox.fixed) }
functions["catch_rate"] = { DoubleValue(species.catchRate) }
functions["labels"] = { species.labels.asArrayValue { StringValue(it) } }
functions["has_label"] = { params -> DoubleValue(species.labels.contains(params.getString(0))) }
return functions
}
}
@@ -24,7 +24,6 @@ import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.net.messages.client.sound.UnvalidatedPlaySoundS2CPacket
import com.cobblemon.mod.common.util.*
import java.util.UUID
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.core.registries.Registries
@@ -46,85 +45,89 @@ import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets
import net.minecraft.world.level.storage.loot.parameters.LootContextParams
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import java.util.UUID
object WorldMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Level>>() {
override fun Holder<Level>.moLangFunctions(): MutableMap<String, (MoParams) -> Any> {
val world = this.value()
val map = mutableMapOf<String, (MoParams) -> Any>(
)
val functions = hashMapOf<String, (MoParams) -> Any>()
map["is_time_of_day"] = put@{ params ->
functions["is_time_of_day"] = isTimeOfDay@{ params ->
val timeOfDay =
TimeRange.timeRanges[params.getString(0).lowercase()]
?: return@isTimeOfDay DoubleValue.ZERO
val time = world.dayTime % 24000
val timeOfDay = TimeRange.timeRanges[params.getString(0).lowercase()]
?: return@put DoubleValue.ZERO
val time = world.dayTime % 24000
return@put DoubleValue(timeOfDay.contains(time.toInt()))
DoubleValue(timeOfDay.contains(time.toInt()))
}
map["game_time"] = { _ -> DoubleValue(world.gameTime.toDouble()) }
map["time_of_day"] = put@{
val time = world.dayTime % 24000
return@put DoubleValue(time.toDouble())
}
map["server"] = { _ -> server()?.asMoLangValue() ?: DoubleValue.ZERO }
map["is_raining_at"] = put@{ params ->
functions["game_time"] = { DoubleValue(world.gameTime.toDouble()) }
functions["time_of_day"] = { DoubleValue((world.dayTime % 24000).toDouble()) }
functions["server"] = { server()?.asMoLangValue() ?: DoubleValue.ZERO }
functions["is_raining_at"] = { params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
return@put DoubleValue(world.isRainingAt(BlockPos(x, y, z)))
DoubleValue(world.isRainingAt(BlockPos(x, y, z)))
}
map["is_snowing_at"] = put@{ params ->
functions["is_snowing_at"] = { params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
val blockPos = BlockPos(x, y, z)
when {
!world.isRaining -> return@put DoubleValue.ZERO
!world.canSeeSky(blockPos) -> return@put DoubleValue.ZERO
!world.isRaining -> {
DoubleValue.ZERO
}
!world.canSeeSky(blockPos) -> {
DoubleValue.ZERO
}
(world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, blockPos).y > blockPos.y) -> {
return@put DoubleValue.ZERO
DoubleValue.ZERO
}
else -> {
val biome = world.getBiome(blockPos).value() as Biome
return@put DoubleValue(biome.getPrecipitationAt(blockPos) == Biome.Precipitation.SNOW)
DoubleValue(biome.getPrecipitationAt(blockPos) == Biome.Precipitation.SNOW)
}
}
}
map["is_chunk_loaded_at"] = put@{ params ->
functions["is_chunk_loaded_at"] = { params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
return@put DoubleValue(world.isLoaded(BlockPos(x, y, z)))
DoubleValue(world.isLoaded(BlockPos(x, y, z)))
}
map["is_thundering"] = { _ -> DoubleValue(world.isThundering) }
map["is_raining"] = { _ -> DoubleValue(world.isRaining) }
map["set_block"] = put@{ params ->
functions["is_thundering"] = { DoubleValue(world.isThundering) }
functions["is_raining"] = { DoubleValue(world.isRaining) }
functions["set_block"] = setBlock@{ params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
val block = world.blockRegistry.get(params.getString(3).asIdentifierDefaultingNamespace())
?: run {
Cobblemon.LOGGER.error("Unknown block: ${params.getString(3)}")
return@put DoubleValue.ZERO
}
val block =
world.blockRegistry.get(params.getString(3).asIdentifierDefaultingNamespace())
?: run {
Cobblemon.LOGGER.error("Unknown block: ${params.getString(3)}")
return@setBlock DoubleValue.ZERO
}
world.setBlock(BlockPos(x, y, z), block.defaultBlockState(), Block.UPDATE_ALL)
}
map["is_air"] = put@{ params ->
functions["is_air"] = { params ->
val x = params.getDouble(0).toInt()
val y = params.getDouble(1).toInt()
val z = params.getDouble(2).toInt()
val blockState = world.getBlockState(BlockPos(x, y, z))
return@put DoubleValue(blockState.isAir)
DoubleValue(blockState.isAir)
}
map["get_block"] = put@{ params ->
functions["get_block"] = { params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
val block = world.getBlockState(BlockPos(x, y, z)).block
return@put world.blockRegistry.wrapAsHolder(block).asMoLangValue(Registries.BLOCK)
world.blockRegistry.wrapAsHolder(block).asMoLangValue(Registries.BLOCK)
}
map["spawn_explosion"] = { params ->
functions["spawn_explosion"] = { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
@@ -136,32 +139,33 @@ object WorldMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Level>>() {
z,
range,
ExplosionInteraction.valueOf(
params.getStringOrNull(4)?.uppercase() ?: ExplosionInteraction.TNT.name
)
params.getStringOrNull(4)?.uppercase() ?: ExplosionInteraction.TNT.name,
),
)
}
map["spawn_lightning"] = put@{ params ->
functions["spawn_lightning"] = { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val lightning = LightningBolt(EntityType.LIGHTNING_BOLT, world)
lightning.setPos(x, y, z)
world.addFreshEntity(lightning)
return@put DoubleValue.ONE
DoubleValue.ONE
}
// q.entity.world.spawn_bedrock_particles(effect, x, y, z, [player]) - sends to everyone nearby or just to the player if they're set.
map["spawn_bedrock_particles"] = { params ->
functions["spawn_bedrock_particles"] = { params ->
val particle = params.getString(0).asResource()
val x = params.getDouble(1)
val y = params.getDouble(2)
val z = params.getDouble(3)
val player = params.getOrNull<MoValue>(4)?.let {
when (it) {
is StringValue -> world.getPlayerByUUID(UUID.fromString(it.value))
is ObjectValue<*> -> it.obj
else -> null
}
} as? ServerPlayer
val player =
params.getOrNull<MoValue>(4)?.let {
when (it) {
is StringValue -> world.getPlayerByUUID(UUID.fromString(it.value))
is ObjectValue<*> -> it.obj
else -> null
}
} as? ServerPlayer
val pos = Vec3(x, y, z)
val packet = SpawnSnowstormParticlePacket(particle, pos)
@@ -171,7 +175,7 @@ object WorldMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Level>>() {
packet.sendToPlayersAround(x, y, z, 64.0, world.dimension())
}
}
map["spawn_pokemon"] = put@{ params ->
functions["spawn_pokemon"] = spawnPokemon@{ params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
@@ -179,49 +183,50 @@ object WorldMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Level>>() {
val pos = BlockPos(x, y, z)
if (!Level.isInSpawnableBounds(pos)) {
return@put DoubleValue.ZERO
}
if (!Level.isInSpawnableBounds(pos)) return@spawnPokemon DoubleValue.ZERO
val pokemon = props.createEntity(world)
pokemon.moveTo(pos, pokemon.yRot, pokemon.xRot)
if (world.addFreshEntity(pokemon)) {
return@put pokemon.struct
} else {
return@put DoubleValue.ZERO
}
if (world.addFreshEntity(pokemon)) return@spawnPokemon pokemon.struct
DoubleValue.ZERO
}
map["spawn_npc"] = put@{ params ->
functions["spawn_npc"] = spawnNpc@{ params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val npcClass = params.getStringOrNull(3)
?.let { NPCClasses.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
val npcClass =
params
.getStringOrNull(3)
?.let { NPCClasses.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
val level = params.getInt(4)
if (npcClass == null) return@put DoubleValue.ZERO
if (npcClass == null) return@spawnNpc DoubleValue.ZERO
val npc = NPCEntity(world)
npc.moveTo(x, y, z, npc.yRot, npc.xRot)
npc.npc = npcClass
npc.initialize(level)
if (world.addFreshEntity(npc)) {
return@put npc.asMoLangValue()
}
return@put DoubleValue.ZERO
if (world.addFreshEntity(npc)) return@spawnNpc npc.asMoLangValue()
DoubleValue.ZERO
}
map["play_sound_on_server"] = { params ->
functions["play_sound_on_server"] = { params ->
val sound = params.getString(0).asResource()
val soundSource = params.getString(1).uppercase()
val x = params.getDouble(2)
val y = params.getDouble(3)
val z = params.getDouble(4)
val player = params.getOrNull<MoValue>(5)?.let {
when (it) {
is StringValue -> world.getPlayerByUUID(UUID.fromString(it.value))
is ObjectValue<*> -> it.obj
else -> null
}
} as? ServerPlayer
val player =
params.getOrNull<MoValue>(5)?.let {
when (it) {
is StringValue -> world.getPlayerByUUID(UUID.fromString(it.value))
is ObjectValue<*> -> it.obj
else -> null
}
} as? ServerPlayer
val volume = params.getDoubleOrNull(6)?.toFloat() ?: 1.0f
val pitch = params.getDoubleOrNull(7)?.toFloat() ?: 1.0f
@@ -233,29 +238,30 @@ object WorldMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Level>>() {
packet.sendToPlayersAround(x, y, z, 16.0, world.dimension())
}
}
map["get_entities_around"] = put@{ params ->
functions["get_entities_around"] = { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val range = params.getDouble(3) * 2
val entities = world.getEntities(null, AABB.ofSize(Vec3(x, y, z), range, range, range))
return@put entities
entities
.filterIsInstance<LivingEntity>()
.map { it.asMostSpecificMoLangValue() }
.asArrayValue()
}
map["is_healer_in_use"] = put@{ params ->
functions["is_healer_in_use"] = isHealerInUse@{ params ->
val pos = params.get<ArrayStruct>(0).asBlockPos()
val healer = world.getBlockEntity(pos, CobblemonBlockEntities.HEALING_MACHINE).orElse(null)
?: return@put DoubleValue.ONE
return@put DoubleValue(healer.isInUse)
val healer =
world.getBlockEntity(pos, CobblemonBlockEntities.HEALING_MACHINE).orElse(null)
?: return@isHealerInUse DoubleValue.ONE
return@isHealerInUse DoubleValue(healer.isInUse)
}
map["spawn_loot_table_items"] = put@{ params ->
functions["spawn_loot_table_items"] = spawnLootTableItems@{ params ->
val serverLevel = world as? ServerLevel
if (serverLevel == null) {
Cobblemon.LOGGER.warn("spawn_loot_table_items: world is not a ServerLevel")
return@put DoubleValue.ZERO
return@spawnLootTableItems DoubleValue.ZERO
}
val lootTableId = params.getString(0).asIdentifierDefaultingNamespace()
val x = params.getDouble(1)
@@ -265,9 +271,11 @@ object WorldMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Level>>() {
val lootTableKey = ResourceKey.create(Registries.LOOT_TABLE, lootTableId)
val lootTable = serverLevel.server.reloadableRegistries().getLootTable(lootTableKey)
val lootParams = LootParams.Builder(serverLevel)
.withParameter(LootContextParams.ORIGIN, Vec3(x, y, z))
.create(LootContextParamSets.COMMAND)
val lootParams =
LootParams
.Builder(serverLevel)
.withParameter(LootContextParams.ORIGIN, Vec3(x, y, z))
.create(LootContextParamSets.COMMAND)
val items = lootTable.getRandomItems(lootParams)
val spawnedItems = ArrayStruct(hashMapOf())
@@ -285,9 +293,10 @@ object WorldMoLangFunctions : AbstractMoLangFunctionHolder<Holder<Level>>() {
}
}
}
return@put spawnedItems
spawnedItems
}
return map
return functions
}
}
@@ -8,8 +8,10 @@
package com.cobblemon.mod.common.api.orientation
import com.cobblemon.mod.common.util.math.QuatSpring
import com.cobblemon.mod.common.util.math.geometry.toDegrees
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.math.simpleSpringDamperQuat
import net.minecraft.client.Minecraft
import net.minecraft.util.Mth
import net.minecraft.world.entity.LivingEntity
@@ -26,6 +28,8 @@ open class OrientationController(val entity: LivingEntity) {
val FORWARDS: Vector3f = Vector3f(0.0f, 0.0f, -1.0f)
val UP: Vector3f = Vector3f(0.0f, 1.0f, 0.0f)
val LEFT: Vector3f = Vector3f(-1.0f, 0.0f, 0.0f)
private const val TICK_DT = 1f / 20f // Minecraft server tick rate
private const val RENDER_SMOOTH_HALFLIFE = 0.1f // seconds to close ~half the gap
}
open var active: Boolean = false
@@ -33,6 +37,8 @@ open class OrientationController(val entity: LivingEntity) {
var orientation: Matrix3f? = null
private set
private val renderSpring = QuatSpring()
private var renderOrientationO: Matrix3f? = null
private var renderOrientation: Matrix3f? = null
@@ -54,6 +60,8 @@ open class OrientationController(val entity: LivingEntity) {
orientation = null
renderOrientationO = null
renderOrientation = null
renderSpring.quat.identity()
renderSpring.angVel.set(0f, 0f, 0f)
active = false
}
@@ -77,18 +85,26 @@ open class OrientationController(val entity: LivingEntity) {
return oldQuat
}
/** Every tick move the current render orientation towards the last known true orientation **/
fun tick() {
if (!active || orientation == null) return
renderOrientationO = renderOrientation
val current = orientation
val renderMatrix = this.renderOrientation ?: current
val renderQuat = Quaternionf().setFromUnnormalized(renderMatrix)
val targetQuat = Quaternionf().setFromUnnormalized(current)
val dampingFactor = 0.66f // Smooth interpolation to reduce jitter of orientations recieved from the server.
renderQuat.slerp(targetQuat, dampingFactor)
val current = orientation!!
val targetQuat = Quaternionf().setFromUnnormalized(current)
if (renderOrientation == null) {
// First tick: snap the spring to the target instead of easing in from identity
renderSpring.quat.set(targetQuat)
renderSpring.angVel.set(0f, 0f, 0f)
}
// Use a "quat spring" to smoothly move the render orientation towards the goal orientation.
// It takes into account the orientations angular velocities and should be much more resilient
// to sharp changes in goal orientation or late/missed packets.
simpleSpringDamperQuat(renderSpring, targetQuat, 0.15f, TICK_DT)
val newRenderOrientation = Matrix3f()
renderQuat.get(newRenderOrientation)
renderSpring.quat.get(newRenderOrientation)
renderOrientation = newRenderOrientation
}
@@ -269,7 +269,7 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
createDefault("dive_ball", CatchRateModifiers.SUBMERGED_IN_WATER, waterDragValue = 0.99F)
createDefault("nest_ball", CatchRateModifiers.NEST)
createDefault("repeat_ball", CatchRateModifiers.REPEAT)
createDefault("timer_ball", CatchRateModifiers.turnBased { turn -> (1F * turn * (1229F / 4096F)).coerceAtMost(4F) })
createDefault("timer_ball", CatchRateModifiers.turnBased { turn -> (1F + turn * (1229F / 4096F)).coerceAtMost(4F) })
createDefault("luxury_ball", effects = listOf(FriendshipEarningBoostEffect(2F)))
createDefault("premier_ball")
createDefault("dusk_ball", CatchRateModifiers.LIGHT_LEVEL)
@@ -278,7 +278,7 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
createDefault("cherish_ball")
createDefault("park_ball", CatchRateModifiers.PARK)
createDefault("dream_ball", CatchRateModifiers.statusBoosting(4F, Statuses.SLEEP))
createDefault("beast_ball", LabelModifier(5F, true, CobblemonPokemonLabels.ULTRA_BEAST)/*, LabelModifier(0.1F, false, CobblemonPokemonLabels.ULTRA_BEAST))*/)
createDefault("beast_ball", DynamicMultiplierModifier({ _, pokemon -> if (pokemon.hasLabels(CobblemonPokemonLabels.ULTRA_BEAST)) 5F else 0.1F }) { _, _ -> true })
createDefault("ancient_poke_ball", ancient = true)
createDefault("ancient_citrine_ball", ancient = true)
createDefault("ancient_verdant_ball", ancient = true)
@@ -297,13 +297,15 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
createDefault("ancient_origin_ball", GuaranteedModifier(), ancient = true)
// Luxury ball effect, low priority as it must be triggered before soothe bell as of gen 4
CobblemonEvents.FRIENDSHIP_UPDATED.subscribe(priority = Priority.LOW) { event ->
var increment = (event.newFriendship - event.pokemon.friendship).toFloat()
if (increment <= 0) //these affects are only meant to affect positive gains
if (!event.earned) {
return@subscribe
if (increment <= 1F) {
event.pokemon.caughtBall.effects.filterIsInstance<FriendshipEarningBoostEffect>()
.forEach { increment *= it.multiplier }
}
var increment = (event.newFriendship - event.pokemon.friendship).toFloat()
if (increment <= 0) {
return@subscribe
}
event.pokemon.caughtBall.effects.filterIsInstance<FriendshipEarningBoostEffect>()
.forEach { increment *= it.multiplier }
event.newFriendship = event.pokemon.friendship + increment.roundToInt()
}
}
@@ -338,7 +340,6 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
ancient: Boolean = false
): PokeBall {
val identifier = cobblemonResource(name)
//val finalModifiers = if (appendUltraBeastPenalty) modifiers + listOf(LabelModifier(0.1F, true, CobblemonPokemonLabels.ULTRA_BEAST)) else modifiers
val pokeball = PokeBall(identifier, modifier, effects, waterDragValue, model2d, model3d, throwPower, ancient)
this.defaults[identifier] = pokeball
return pokeball
@@ -54,22 +54,20 @@ object CatchRateModifiers {
/**
* Used by [PokeBalls.LOVE_BALL].
* Boosts the catch rate if the Pokémon is of the same species and opposite gender
* Boosts the catch rate 2.5× if any of the player's active Pokémon is of the opposite gender,
* or 8× if that Pokémon is also of the same species.
*/
val LOVE = BattleModifier { _, playerPokemon, pokemon ->
if (
pokemon.gender != Gender.GENDERLESS
&& playerPokemon.mapNotNull { it.battlePokemon?.originalPokemon }
.any {
it.gender != Gender.GENDERLESS &&
it.species.name.equals(pokemon.species.name, true) &&
it.gender != pokemon.gender
}
) {
if (playerPokemon.any { it.battlePokemon?.originalPokemon?.species?.resourceIdentifier == pokemon.species.resourceIdentifier }) 8F else 2.5F
if (pokemon.gender == Gender.GENDERLESS) {
return@BattleModifier 1F
}
else {
1F
val oppositeGender = playerPokemon
.mapNotNull { it.battlePokemon?.originalPokemon }
.filter { it.gender != Gender.GENDERLESS && it.gender != pokemon.gender }
when {
oppositeGender.any { it.species.resourceIdentifier == pokemon.species.resourceIdentifier } -> 8F
oppositeGender.isNotEmpty() -> 2.5F
else -> 1F
}
}
@@ -81,7 +79,7 @@ object CatchRateModifiers {
* 1× otherwise.
*/
val MOON_PHASES: CatchRateModifier = WorldStateModifier { _, entity ->
if (entity.level().gameTime in 12000..24000)
if (entity.level().dayTime % 24000 < 12000)
return@WorldStateModifier 1F
when (entity.level().moonPhase) {
2, 6 -> 1.5F
@@ -110,7 +108,7 @@ object CatchRateModifiers {
* Used by [PokeBalls.SAFARI_BALL].
* Checks if the target is not battling, if true boost by *1.5
*/
val SAFARI = WorldStateModifier { _, entity -> if (!entity.isBattling) 1.5F else 1F }
val SAFARI = MultiplierModifier(1.5f) { _, poke -> !poke.entity?.isBattling!! }
/**
* Used by [PokeBalls.PARK_BALL].
@@ -13,6 +13,10 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
import com.cobblemon.mod.common.api.pokedex.entry.PokedexForm
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.feature.ChoiceSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.WeightedChoiceSpeciesFeatureProvider
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData
import com.cobblemon.mod.common.pokemon.Gender
import net.minecraft.resources.ResourceLocation
@@ -72,6 +76,57 @@ abstract class AbstractPokedexManager {
} ?: PokedexEntryProgress.UNREGISTERED
}
fun getHighestKnowledgeFor(entry: PokedexEntry, form: PokedexForm?): PokedexEntryProgress {
if (form == null) return getHighestKnowledgeFor(entry)
val speciesRecord = getSpeciesRecord(entry.speciesId) ?: return PokedexEntryProgress.UNREGISTERED
val hasAllAspects = entry.conditionAspects.all(speciesRecord::hasAspect)
if (!hasAllAspects) {
return PokedexEntryProgress.UNREGISTERED
}
return speciesRecord.getFormRecord(form.displayForm)?.knowledge ?: PokedexEntryProgress.UNREGISTERED
}
fun getHighestKnowledgeFor(entry: PokedexEntry, form: PokedexForm?, aspects: Iterable<String>): PokedexEntryProgress {
val formKnowledge = getHighestKnowledgeFor(entry, form)
if (formKnowledge == PokedexEntryProgress.UNREGISTERED) return PokedexEntryProgress.UNREGISTERED
val seenAspects = getSeenAspects(entry)
// Aspects that select one of the species' own forms are already covered by the form knowledge check above,
// so exempt them (and every other value of the same feature, since defaults like "phony" never appear on a form directly).
val species = PokemonSpecies.getByIdentifier(entry.speciesId)
val formAspects = species?.forms?.flatMapTo(hashSetOf()) { it.aspects } ?: emptySet()
val formSelectingFeatureAspects = species?.let(SpeciesFeatures::getFeaturesFor).orEmpty()
.mapNotNull { provider ->
val allAspects: Collection<String> = when (provider) {
is ChoiceSpeciesFeatureProvider -> provider.getAllAspects()
is WeightedChoiceSpeciesFeatureProvider -> provider.getAllAspects().keys
else -> return@mapNotNull null
}
allAspects.takeIf { it.any { aspect -> aspect in formAspects } }
}
.flatten()
.toHashSet()
val allSeenAspects = aspects
.filter { aspect ->
if (Dexes.equivalentFormsLookup[aspect] != null) return@filter false
if (aspect in formAspects || aspect in formSelectingFeatureAspects) return@filter false
if (aspect.uppercase() in setOf(Gender.MALE.name, Gender.FEMALE.name, Gender.GENDERLESS.name)) return@filter false
if ("shiny".equals(aspect, ignoreCase = true)) return@filter false
return@filter true
}
.all { aspect -> seenAspects.contains(aspect) }
if (!allSeenAspects) return PokedexEntryProgress.UNREGISTERED
return formKnowledge
}
fun getEncounteredForms(entry: PokedexEntry) : List<PokedexForm> {
return getFormsWithKnowledge(entry, PokedexEntryProgress.SEEN)
}
@@ -40,6 +40,17 @@ object Dexes : JsonDataRegistry<PokedexDef> {
//Maps a dex id to its PokedexDef
val dexEntryMap = linkedMapOf<ResourceLocation, PokedexDef>()
val equivalentForms = mapOf(
"galar" to listOf("region-bias-galar", "bias-galar", "galar-bias", "galarian", "galar"),
"alola" to listOf("region-bias-alola", "bias-alola", "alola-bias", "alolan", "alola"),
"hisui" to listOf("region-bias-hisui", "bias-hisui", "hisui-bias", "hisuian", "hisui"),
"paldea" to listOf("region-bias-paldea", "bias-paldea", "paldea-bias", "paldean", "paldea"),
)
val equivalentFormsLookup = equivalentForms.flatMap { (key, values) ->
values.map { it to key }
}.toMap()
override fun reload(data: Map<ResourceLocation, PokedexDef>) {
dexEntryMap.clear()
data.entries.sortedBy { it.value.sortOrder }.forEach { (id, def) -> dexEntryMap[id] = def }
@@ -49,4 +60,9 @@ object Dexes : JsonDataRegistry<PokedexDef> {
override fun sync(player: ServerPlayer) {
PokedexDexSyncPacket(dexEntryMap.values).sendToPlayer(player)
}
fun getFormEquivalents(formName: String): List<String> {
val key = equivalentFormsLookup[formName.lowercase()]
return equivalentForms[key] ?: listOf(formName.lowercase())
}
}
@@ -42,7 +42,7 @@ class PokedexManager(
}
// Java interop isn't great lol
@Deprecated("Use encounter(PokedexEntityData) instead")
@Deprecated("Use obtain(Pokemon) instead")
fun catch(pokemon: Pokemon) {
obtain(pokemon)
}
@@ -121,6 +121,7 @@ class SpeciesDexRecord {
fun addAspects(addedAspects: Set<String>) {
aspects.addAll(addedAspects)
pokedexManager.onSpeciesRecordUpdated(this)
}
/** Returns true if the given Pokémon contains new information. Internal because it's only to be called from [FormDexRecord.wouldBeDifferent]. */
@@ -8,13 +8,20 @@
package com.cobblemon.mod.common.api.pokedex.entry
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.DexEntrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.DexEvolutionDisplaySyncPacket
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
@@ -38,6 +45,8 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
val entries = mutableMapOf<ResourceLocation, PokedexEntry>()
val evolutionDisplays = mutableListOf<DexEvolutionDisplay>()
override fun reload(data: Map<ResourceLocation, PokedexEntry>) {
data.forEach { _, entry ->
entries[entry.id] = entry
@@ -50,6 +59,7 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
}
}
}
computeEvolutionDisplays()
observable.emit(this)
}
@@ -57,5 +67,58 @@ object DexEntries : JsonDataRegistry<PokedexEntry> {
override fun sync(player: ServerPlayer) {
DexEntrySyncPacket(entries.values).sendToPlayer(player)
DexEvolutionDisplaySyncPacket(evolutionDisplays).sendToPlayer(player)
}
}
fun computeEvolutionDisplays() {
evolutionDisplays.clear()
PokemonSpecies.species.forEach {
try {
evolutionDisplays.addAll(buildDisplays(it.standardForm) + it.forms.flatMap { form -> buildDisplays(form) })
} catch (e: Exception) {
Cobblemon.LOGGER.error("Failed to build dex evolution displays for ${it.resourceIdentifier}", e)
}
}
}
fun buildDisplays(form: FormData): List<DexEvolutionDisplay> {
val result = mutableListOf<DexEvolutionDisplay>()
(form.ownEvolutions ?: form.evolutions).forEach { evolution ->
val requirements = mutableListOf<DexEvolutionRequirementDisplay>()
getToolTipHeaderIcon(evolution)?.let { header ->
requirements.add(header)
}
evolution.requirements.mapNotNull { resolveRequirementData(it) }.forEach { requirement ->
requirements.add(requirement)
}
listOfNotNull(evolution.result, evolution.shedder).forEach resultLoop@{ resultProperties ->
val resultSpecies = resultProperties.species
?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
?: return@resultLoop
// The result's own aspects win, otherwise carry the source form's aspects over
// like a real evolution does (Alola-Bias Pichu -> Alola-Bias Pikachu)
val resultForm = resultSpecies.getForm(resultProperties.aspects).takeIf { it != resultSpecies.standardForm }
?: resultSpecies.getForm(form.aspects.toSet() + resultProperties.aspects)
val toAspects = resultProperties.aspects + resultForm.aspects
val resultRequirements = if (resultProperties === evolution.shedder) {
requirements + CobblemonDexEvolutionRequirementDisplay(CobblemonItems.POKE_BALL, lang("ui.pokedex.evolution.shedder.tooltip"))
} else {
requirements
}
result.add(DexEvolutionDisplay(
fromSpecies = form.species.resourceIdentifier,
fromForm = form.name,
fromAspects = form.aspects.toSet(),
toSpecies = resultSpecies.resourceIdentifier,
toForm = resultForm.name,
toAspects = toAspects,
requirements = resultRequirements,
))
}
}
return result
}
}
@@ -0,0 +1,466 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokedex.entry
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeIdentifierCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeTagCondition
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.requirement.Requirement
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.text.add
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
import com.cobblemon.mod.common.pokemon.requirements.*
import com.cobblemon.mod.common.util.adapters.RegistryAccessResolver
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.lang
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.ExtraCodecs
import net.minecraft.world.item.Item
import net.minecraft.world.item.Items
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.levelgen.structure.Structure
import kotlin.jvm.optionals.getOrNull
data class DexEvolutionDisplay(
val fromSpecies: ResourceLocation,
val fromForm: String,
val fromAspects: Set<String>,
val toSpecies: ResourceLocation,
val toForm: String,
val toAspects: Set<String>,
val requirements: List<DexEvolutionRequirementDisplay>,
)
interface DexEvolutionRequirementDisplay {
val icon: Item
val tooltip: MutableComponent
}
data class CobblemonDexEvolutionRequirementDisplay(
override val icon: Item,
override val tooltip: MutableComponent,
) : DexEvolutionRequirementDisplay
data class CobblemonBiomeDexEvolutionRequirementDisplay(
override val icon: Item,
override val tooltip: MutableComponent,
val condition: ExtraCodecs.TagOrElementLocation? = null,
val antiCondition: ExtraCodecs.TagOrElementLocation? = null,
) : DexEvolutionRequirementDisplay
fun getToolTipHeaderIcon(evolution: Evolution): DexEvolutionRequirementDisplay? {
when (evolution) {
is ItemInteractionEvolution -> return getToolTipHeaderIcon(evolution)
is TradeEvolution -> return getToolTipHeaderIcon(evolution)
}
return null
}
fun getToolTipHeaderIcon(itemInteractionEvolution: ItemInteractionEvolution): CobblemonDexEvolutionRequirementDisplay? {
val item = itemInteractionEvolution.requiredContext.items.orElse(null)?.firstOrNull()?.value() ?: return null
return CobblemonDexEvolutionRequirementDisplay(item, lang("ui.pokedex.evolution.item_interact.tooltip", item.description.plainCopy().green()))
}
fun getToolTipHeaderIcon(tradeEvolution: TradeEvolution): CobblemonDexEvolutionRequirementDisplay {
val species = tradeEvolution.requiredContext.species
val tooltip = if (species == null) {
lang("ui.pokedex.evolution.trade.tooltip.with_any_species")
} else {
PokemonSpecies.getByIdentifier(species.asIdentifierDefaultingNamespace())
?.let { lang("ui.pokedex.evolution.trade.tooltip.with_species", it.translatedName.green()) }
?: lang("ui.pokedex.evolution.trade.tooltip.with_any_species")
}
return CobblemonDexEvolutionRequirementDisplay(CobblemonItems.LINK_CABLE, tooltip)
}
fun resolveRequirementData(requirement: Requirement): DexEvolutionRequirementDisplay? {
val icon = requirement.pokedexIcon?.let {
RegistryAccessResolver.getAccess().registryOrThrow(Registries.ITEM).get(it)
}
val tooltip = requirement.pokedexTooltip?.let {
Component.translatable(it)
}
if (icon != null && tooltip != null) {
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
return buildRequirementData(requirement)
}
fun buildRequirementData(requirement: Requirement): DexEvolutionRequirementDisplay? {
when (requirement) {
is AdvancementRequirement -> return buildAdvancementRequirement(requirement)
is BattleCriticalHitsRequirement -> return buildBattleCriticalHitsRequirement(requirement)
is BiomeRequirement -> return buildBiomeRequirement(requirement)
is BlocksTraveledRequirement -> return buildBlocksTraveledRequirement(requirement)
is DamageTakenRequirement -> return buildDamageTakenRequirement(requirement)
is DefeatRequirement -> return buildDefeatRequirement(requirement)
is FriendshipRequirement -> return buildFriendshipRequirement(requirement)
is HeldItemRequirement -> return buildHeldItemRequirement(requirement)
is LevelRequirement -> return buildLevelRequirement(requirement)
is MoonPhaseRequirement -> return buildMoonPhaseRequirement(requirement)
is MoveSetRequirement -> return buildMoveSetRequirement(requirement)
is MoveTypeRequirement -> return buildMoveTypeRequirement(requirement)
is NegateRequirement -> return buildNegateRequirement(requirement)
is OwnerHoldsItemRequirement -> return buildOwnerHoldsItemRequirement(requirement)
is PartyMemberRequirement -> return buildPartyMemberRequirement(requirement)
is PokemonPropertiesRequirement -> return buildPokemonPropertiesRequirement(requirement)
is PropertyRangeRequirement -> return buildPropertyRangeRequirement(requirement)
is RecoilRequirement -> return buildRecoilRequirement(requirement)
is StatCompareRequirement -> return buildStatCompareRequirement(requirement)
is StatEqualRequirement -> return buildStatEqualRequirement(requirement)
is StructureRequirement -> return buildStructureRequirement(requirement)
is TimeRangeRequirement -> return buildTimeRangeRequirement(requirement)
is UseMoveRequirement -> return buildUseMoveRequirement(requirement)
is WeatherRequirement -> return buildWeatherRequirement(requirement)
}
return null
}
fun buildAdvancementRequirement(advancementRequirement: AdvancementRequirement): DexEvolutionRequirementDisplay {
val icon = Items.BOOK
val identifier = advancementRequirement.requiredAdvancement
val advancementLanguageKey = "${identifier.namespace}.${identifier.path.substringAfterLast('/')}"
val advancementComponent = Component.translatable("advancements.$advancementLanguageKey").green()
val tooltip = lang("ui.pokedex.requirement.advancement.tooltip", advancementComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildBattleCriticalHitsRequirement(battleCriticalHitsRequirement: BattleCriticalHitsRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.MEDICINAL_LEEK
val criticalsComponent = Component.literal(battleCriticalHitsRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.battle_critical_hits.tooltip", criticalsComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
private fun tagDisplayName(registry: String, id: ResourceLocation): MutableComponent {
return Component.translatableWithFallback("tag.$registry.${id.namespace}.${id.path.replace('/', '.')}", prettifyPath(id.path))
}
private fun prettifyPath(path: String): String {
return path.substringAfterLast('/').removePrefix("is_").split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) }
}
fun buildNegateRequirement(negateRequirement: NegateRequirement): DexEvolutionRequirementDisplay? {
val inner = negateRequirement.inner
when (inner) {
is BiomeRequirement -> buildBiomeRequirement(inner, negated = true)?.let { return it }
is StructureRequirement -> buildStructureRequirement(inner, negated = true)?.let { return it }
}
val innerDisplay = resolveRequirementData(inner) ?: return null
return CobblemonDexEvolutionRequirementDisplay(innerDisplay.icon, lang("ui.pokedex.requirement.negate.tooltip", innerDisplay.tooltip))
}
fun buildBiomeRequirement(biomeRequirement: BiomeRequirement, negated: Boolean = false): DexEvolutionRequirementDisplay? {
// Negating "in X" is "not in X"; negating both a condition and an anticondition isn't expressible as one line
if (negated && biomeRequirement.biomeCondition != null && biomeRequirement.biomeAnticondition != null) return null
val condition = if (negated) biomeRequirement.biomeAnticondition else biomeRequirement.biomeCondition
val antiCondition = if (negated) biomeRequirement.biomeCondition else biomeRequirement.biomeAnticondition
if (condition == null && antiCondition == null) {
return null
}
val icon = Items.COMPASS
val displayName: (RegistryLikeCondition<Biome>) -> MutableComponent = {
when (it) {
is RegistryLikeTagCondition -> tagDisplayName("worldgen.biome", it.tag.location())
is RegistryLikeIdentifierCondition -> Component.translatable("biome.${it.identifier.namespace}.${it.identifier.path}")
else -> Component.literal(it.toString())
}
}
val tooltip = if (condition != null && antiCondition != null) {
lang("ui.pokedex.requirement.biome.both.tooltip", displayName(condition).green(), displayName(antiCondition).green())
} else if (condition != null) {
lang("ui.pokedex.requirement.biome.tooltip", displayName(condition).green())
} else {
lang("ui.pokedex.requirement.biome.anticondition.tooltip", displayName(antiCondition!!).green())
}
var conditionConverted: ExtraCodecs.TagOrElementLocation? = null
if (condition != null) {
val conditionIdentifier = if (condition is RegistryLikeIdentifierCondition) condition.identifier else (condition as RegistryLikeTagCondition).tag.location
conditionConverted = ExtraCodecs.TagOrElementLocation(conditionIdentifier, condition is RegistryLikeTagCondition)
}
var antiConditionConverted: ExtraCodecs.TagOrElementLocation? = null
if (antiCondition != null) {
val conditionIdentifier = if (antiCondition is RegistryLikeIdentifierCondition) antiCondition.identifier else (antiCondition as RegistryLikeTagCondition).tag.location
antiConditionConverted = ExtraCodecs.TagOrElementLocation(conditionIdentifier, antiCondition is RegistryLikeTagCondition)
}
return CobblemonBiomeDexEvolutionRequirementDisplay(icon, tooltip, conditionConverted, antiConditionConverted)
}
fun buildBlocksTraveledRequirement(blocksTraveledRequirement: BlocksTraveledRequirement): DexEvolutionRequirementDisplay {
val icon = Items.LEATHER_BOOTS
val blocksComponent = Component.literal(blocksTraveledRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.blocks_traveled.tooltip", blocksComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildDamageTakenRequirement(damageTakenRequirement: DamageTakenRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.PUNCHING_GLOVE
val blocksComponent = Component.literal(damageTakenRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.damage_taken.tooltip", blocksComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildDefeatRequirement(defeatRequirement: DefeatRequirement): DexEvolutionRequirementDisplay? {
val description = describePokemonProperties(defeatRequirement.target) ?: return null
val icon = CobblemonItems.PUNCHING_GLOVE
val defeatedAmount = defeatRequirement.amount
val descriptionComponent = Component.literal(defeatedAmount.toString()).green().add(" ").add(description)
val tooltip = lang("ui.pokedex.requirement.defeat.tooltip", descriptionComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildFriendshipRequirement(friendshipRequirement: FriendshipRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.SOOTHE_BELL
val friendshipAmountComponent = Component.literal(friendshipRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.friendship.tooltip", friendshipAmountComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildHeldItemRequirement(heldItemRequirement: HeldItemRequirement): DexEvolutionRequirementDisplay? {
val items = heldItemRequirement.itemCondition.items.getOrNull() ?: return null
val item = items.firstOrNull()?.value() ?: return null
val itemComponent = items.unwrapKey().map { tag ->
val id = tag.location()
val fallback = id.path.substringAfterLast('/').split('_').joinToString(" ") { part -> part.replaceFirstChar(Char::titlecase) }
Component.translatableWithFallback("tag.item.${id.namespace}.${id.path.replace('/', '.')}", fallback)
}.orElseGet { item.description.plainCopy() }
val tooltip = lang("ui.pokedex.requirement.held_item.tooltip", itemComponent.green())
return CobblemonDexEvolutionRequirementDisplay(item, tooltip)
}
fun buildLevelRequirement(levelRequirement: LevelRequirement): DexEvolutionRequirementDisplay {
val minLevel = levelRequirement.minLevel
val maxLevel = levelRequirement.maxLevel
val icon = CobblemonItems.RARE_CANDY
val tooltip =
if (minLevel == maxLevel || maxLevel == Int.MAX_VALUE)
lang("ui.pokedex.requirement.level.tooltip.min", Component.literal(minLevel.toString()).green())
else
lang("ui.pokedex.requirement.level.tooltip.between", Component.literal(minLevel.toString()).green(), Component.literal(maxLevel.toString()).green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildMoonPhaseRequirement(moonPhaseRequirement: MoonPhaseRequirement): DexEvolutionRequirementDisplay {
val icon = Items.CLOCK
val moonPhaseComponent = moonPhaseRequirement.moonPhase.displayName().green()
val tooltip = lang("ui.pokedex.requirement.moon_phase.tooltip", moonPhaseComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildMoveSetRequirement(moveSetRequirement: MoveSetRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.TECHNICAL_MACHINE
val tooltip = lang("ui.pokedex.requirement.has_move.tooltip", moveSetRequirement.move.displayName.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildMoveTypeRequirement(moveTypeRequirement: MoveTypeRequirement): DexEvolutionRequirementDisplay {
val type = ElementalTypes.get(moveTypeRequirement.type.name)
val icon = when (type) {
ElementalTypes.NORMAL -> CobblemonItems.NORMAL_GEM
ElementalTypes.FIRE -> CobblemonItems.FIRE_GEM
ElementalTypes.WATER -> CobblemonItems.WATER_GEM
ElementalTypes.GRASS -> CobblemonItems.GRASS_GEM
ElementalTypes.ELECTRIC -> CobblemonItems.ELECTRIC_GEM
ElementalTypes.ICE -> CobblemonItems.ICE_GEM
ElementalTypes.FIGHTING -> CobblemonItems.FIGHTING_GEM
ElementalTypes.POISON -> CobblemonItems.POISON_GEM
ElementalTypes.GROUND -> CobblemonItems.GROUND_GEM
ElementalTypes.FLYING -> CobblemonItems.FLYING_GEM
ElementalTypes.PSYCHIC -> CobblemonItems.PSYCHIC_GEM
ElementalTypes.BUG -> CobblemonItems.BUG_GEM
ElementalTypes.ROCK -> CobblemonItems.ROCK_GEM
ElementalTypes.GHOST -> CobblemonItems.GHOST_GEM
ElementalTypes.DRAGON -> CobblemonItems.DRAGON_GEM
ElementalTypes.DARK -> CobblemonItems.DARK_GEM
ElementalTypes.STEEL -> CobblemonItems.STEEL_GEM
ElementalTypes.FAIRY -> CobblemonItems.FAIRY_GEM
else -> CobblemonItems.NORMAL_GEM
}
val tooltip = lang("ui.pokedex.requirement.has_move_type.tooltip", moveTypeRequirement.type.displayName.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildOwnerHoldsItemRequirement(ownerHoldsItemRequirement: OwnerHoldsItemRequirement): DexEvolutionRequirementDisplay? {
val item = ownerHoldsItemRequirement.itemCondition.items.orElse(null)?.firstOrNull()?.value() ?: return null
val tooltip = lang("ui.pokedex.requirement.owner_held_item.tooltip", item.description.plainCopy().green())
return CobblemonDexEvolutionRequirementDisplay(item, tooltip)
}
fun buildPartyMemberRequirement(partyMemberRequirement: PartyMemberRequirement): DexEvolutionRequirementDisplay? {
val description = describePokemonProperties(partyMemberRequirement.target) ?: return null
val icon = CobblemonItems.POKE_BALL
val tooltip = if (partyMemberRequirement.contains) {
lang("ui.pokedex.requirement.party_member.tooltip", description)
} else {
lang("ui.pokedex.requirement.party_member.anticondition.tooltip", description)
}
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildPokemonPropertiesRequirement(pokemonPropertiesRequirement: PokemonPropertiesRequirement): DexEvolutionRequirementDisplay? {
val description = describePokemonProperties(pokemonPropertiesRequirement.target) ?: return null
val icon = Items.NAME_TAG
val tooltip = lang("ui.pokedex.requirement.pokemon_properties.tooltip", description)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildPropertyRangeRequirement(propertyRangeRequirement: PropertyRangeRequirement): DexEvolutionRequirementDisplay? {
val provider = SpeciesFeatures.getFeature(propertyRangeRequirement.feature) as? IntSpeciesFeatureProvider ?: return null
val displayName = provider.display?.name?.takeIf { it.isNotBlank() }?.asTranslated()?.green() ?: return null
val icon = provider.itemPoints.keys.firstOrNull()?.let { BuiltInRegistries.ITEM.get(it) } ?: Items.NAME_TAG
val range = propertyRangeRequirement.range
val tooltip = lang(
"ui.pokedex.requirement.property_range.tooltip",
displayName,
Component.literal(range.first.toString()).green(),
Component.literal(range.last.toString()).green()
)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildRecoilRequirement(recoilRequirement: RecoilRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.LIFE_ORB
val recoilComponent = Component.literal(recoilRequirement.amount.toString()).green()
val tooltip = lang("ui.pokedex.requirement.recoil.tooltip", recoilComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildStatCompareRequirement(statCompareRequirement: StatCompareRequirement): DexEvolutionRequirementDisplay {
val highStat = Stats.getStat(statCompareRequirement.highStat)
val lowStat = Stats.getStat(statCompareRequirement.lowStat)
val icon = when (highStat) {
Stats.HP, Stats.EVASION, Stats.ACCURACY -> CobblemonItems.HEALTH_MOCHI
Stats.ATTACK -> CobblemonItems.MUSCLE_MOCHI
Stats.DEFENCE -> CobblemonItems.RESIST_MOCHI
Stats.SPECIAL_ATTACK -> CobblemonItems.GENIUS_MOCHI
Stats.SPECIAL_DEFENCE -> CobblemonItems.CLEVER_MOCHI
Stats.SPEED -> CobblemonItems.SWIFT_MOCHI
}
val highStatComponent = highStat.displayName.plainCopy().green()
val lowStatComponent = lowStat.displayName.plainCopy().green()
val tooltip = lang("ui.pokedex.requirement.stat_compare.tooltip", highStatComponent, lowStatComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildStatEqualRequirement(statEqualRequirement: StatEqualRequirement): DexEvolutionRequirementDisplay {
val statOne = Stats.getStat(statEqualRequirement.statOne)
val statTwo = Stats.getStat(statEqualRequirement.statTwo)
val statOneComponent = statOne.displayName.plainCopy().green()
val statTwoComponent = statTwo.displayName.plainCopy().green()
val icon = CobblemonItems.FRESH_START_MOCHI
val tooltip = lang("ui.pokedex.requirement.stat_equal.tooltip", statOneComponent, statTwoComponent)
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildStructureRequirement(structureRequirement: StructureRequirement, negated: Boolean = false): DexEvolutionRequirementDisplay? {
if (negated && structureRequirement.structureCondition != null && structureRequirement.structureAnticondition != null) return null
val condition = if (negated) structureRequirement.structureAnticondition else structureRequirement.structureCondition
val anticondition = if (negated) structureRequirement.structureCondition else structureRequirement.structureAnticondition
if (condition == null && anticondition == null) {
return null
}
val icon = Items.FILLED_MAP
val displayName: (RegistryLikeCondition<Structure>) -> MutableComponent = {
when (it) {
is RegistryLikeTagCondition -> tagDisplayName("worldgen.structure", it.tag.location())
is RegistryLikeIdentifierCondition -> Component.literal(prettifyPath(it.identifier.path))
else -> Component.literal(it.toString())
}
}
val tooltip = if (condition != null && anticondition != null) {
lang("ui.pokedex.requirement.structure.both.tooltip", displayName(condition).green(), displayName(anticondition).green())
} else if (condition != null) {
lang("ui.pokedex.requirement.structure.tooltip", displayName(condition).green())
} else {
lang("ui.pokedex.requirement.structure.anticondition.tooltip", displayName(anticondition!!).green())
}
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildTimeRangeRequirement(timeRangeRequirement: TimeRangeRequirement): DexEvolutionRequirementDisplay? {
val icon = Items.CLOCK
val timeRangeComponent = timeRangeRequirement.range.displayName() ?: return null
val tooltip = lang("ui.pokedex.requirement.time_range.tooltip", timeRangeComponent.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildUseMoveRequirement(useMoveRequirement: UseMoveRequirement): DexEvolutionRequirementDisplay {
val icon = CobblemonItems.CHOICE_BAND
val tooltip = lang("ui.pokedex.requirement.use_move.tooltip", useMoveRequirement.move.displayName.green())
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
fun buildWeatherRequirement(weatherRequirement: WeatherRequirement): DexEvolutionRequirementDisplay {
val icon: Item
val tooltip: MutableComponent
if (weatherRequirement.isThundering == true) {
icon = Items.LIGHTNING_ROD
tooltip = lang("ui.pokedex.requirement.weather.tooltip.thundering")
} else {
icon = Items.WATER_BUCKET
tooltip = lang("ui.pokedex.requirement.weather.tooltip.raining")
}
return CobblemonDexEvolutionRequirementDisplay(icon, tooltip)
}
@@ -0,0 +1,142 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.pokedex.entry
import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.feature.IntSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.StringSpeciesFeature
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.text.add
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.lang
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.locale.Language
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.item.Items
fun describePokemonProperties(properties: PokemonProperties): MutableComponent? {
if (hasUndescribedProperty(properties)) {
return null
}
val descriptors = mutableListOf<MutableComponent>()
properties.species?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }?.let {
descriptors.add(it.translatedName.plainCopy().green())
}
properties.gender?.let {
descriptors.add(lang("gender.${it.name.lowercase()}").green())
}
properties.nickname?.let {
descriptors.add(lang("ui.pokedex.requirement.properties.nickname", it.plainCopy().green()))
}
properties.type?.let {
val typeName = ElementalTypes.get(it)?.displayName?.plainCopy() ?: Component.literal(it)
descriptors.add(lang("ui.pokedex.requirement.properties.type", typeName.green()))
}
properties.nature?.let {
val natureName = Natures.getNature(it.asIdentifierDefaultingNamespace())?.displayName?.asTranslated() ?: Component.literal(it)
descriptors.add(lang("ui.pokedex.requirement.properties.nature", natureName.green()))
}
properties.customProperties.forEach { customProperty ->
describeCustomProperty(customProperty)?.let { descriptors.add(it) }
}
val heldItemDescriptor = properties.heldItem?.let { heldItem ->
val itemId = heldItem.substringBefore('[').trim().asIdentifierDefaultingNamespace()
val item = BuiltInRegistries.ITEM.get(itemId)
if (item == Items.AIR) null else lang("ui.pokedex.requirement.properties.held_item", item.description.plainCopy().green())
}
if (descriptors.isEmpty() && heldItemDescriptor == null) {
return null
}
val mainDescriptor = when {
descriptors.isEmpty() -> null
descriptors.size == 1 -> descriptors[0]
else -> {
val allButLast = descriptors.dropLast(1).reduce { acc, next -> acc.add(", ").add(next) }
lang("ui.pokedex.requirement.properties.and", allButLast, descriptors.last())
}
}
return when {
mainDescriptor != null && heldItemDescriptor != null -> mainDescriptor.add(" ").add(heldItemDescriptor)
mainDescriptor != null -> mainDescriptor
else -> heldItemDescriptor
}
}
private fun hasUndescribedProperty(properties: PokemonProperties): Boolean {
return properties.form != null ||
properties.shiny != null ||
properties.level != null ||
properties.friendship != null ||
properties.fullness != null ||
properties.pokeball != null ||
properties.ability != null ||
properties.status != null ||
properties.teraType != null ||
properties.dmaxLevel != null ||
properties.gmaxFactor != null ||
properties.tradeable != null ||
properties.originalTrainerType != null ||
properties.originalTrainer != null ||
properties.moves != null ||
properties.movesetBuilders != null ||
properties.cosmeticItem != null ||
properties.scaleModifier != null ||
properties.isAlpha != null ||
properties.minPerfectIVs != null ||
properties.customProperties.any { describeCustomProperty(it) == null }
}
private fun describeCustomProperty(property: CustomPokemonProperty): MutableComponent? {
return when (property) {
is IntSpeciesFeature -> describeIntSpeciesFeature(property)
is StringSpeciesFeature -> describeStringSpeciesFeature(property)
else -> null
}
}
private fun describeIntSpeciesFeature(property: IntSpeciesFeature): MutableComponent? {
val value = Component.literal(property.value.toString())
val overrideKey = "ui.pokedex.requirement.properties.custom.${property.name}"
if (Language.getInstance().has("cobblemon.$overrideKey")) {
return lang(overrideKey, value.green())
}
val displayName = (SpeciesFeatures.getFeature(property.name) as? IntSpeciesFeatureProvider)
?.display?.name?.takeIf { it.isNotBlank() }
?: return null
return lang("ui.pokedex.requirement.properties.int_feature", value.green(), displayName.asTranslated().green())
}
private fun describeStringSpeciesFeature(property: StringSpeciesFeature): MutableComponent? {
val key = "ui.pokedex.requirement.properties.custom.${property.name}"
if (!Language.getInstance().has("cobblemon.$key")) {
return null
}
val value = PokemonSpecies.getByIdentifier(property.value.asIdentifierDefaultingNamespace())?.translatedName?.plainCopy()
?: Component.literal(property.value)
return lang(key, value.green())
}
@@ -26,7 +26,6 @@ import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.MovesetBuilder
import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies.getByIdentifier
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffect
import com.cobblemon.mod.common.api.pokemon.effect.adapter.ShoulderEffectAdapter
import com.cobblemon.mod.common.api.pokemon.egg.EggGroup
@@ -128,7 +127,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
.registerTypeAdapter(EggGroup::class.java, EggGroupAdapter)
.registerTypeAdapter(MobEffect::class.java, RegistryElementAdapter<MobEffect>(BuiltInRegistries::MOB_EFFECT))
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter())
.registerTypeAdapter(RidingBehaviourSettings::class.java, RidingBehaviourSettingsAdapter)
.registerTypeAdapter(RideSoundSettingsList::class.java, RideSoundSettingsListAdapter)
.registerTypeAdapter(ObtainableItemCondition::class.java, ObtainableItemConditionAdapter)
@@ -15,6 +15,8 @@ import com.cobblemon.mod.common.block.entity.PokeSnackBlockEntity
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
import net.minecraft.client.ParticleStatus
import net.minecraft.core.particles.BlockParticleOption
import net.minecraft.core.particles.ParticleOptions
import net.minecraft.core.particles.ParticleTypes
@@ -40,13 +42,25 @@ sealed class ParticleData {
val chance: Double,
val amount: Int,
val locatorResolver: (Map<String, MatrixWrapper>) -> List<String>
): ParticleData()
): ParticleData() {
fun amount(): Int = when(Minecraft.getInstance().options.particles().get()) {
ParticleStatus.ALL -> amount
ParticleStatus.DECREASED -> amount / 2
ParticleStatus.MINIMAL -> amount / 4
}
}
data class MinecraftParticle(
val particle: ParticleOptions,
val chance: Double,
val amount: Int
): ParticleData()
): ParticleData() {
fun amount(): Int = when(Minecraft.getInstance().options.particles().get()) {
ParticleStatus.ALL -> amount
ParticleStatus.DECREASED -> amount / 2
ParticleStatus.MINIMAL -> amount / 4
}
}
}
/**
@@ -11,6 +11,8 @@ package com.cobblemon.mod.common.api.pokemon.requirement
import com.cobblemon.mod.common.api.pokemon.evolution.ContextEvolution
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.resources.ResourceLocation
import java.util.WeakHashMap
/**
* Represents a universal requirement suitable for various checks on pokemon..
@@ -32,4 +34,11 @@ fun interface Requirement {
*/
fun check(pokemon: Pokemon): Boolean
val pokedexIcon: ResourceLocation? get() = pokedexIconOverrides[this]
val pokedexTooltip: String? get() = pokedexTooltipOverrides[this]
companion object {
val pokedexIconOverrides = WeakHashMap<Requirement, ResourceLocation>()
val pokedexTooltipOverrides = WeakHashMap<Requirement, String>()
}
}
@@ -54,8 +54,8 @@ enum class Stats(override val identifier: ResourceLocation, override val display
"hp" -> HP
"atk", "attack" -> ATTACK // Hyper Cutter states the full stat name "Attack"
"def", "defense", "defence" -> DEFENCE // Big Pecks states the full stat name "Defense"
"spa" -> SPECIAL_ATTACK
"spd" -> SPECIAL_DEFENCE
"spa", "special_attack" -> SPECIAL_ATTACK
"spd", "special_defence", "special_defense" -> SPECIAL_DEFENCE
"spe", "speed" -> SPEED
"evasion" -> EVASION
else -> ACCURACY
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.util
import net.minecraft.core.BlockPos
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.phys.AABB
import kotlin.math.floor
/**
* Walks every block position inside [searchBox] and turns each solid block into one
* full-cube [FieldPrimitive].
*
* @author Jackson
* @since July 27th, 2026
*/
fun collectPrimitivesFromBlocks(
level: BlockGetter,
searchBox: AABB,
shape: PrimitiveShape,
includeFluids: Boolean = false
): List<FieldPrimitive> {
val primitives = mutableListOf<FieldPrimitive>()
val minX = floor(searchBox.minX).toInt()
val maxX = floor(searchBox.maxX).toInt()
val minY = floor(searchBox.minY).toInt()
val maxY = floor(searchBox.maxY).toInt()
val minZ = floor(searchBox.minZ).toInt()
val maxZ = floor(searchBox.maxZ).toInt()
val pos = BlockPos.MutableBlockPos()
for (x in minX..maxX) {
for (y in minY..maxY) {
for (z in minZ..maxZ) {
pos.set(x, y, z)
val state = level.getBlockState(pos)
val isSolid = !state.getCollisionShape(level, pos).isEmpty
val isFluid = includeFluids && !state.fluidState.isEmpty
if (!isSolid && !isFluid) continue
primitives.add(
FieldPrimitive(
shape = shape,
centerX = x + 0.5f, centerY = y + 0.5f, centerZ = z + 0.5f,
halfWidth = 0.5f, halfHeight = 0.5f, halfDepth = 0.5f
)
)
}
}
}
return primitives
}
@@ -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.api.riding.util
import net.minecraft.world.phys.Vec3
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sqrt
/**
* Helpful Functions when working with signed distance fields/functions.
* https://iquilezles.org/articles/distfunctions/
*
* @author Jackson
* @since July 27th, 2026
*/
/** Which distance formula a primitive uses. */
enum class PrimitiveShape {
BOX,
ROUNDED_BOX,
ELLIPSOID
}
/** Tunable constants for the field. */
data class DistanceFieldTuning(
val smoothingRange: Float = 0.5f,
val roundingRadius: Float = 0.08f
)
/** A single shape in the field - a center position, half-extents, and which formula to use. */
data class FieldPrimitive(
val shape: PrimitiveShape,
val centerX: Float, val centerY: Float, val centerZ: Float,
val halfWidth: Float, val halfHeight: Float, val halfDepth: Float
)
/** Fast approximate 1/sqrt(x) - the Quake III trick, cheaper than a real sqrt + divide. */
fun fastInverseSqrt(value: Float): Float {
var x = value
val halfX = 0.5f * x
var bits = java.lang.Float.floatToIntBits(x)
bits = 0x5f3759df - (bits shr 1)
x = java.lang.Float.intBitsToFloat(bits)
x *= (1.5f - halfX * x * x)
return x
}
/** Exact distance from a point to an axis-aligned box. Inputs are relative to the box's own center. */
fun boxDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float): Float {
val qx = abs(relX) - halfWidth
val qy = abs(relY) - halfHeight
val qz = abs(relZ) - halfDepth
val ox = max(qx, 0f)
val oy = max(qy, 0f)
val oz = max(qz, 0f)
val outside = sqrt(ox * ox + oy * oy + oz * oz)
val inside = min(max(qx, max(qy, qz)), 0f)
return outside + inside
}
/** Same as boxDistance but with rounded corners/edges https://iquilezles.org/articles/distfunctions/ */
fun roundedBoxDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float, roundingRadius: Float): Float =
boxDistance(relX, relY, relZ, halfWidth - roundingRadius, halfHeight - roundingRadius, halfDepth - roundingRadius) - roundingRadius
/** Approximate (lower-bound) distance to an ellipsoid https://iquilezles.org/articles/ellipsoids/. */
fun ellipsoidDistance(relX: Float, relY: Float, relZ: Float, halfWidth: Float, halfHeight: Float, halfDepth: Float): Float {
val px = relX / halfWidth
val py = relY / halfHeight
val pz = relZ / halfDepth
val inverseMagnitude = fastInverseSqrt(px * px + py * py + pz * pz)
val trueDistance = sqrt(relX * relX + relY * relY + relZ * relZ)
return trueDistance * (1.0f - inverseMagnitude)
}
/** Distance from a point to one primitive, dispatched by its shape. */
fun distanceToPrimitive(primitive: FieldPrimitive, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
val relX = sampleX - primitive.centerX
val relY = sampleY - primitive.centerY
val relZ = sampleZ - primitive.centerZ
return when (primitive.shape) {
PrimitiveShape.BOX -> boxDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth)
PrimitiveShape.ROUNDED_BOX -> roundedBoxDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth, tuning.roundingRadius)
PrimitiveShape.ELLIPSOID -> ellipsoidDistance(relX, relY, relZ, primitive.halfWidth, primitive.halfHeight, primitive.halfDepth)
}
}
///** Normalized quadratic smooth-min https://iquilezles.org/articles/smin/ */
//fun smoothMinimum(a: Float, b: Float, smoothingRange: Float): Float {
// if (smoothingRange <= 0.001f) return min(a, b)
// val k = smoothingRange * 4.0f
// val h = (0.5f + 0.5f * (b - a) / k).coerceIn(0f, 1f)
// return b + (a - b) * h - k * h * (1f - h)
//}
//
///** Folds every primitive's distance together into one field value at a point. */
//fun sampleField(primitives: List<FieldPrimitive>, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
// var result = Float.POSITIVE_INFINITY
// for (primitive in primitives) {
// val distance = distanceToPrimitive(primitive, sampleX, sampleY, sampleZ, tuning)
// result = if (result == Float.POSITIVE_INFINITY) distance else smoothMinimum(result, distance, tuning.smoothingRange)
// }
// return result
//}
/** Normalized quadratic smooth-min https://iquilezles.org/articles/smin/ */
fun smoothMinimum(a: Float, b: Float, smoothingRange: Float): Float {
if (smoothingRange <= 0.001f) return min(a, b)
val k = smoothingRange * 4.0f
val h = (0.5f + 0.5f * (b - a) / k).coerceIn(0f, 1f)
return b + (a - b) * h - k * h * (1f - h)
}
/** Folds nearby primitive distances together into one field value at a point. */
fun sampleField(primitives: List<FieldPrimitive>, sampleX: Float, sampleY: Float, sampleZ: Float, tuning: DistanceFieldTuning): Float {
if (primitives.isEmpty()) return Float.POSITIVE_INFINITY
val distances = FloatArray(primitives.size) { distanceToPrimitive(primitives[it], sampleX, sampleY, sampleZ, tuning) }
val minDistance = distances.min()
if (tuning.smoothingRange <= 0.001f) return minDistance
// Anything more than a few blend-widths past the closest primitive is cut - its
// contribution would be negligible in a correct blend anyway, but left in, it still
// drags the chained result down the more of them there are.
val cutoff = tuning.smoothingRange * 3f
var result = Float.POSITIVE_INFINITY
for (distance in distances) {
if (distance - minDistance > cutoff) continue
result = if (result == Float.POSITIVE_INFINITY) distance else smoothMinimum(result, distance, tuning.smoothingRange)
}
return result
}
/** Finite-difference gradient of the field at a point - points away from the surface, magnitude ~1 for a clean field. */
fun estimateGradient(primitives: List<FieldPrimitive>, point: Vec3, tuning: DistanceFieldTuning, epsilon: Float = 0.05f): Vec3 {
val px = point.x.toFloat()
val py = point.y.toFloat()
val pz = point.z.toFloat()
val dx = sampleField(primitives, px + epsilon, py, pz, tuning) - sampleField(primitives, px - epsilon, py, pz, tuning)
val dy = sampleField(primitives, px, py + epsilon, pz, tuning) - sampleField(primitives, px, py - epsilon, pz, tuning)
val dz = sampleField(primitives, px, py, pz + epsilon, tuning) - sampleField(primitives, px, py, pz - epsilon, tuning)
val gradient = Vec3(dx.toDouble(), dy.toDouble(), dz.toDouble())
return if (gradient.lengthSqr() > 1e-8) gradient.normalize() else Vec3(0.0, 1.0, 0.0)
}
@@ -0,0 +1,105 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.util
import net.minecraft.core.particles.DustParticleOptions
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.Entity
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
/** One shell to draw - a target field value, a color, a particle size. */
data class DistanceFieldLayer(
val isoValue: Float,
val color: Vector3f,
val particleSize: Float = 1.0f
)
/**
* Marches a grid of vertical columns through the field around [center] and spawns a
* particle everywhere it crosses one of [layers]' target values. Server-side only.
*/
fun visualizeDistanceField(
referenceEntity: Entity,
center: Vec3,
layers: List<DistanceFieldLayer>,
shape: PrimitiveShape = PrimitiveShape.BOX,
horizontalRadius: Double = 4.0,
verticalRange: Double = 4.0,
sampleSpacing: Double = 0.5,
marchStep: Double = 0.1,
tuning: DistanceFieldTuning = DistanceFieldTuning()
) {
val level = referenceEntity.level()
if (level !is ServerLevel) return
val searchBox = AABB(
center.x - horizontalRadius, center.y - verticalRange, center.z - horizontalRadius,
center.x + horizontalRadius, center.y, center.z + horizontalRadius
)
val primitives = collectPrimitivesFromBlocks(level, searchBox, shape, includeFluids = true)
if (primitives.isEmpty()) return
var x = -horizontalRadius
while (x <= horizontalRadius) {
var z = -horizontalRadius
while (z <= horizontalRadius) {
marchColumn(level, primitives, center, x, z, verticalRange, marchStep, layers, tuning)
z += sampleSpacing
}
x += sampleSpacing
}
}
private fun marchColumn(
level: ServerLevel,
primitives: List<FieldPrimitive>,
center: Vec3,
offsetX: Double,
offsetZ: Double,
verticalRange: Double,
marchStep: Double,
layers: List<DistanceFieldLayer>,
tuning: DistanceFieldTuning
) {
val previousValue = FloatArray(layers.size) { Float.NaN }
var y = verticalRange
while (y >= -verticalRange) {
val worldX = (center.x + offsetX).toFloat()
val worldY = (center.y + y).toFloat()
val worldZ = (center.z + offsetZ).toFloat()
val fieldValue = sampleField(primitives, worldX, worldY, worldZ, tuning)
for (layerIndex in layers.indices) {
val layer = layers[layerIndex]
val previous = previousValue[layerIndex]
if (!previous.isNaN() && crossedTarget(previous, fieldValue, layer.isoValue)) {
val t = (layer.isoValue - previous) / (fieldValue - previous)
val crossingY = (y + marchStep) - marchStep * t
spawnLayerParticle(level, center.add(offsetX, crossingY, offsetZ), layer)
}
previousValue[layerIndex] = fieldValue
}
y -= marchStep
}
}
private fun crossedTarget(previous: Float, current: Float, target: Float): Boolean =
(previous - target) * (current - target) < 0f
private fun spawnLayerParticle(level: ServerLevel, pos: Vec3, layer: DistanceFieldLayer) {
val particle = DustParticleOptions(layer.color, layer.particleSize)
level.sendParticles(particle, pos.x, pos.y, pos.z, 1, 0.0, 0.0, 0.0, 0.0)
}
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.api.spawning.TimeRange.Companion.timeRanges
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.util.lang
import net.minecraft.network.chat.MutableComponent
import com.cobblemon.mod.common.util.adapters.IntRangesAdapter
/**
@@ -49,4 +51,25 @@ class TimeRange : IntRanges {
constructor() : super()
constructor(vararg ranges: IntRange) : super(*ranges)
fun displayName(): MutableComponent? {
val key = timeRanges.entries.firstOrNull { entry -> this == entry.value }?.key ?: return null
return lang("ui.time_range.$key")
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is TimeRange) return false
if (other.ranges.size != this.ranges.size) return false
other.ranges.forEachIndexed { index, otherRange ->
if (this.ranges[index] != otherRange) return false
}
return true
}
override fun hashCode(): Int {
return ranges.hashCode()
}
}
@@ -8,6 +8,8 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.util.lang
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.level.Level
/**
@@ -42,4 +44,7 @@ enum class MoonPhase {
fun ofWorld(world: Level): MoonPhase = VALUES[world.moonPhase]
}
fun displayName(): MutableComponent {
return lang("ui.moon_phase.${this.name.lowercase()}")
}
}
@@ -71,7 +71,7 @@ class FishingSpawnCause(
}
fun alterSize(pokemonEntity: PokemonEntity, effect: SpawnBait.Effect) {
pokemonEntity.pokemon.scaleModifier *= effect.value.toFloat() / 1000.0f
pokemonEntity.pokemon.scaleModifier += effect.value.toFloat() / 1000.0f
}
fun saveDropsReroll(pokemonEntity: PokemonEntity, effect: SpawnBait.Effect) {
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.AlphaLevelMatchingSensor
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.Entity
@@ -76,7 +77,7 @@ open class SpawnBaitInfluence(val effects: List<SpawnBait.Effect>, val onUsed: (
?: return super.affectWeight(detail, spawnablePosition, weight)
val detailSpecies = pokemonDetail.pokemon.species
?.let { PokemonSpecies.getByName(it) }
?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) }
?: return super.affectWeight(detail, spawnablePosition, weight)
val formData = detailSpecies.getForm(pokemonDetail.pokemon.aspects)
@@ -27,6 +27,7 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.isBoxLoaded
import com.cobblemon.mod.common.util.removeIf
import com.cobblemon.mod.common.util.squeezeWithinBounds
import com.cobblemon.mod.common.util.toBlockPos
import com.cobblemon.mod.common.util.toVec3f
import com.cobblemon.mod.common.util.weightedSelection
import kotlin.math.max
@@ -190,74 +191,61 @@ interface Spawner {
}
fun isValidStartPoint(world: Level, chunk: ChunkAccess, startPos: BlockPos.MutableBlockPos): Boolean {
val y = startPos.y
if (!world.isLoaded(startPos) || !world.isLoaded(startPos.setY(y + 1))) {
val abovePos = startPos.above()
if (!world.isLoaded(startPos) || !world.isLoaded(abovePos)) {
return false
}
val mid = chunk.getBlockState(startPos)
val above = chunk.getBlockState(abovePos)
val mid = chunk.getBlockState(startPos.setY(y))
val above = chunk.getBlockState(startPos.setY(y + 1))
// Above must be non-solid
if (!above.isPathfindable(PathComputationType.AIR)) {
return false
}
// Position must be non-air
if (mid.isAir) {
return false
}
return true
return above.isPathfindable(PathComputationType.AIR) && mid.isAir
}
fun constrainArea(area: SpawningZoneInput): SpawningZoneInput? {
val basePos = BlockPos.MutableBlockPos(area.baseX, area.baseY, area.baseZ)
val originalY = area.baseY
val (chunkX, chunkZ) = Pair(SectionPos.blockToSectionCoord(area.baseX), SectionPos.blockToSectionCoord(area.baseZ))
val world = area.world
val centerPos = area.getCenter().toBlockPos()
val (centerChunkX, centerChunkZ) = Pair(SectionPos.blockToSectionCoord(centerPos.x), SectionPos.blockToSectionCoord(centerPos.z))
// if the chunk isn't loaded, we don't want to go further & we don't want the getChunk function below to load/create the chunk.
if (!area.world.areEntitiesLoaded(ChunkPos.asLong(chunkX, chunkZ))) return null
if (!world.areEntitiesLoaded(ChunkPos.asLong(centerChunkX, centerChunkZ))) return null
val chunk = area.world.getChunk(chunkX, chunkZ, ChunkStatus.FULL) ?: return null
val chunk = world.getChunk(centerChunkX, centerChunkZ, ChunkStatus.FULL) ?: return null
var valid = isValidStartPoint(area.world, chunk, basePos)
val probePos = centerPos.mutable()
var validY = if (isValidStartPoint(world, chunk, probePos)) centerPos.y else null
if (!valid) {
var offset = 1
do {
if (isValidStartPoint(area.world, chunk, basePos.setY(originalY + offset))) {
valid = true
basePos.y = originalY + offset
if (validY == null) {
for (offset in 1..Cobblemon.config.maxVerticalCorrectionBlocks) {
if (isValidStartPoint(world, chunk, probePos.set(centerPos.x, centerPos.y + offset, centerPos.z))) {
validY = centerPos.y + offset
break
} else if (isValidStartPoint(area.world, chunk, basePos.setY(originalY - offset))) {
valid = true
basePos.y = originalY - offset
} else if (isValidStartPoint(world, chunk, probePos.set(centerPos.x, centerPos.y - offset, centerPos.z))) {
validY = centerPos.y - offset
break
}
offset++
} while (offset <= Cobblemon.config.maxVerticalCorrectionBlocks)
}
if (valid) {
val min = area.world.squeezeWithinBounds(basePos)
val max = area.world.squeezeWithinBounds(basePos.move(area.length, area.height, area.width))
if (area.world.isLoaded(min) && area.world.isLoaded(max) &&
min.x < max.x && min.y < max.y && min.z < max.z
) {
return SpawningZoneInput(
cause = area.cause,
world = area.world,
baseX = min.x,
baseY = min.y,
baseZ = min.z,
length = max.x - min.x,
height = max.y - min.y,
width = max.z - min.z
)
}
}
val targetY = validY ?: return null
val newBaseY = (targetY - (area.height / 2)).coerceIn(world.minBuildHeight, world.maxBuildHeight)
val minPos = world.squeezeWithinBounds(BlockPos(area.baseX, newBaseY, area.baseZ))
val maxPos = world.squeezeWithinBounds(BlockPos(area.baseX + area.length, newBaseY + area.height, area.baseZ + area.width))
val finalBox = AABB(
minPos.x.toDouble(), minPos.y.toDouble(), minPos.z.toDouble(),
maxPos.x.toDouble(), maxPos.y.toDouble(), maxPos.z.toDouble()
)
if (minPos.x < maxPos.x && minPos.y < maxPos.y && minPos.z < maxPos.z && world.isBoxLoaded(finalBox)) {
return SpawningZoneInput(
cause = area.cause,
world = world,
baseX = minPos.x,
baseY = minPos.y,
baseZ = minPos.z,
length = maxPos.x - minPos.x,
height = maxPos.y - minPos.y,
width = maxPos.z - minPos.z
)
}
return null
}
@@ -17,10 +17,10 @@ import com.cobblemon.mod.common.api.storage.factory.PokemonStoreFactory
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
import com.google.gson.JsonObject
import java.util.UUID
import net.minecraft.core.RegistryAccess
import net.minecraft.nbt.CompoundTag
import net.minecraft.server.level.ServerPlayer
import java.util.UUID
/**
* The base class for all stores of [Pokemon]. A store specifies the kind of coordinate it needs to be given
@@ -77,7 +77,6 @@ abstract class PokemonStore<T : StorePosition> : Iterable<Pokemon> {
remove(pokemon)
val position = getFirstAvailablePosition() ?: return false // Couldn't fit, shrug emoji
set(position, pokemon)
Cobblemon.playerDataManager.getTMData(uuid)?.syncTMsFromPokemon(pokemon)
return true
}
@@ -120,7 +120,7 @@ object CobblemonItemTags {
@JvmField val DESTINY_KNOT = create("held/destiny_knot")
@JvmField val EVERSTONE = create("held/everstone")
@JvmField val EXPERIENCE_SHARE = create("held/experience_share")
@JvmField val IS_FRIENDSHIP_BOOSTER = create("is_friendship_booster")
@JvmField val IS_FRIENDSHIP_BOOSTER = create("held/is_friendship_booster")
@JvmField val ANY_HELD_ITEM = create("held/is_held_item")
@JvmField val BLACKLISTED_ITEMS_TO_HOLD = create("held/blacklisted_items_to_hold")
@JvmField val WHITELISTED_ITEMS_TO_HOLD = create("held/whitelisted_items_to_hold")
@@ -34,6 +34,7 @@ class ElementalType(
val resourceLocation: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, "ui/types.png"),
val showdownId: String = ShowdownIdentifiable.REGEX.replace(name.lowercase(), ""),
val typeGem: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, showdownId + "_gem"),
val order: Int = 1000
) : ShowdownIdentifiable {
override fun showdownId(): String {
@@ -25,7 +25,8 @@ object ElementalTypes {
hue = 0xE8E8DA,
textureXMultiplier = 0,
primaryColor = 0x8FA689,
secondaryColor = 0xFFFBE2
secondaryColor = 0xFFFBE2,
order = 0
)
@JvmField
@@ -35,7 +36,8 @@ object ElementalTypes {
hue = 0xFF6E21,
textureXMultiplier = 1,
primaryColor = 0xF92D2D,
secondaryColor = 0xF99B1D
secondaryColor = 0xF99B1D,
order = 1
)
@JvmField
@@ -45,7 +47,8 @@ object ElementalTypes {
hue = 0x3FA5FF,
textureXMultiplier = 2,
primaryColor = 0x377AF6,
secondaryColor = 0x50DDF9
secondaryColor = 0x50DDF9,
order = 2
)
@JvmField
@@ -55,7 +58,8 @@ object ElementalTypes {
hue = 0x62D14F,
textureXMultiplier = 3,
primaryColor = 0x0EAE95,
secondaryColor = 0xC6F554
secondaryColor = 0xC6F554,
order = 3
)
@JvmField
@@ -65,7 +69,8 @@ object ElementalTypes {
hue = 0xFFD314,
textureXMultiplier = 4,
primaryColor = 0xF7AB19,
secondaryColor = 0xFFF257
secondaryColor = 0xFFF257,
order = 4
)
@JvmField
@@ -75,7 +80,8 @@ object ElementalTypes {
hue = 0x54F2F2,
textureXMultiplier = 5,
primaryColor = 0x41BAED,
secondaryColor = 0x91F4F9
secondaryColor = 0x91F4F9,
order = 5
)
@JvmField
@@ -85,7 +91,8 @@ object ElementalTypes {
hue = 0xEF565D,
textureXMultiplier = 6,
primaryColor = 0xC8356B,
secondaryColor = 0xF36A3F
secondaryColor = 0xF36A3F,
order = 6
)
@JvmField
@@ -95,7 +102,8 @@ object ElementalTypes {
hue = 0xD651FF,
textureXMultiplier = 7,
primaryColor = 0x7B41DD,
secondaryColor = 0xE568F4
secondaryColor = 0xE568F4,
order = 7
)
@JvmField
@@ -105,7 +113,8 @@ object ElementalTypes {
hue = 0xF4A453,
textureXMultiplier = 8,
primaryColor = 0xE08835,
secondaryColor = 0xFFD057
secondaryColor = 0xFFD057,
order = 8
)
@JvmField
@@ -115,7 +124,8 @@ object ElementalTypes {
hue = 0xB8B2FF,
textureXMultiplier = 9,
primaryColor = 0x6083F6,
secondaryColor = 0xD5CCFA
secondaryColor = 0xD5CCFA,
order = 9
)
@JvmField
@@ -125,7 +135,8 @@ object ElementalTypes {
hue = 0xFF5E9E,
textureXMultiplier = 10,
primaryColor = 0xE553CE,
secondaryColor = 0xF69DD5
secondaryColor = 0xF69DD5,
order = 10
)
@JvmField
@@ -135,7 +146,8 @@ object ElementalTypes {
hue = 0xD3D319,
textureXMultiplier = 11,
primaryColor = 0x9FBE1B,
secondaryColor = 0xE9FB5B
secondaryColor = 0xE9FB5B,
order = 11
)
@JvmField
@@ -145,7 +157,8 @@ object ElementalTypes {
hue = 0xB7A16E,
textureXMultiplier = 12,
primaryColor = 0x9F814A,
secondaryColor = 0xEECB7A
secondaryColor = 0xEECB7A,
order = 12
)
@JvmField
@@ -155,7 +168,8 @@ object ElementalTypes {
hue = 0x9C80F7,
textureXMultiplier = 13,
primaryColor = 0x4F57DD,
secondaryColor = 0xC182F1
secondaryColor = 0xC182F1,
order = 13
)
@JvmField
@@ -165,7 +179,8 @@ object ElementalTypes {
hue = 0x7580FF,
textureXMultiplier = 14,
primaryColor = 0x4E68E2,
secondaryColor = 0x47C7F7
secondaryColor = 0x47C7F7,
order = 14
)
@JvmField
@@ -175,7 +190,8 @@ object ElementalTypes {
hue = 0x587DA0,
textureXMultiplier = 15,
primaryColor = 0x465B99,
secondaryColor = 0x689EBF
secondaryColor = 0x689EBF,
order = 15
)
@JvmField
@@ -185,7 +201,8 @@ object ElementalTypes {
hue = 0xABD1F4,
textureXMultiplier = 16,
primaryColor = 0x628CB9,
secondaryColor = 0xD6F7F3
secondaryColor = 0xD6F7F3,
order = 16
)
@JvmField
@@ -195,18 +212,21 @@ object ElementalTypes {
hue = 0xFF7FE5,
textureXMultiplier = 17,
primaryColor = 0xE2678C,
secondaryColor = 0xF5A7B7
secondaryColor = 0xF5A7B7,
order = 17
)
@JvmStatic
fun register(name: String, displayName: MutableComponent, hue: Int, textureXMultiplier: Int, primaryColor: Int, secondaryColor: Int): ElementalType {
@JvmOverloads
fun register(name: String, displayName: MutableComponent, hue: Int, textureXMultiplier: Int, primaryColor: Int, secondaryColor: Int, order: Int = 1000): ElementalType {
return ElementalType(
name = name,
displayName = displayName,
hue = hue,
textureXMultiplier = textureXMultiplier,
primaryColor = primaryColor,
secondaryColor = secondaryColor
secondaryColor = secondaryColor,
order = order
).also {
allTypes.add(it)
}
@@ -83,7 +83,8 @@ open class BattlePokemon(
"max_health" to Function { DoubleValue(maxHealth.toDouble()) },
"ivs" to Function { effectedPokemon.ivs.struct },
"nature" to Function { StringValue(effectedPokemon.nature.name.toString()) },
"moveset" to Function { effectedPokemon.moveSet.toStruct() }
"moveset" to Function { effectedPokemon.moveSet.toStruct() },
"has_faced_opponents" to Function { DoubleValue(facedOpponents.isNotEmpty()) }
))
val uuid: UUID
@@ -0,0 +1,137 @@
/*
* 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.block.decor
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.util.StringRepresentable
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class ActivatableDecorationBlock(settings: Properties, val type: ActivatableDecorationBlockType) : HorizontalDirectionalBlock(settings), SimpleWaterloggedBlock {
enum class ActivatableDecorationBlockType: StringRepresentable {
//For basic use, also ignore camelcase errors, it's just dumb
CENTERED_8x,
//For specific items
METRONOME;
override fun getSerializedName(): String = this.name.lowercase()
companion object {
val CODEC = StringRepresentable.fromEnum(::values)
}
}
companion object {
val CODEC: MapCodec<ActivatableDecorationBlock> = RecordCodecBuilder.mapCodec { it.group(
propertiesCodec(),
ActivatableDecorationBlockType.CODEC.fieldOf("type").forGetter(ActivatableDecorationBlock::type)
).apply(it, ::ActivatableDecorationBlock) }
val ACTIVE = BooleanProperty.create("active")
val CENTERED_8x = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.5, 0.75)
val METRONOME_HITBOX = Shapes.box(0.25, 0.0, 0.375, 0.75, 0.8125, 0.75)
fun getShape(type: ActivatableDecorationBlockType): VoxelShape {
return when (type) {
ActivatableDecorationBlockType.METRONOME -> METRONOME_HITBOX
else -> CENTERED_8x
}
}
}
init {
registerDefaultState(stateDefinition.any()
.setValue(HORIZONTAL_FACING, Direction.NORTH)
.setValue(ACTIVE, false)
.setValue(WATERLOGGED, false)
)
}
override fun codec(): MapCodec<ActivatableDecorationBlock> = CODEC
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(HORIZONTAL_FACING, ACTIVE, WATERLOGGED)
}
override fun getFluidState(blockState: BlockState): FluidState? = if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState)
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (state.getValue(HORIZONTAL_FACING).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
val direction = state.getValue(HORIZONTAL_FACING)
return rotateShape(Direction.NORTH, direction, getShape(type))
}
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
return getCollisionShape(state, level, pos, context).singleEncompassing()
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val fluidState = context.level.getFluidState(context.clickedPos)
return super.getStateForPlacement(context)?.setValue(HORIZONTAL_FACING, context.horizontalDirection.opposite)?.setValue(WATERLOGGED, fluidState.type === Fluids.WATER)
}
override fun useWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hitResult: BlockHitResult
): InteractionResult {
if (level.isClientSide) {
level.setBlockAndUpdate(pos, state.cycle(ACTIVE))
return InteractionResult.SUCCESS
} else {
this.push(state, level, pos, null as Player?)
return InteractionResult.CONSUME
}
}
fun push(state: BlockState, level: Level, pos: BlockPos?, player: Player?) {
var state = state
state = state.cycle(ACTIVE)
level.setBlock(pos!!, state, 3)
level.gameEvent(player, if (state.getValue(ACTIVE)) GameEvent.BLOCK_ACTIVATE else GameEvent.BLOCK_DEACTIVATE, pos)
}
override fun hasAnalogOutputSignal(state: BlockState): Boolean {
return true
}
//TODO: Replace this forced 4 with a value that can be set by the block, only relevant when more blocks of this class are added.
override fun getAnalogOutputSignal(state: BlockState, level: Level, pos: BlockPos): Int {
return if (level.getBlockState(pos).getValue(ACTIVE) as Boolean) 4 else 0
}
}
@@ -0,0 +1,133 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.util.StringRepresentable
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class DecorativeItemBlock(settings: Properties, val type: DecorativeItemBlockType): HorizontalDirectionalBlock(settings), SimpleWaterloggedBlock {
enum class DecorativeItemBlockType: StringRepresentable {
//For basic use, also ignore camelcase errors, it's just dumb
CENTERED_8x,
CENTERED_6x,
FLATTENED_8x6,
FLATTENED_8x4,
FLATTENED_10x4,
//For specific items
TEACUP,
TEAPOT,
KNOT,
POUCH,
NEVERMELT,
SCOPE,
WIDE,
ZOOM,
IZER;
override fun getSerializedName(): String = this.name.lowercase()
companion object {
val CODEC = StringRepresentable.fromEnum(::values)
}
}
companion object {
val CODEC: MapCodec<DecorativeItemBlock> = RecordCodecBuilder.mapCodec { it.group(
propertiesCodec(),
DecorativeItemBlockType.CODEC.fieldOf("type").forGetter(DecorativeItemBlock::type)
).apply(it, ::DecorativeItemBlock) }
val CENTERED_8x = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.5, 0.75)
val CENTERED_6x = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875)
val FLAT6_HITBOX = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.375, 0.75)
val FLAT4_HITBOX = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.25, 0.75)
val SMOOSHED4_HITBOX = Shapes.box(0.1875, 0.0, 0.1875, 0.8125, 0.25, 0.8125)
val TEACUP_HITBOX = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.25, 0.6875)
val TEAPOT_HITBOX = Shapes.box(0.1875, 0.0, 0.1875, 0.8125, 0.625, 0.8125)
val KNOT_HITBOX = Shapes.box(0.375, 0.0, 0.375, 0.75, 0.375, 0.75)
val POUCH_HITBOX = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.3125, 0.6875)
val NEVERMELT_HITBOX = Shapes.box(0.25, 0.0, 0.3125, 0.6875, 0.25, 0.75)
val SCOPE_HITBOX = Shapes.box(0.625, 0.0, 0.4375, 0.9375, 0.125, 0.75)
val WIDE_HITBOX = Shapes.box(0.625, 0.0, 0.125, 0.9375, 0.0625, 0.625)
val ZOOM_HITBOX = Shapes.box(0.125, 0.0, 0.1875, 0.375, 0.1875, 0.5)
val IZER_HITBOX = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.375, 0.75)
fun getShape(type: DecorativeItemBlockType): VoxelShape {
return when (type) {
DecorativeItemBlockType.TEAPOT -> TEAPOT_HITBOX
DecorativeItemBlockType.TEACUP -> TEACUP_HITBOX
DecorativeItemBlockType.KNOT -> KNOT_HITBOX
DecorativeItemBlockType.POUCH -> POUCH_HITBOX
DecorativeItemBlockType.NEVERMELT -> NEVERMELT_HITBOX
DecorativeItemBlockType.SCOPE -> SCOPE_HITBOX
DecorativeItemBlockType.WIDE -> WIDE_HITBOX
DecorativeItemBlockType.ZOOM -> ZOOM_HITBOX
DecorativeItemBlockType.IZER -> IZER_HITBOX
DecorativeItemBlockType.FLATTENED_10x4 -> SMOOSHED4_HITBOX
DecorativeItemBlockType.FLATTENED_8x4 -> FLAT4_HITBOX
DecorativeItemBlockType.FLATTENED_8x6 -> FLAT6_HITBOX
DecorativeItemBlockType.CENTERED_6x -> CENTERED_6x
else -> CENTERED_8x
}
}
}
init {
registerDefaultState(stateDefinition.any()
.setValue(HORIZONTAL_FACING, Direction.NORTH)
.setValue(WATERLOGGED, false))
}
override fun codec(): MapCodec<DecorativeItemBlock> = CODEC
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(HORIZONTAL_FACING, WATERLOGGED)
}
override fun getFluidState(blockState: BlockState): FluidState? = if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState)
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (state.getValue(HORIZONTAL_FACING).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
val direction = state.getValue(HORIZONTAL_FACING)
return rotateShape(Direction.NORTH, direction, getShape(type))
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val fluidState = context.level.getFluidState(context.clickedPos)
return super.getStateForPlacement(context)?.setValue(HORIZONTAL_FACING, context.horizontalDirection.opposite)?.setValue(WATERLOGGED, fluidState.type === Fluids.WATER)
}
}
@@ -0,0 +1,139 @@
/*
* 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.block
import com.cobblemon.mod.common.util.rotateShape
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.ButtonBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.AttachFace
import net.minecraft.world.level.block.state.properties.BlockSetType
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class EjectButtonBlock(properties: Properties, ticksToStayPressed: Int) : ButtonBlock(BlockSetType.IRON, ticksToStayPressed, properties), SimpleWaterloggedBlock {
companion object {
val FLOOR_UNPOWERED = Shapes.or(
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.1875, 0.875),
Shapes.box(0.375, 0.1875, 0.375, 0.625, 0.3125, 0.625) )
val FLOOR_POWERED = Shapes.or(
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.1875, 0.875),
Shapes.box(0.375, 0.1875, 0.375, 0.625, 0.25, 0.625) )
val FLOOR_PANEL = Shapes.box(0.125, 0.0, 0.125, 0.875, 0.1875, 0.875)
val CEILING_UNPOWERED = Shapes.or(
Shapes.box(0.125, 0.8125, 0.125, 0.875, 1.0, 0.875),
Shapes.box(0.375, 0.6875, 0.375, 0.625, 0.8125, 0.625) )
val CEILING_POWERED = Shapes.or(
Shapes.box(0.125, 0.8125, 0.125, 0.875, 1.0, 0.875),
Shapes.box(0.375, 0.75, 0.375, 0.625, 0.8125, 0.625) )
val CEILING_PANEL = Shapes.box(0.125, 0.8125, 0.125, 0.875, 1.0, 0.875)
val WALL_UNPOWERED = Shapes.or(
Shapes.box(0.125, 0.125, 0.8125, 0.875, 0.875, 1.0),
Shapes.box(0.375, 0.375, 0.6875, 0.625, 0.625, 0.8125) )
val WALL_POWERED = Shapes.or(
Shapes.box(0.125, 0.125, 0.8125, 0.875, 0.875, 1.0),
Shapes.box(0.375, 0.375, 0.75, 0.625, 0.625, 0.8125) )
val WALL_PANEL = Shapes.box(0.125, 0.125, 0.8125, 0.875, 0.875, 1.0)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(FACE, AttachFace.WALL)
.setValue(POWERED, false)
.setValue(WATERLOGGED, false)
)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING, FACE, POWERED, WATERLOGGED)
}
override fun getFluidState(blockState: BlockState): FluidState? {
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState);
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val fluidState = context.level.getFluidState(context.clickedPos)
for (direction in context.nearestLookingDirections) {
val blockState: BlockState
if (direction.getAxis() === Direction.Axis.Y) {
blockState = (defaultBlockState()
.setValue(FACE, if (direction == Direction.UP) AttachFace.CEILING else AttachFace.FLOOR) as BlockState)
.setValue(FACING, context.horizontalDirection.opposite)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
} else {
blockState = (defaultBlockState()
.setValue(FACE, AttachFace.WALL) as BlockState)
.setValue(FACING, direction.opposite)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
}
if (blockState.canSurvive(context.getLevel(), context.getClickedPos())) {
return blockState
}
}
return null
}
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
val direction = state.getValue(FACING)
return when (state.getValue(FACE)) {
AttachFace.FLOOR -> if (state.getValue(POWERED)) {FLOOR_POWERED} else {FLOOR_UNPOWERED}
AttachFace.WALL ->
when (direction) {
Direction.NORTH, Direction.UP, Direction.DOWN -> if (state.getValue(POWERED)) {WALL_POWERED} else {WALL_UNPOWERED}
else -> rotateShape(Direction.NORTH, direction, if (state.getValue(POWERED)) {WALL_POWERED} else {WALL_UNPOWERED})
}
AttachFace.CEILING -> if (state.getValue(POWERED)) {CEILING_POWERED} else {CEILING_UNPOWERED}
}
}
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
val direction = state.getValue(FACING)
return when (state.getValue(FACE)) {
AttachFace.FLOOR -> FLOOR_PANEL
AttachFace.WALL -> when (direction) {
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_PANEL
else -> rotateShape(Direction.NORTH, direction, WALL_PANEL)
}
AttachFace.CEILING -> CEILING_PANEL
}
}
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (getConnectedDirection(state).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return if (state.getValue(FACE) != AttachFace.WALL) {
val direction = getConnectedDirection(state)
canSupportCenter(level, pos.relative(direction.opposite), direction)
} else {
level.getBlockState(pos.relative(state.getValue(FACING).opposite)).isSolid
}
}
}
@@ -0,0 +1,63 @@
/*
* 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.block
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.AttachFace
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class GalaricaWreathBlock(properties: Properties) : SixFaceRotationalBlock(properties) {
companion object {
val CODEC: MapCodec<GalaricaWreathBlock> = RecordCodecBuilder.mapCodec {
it.group(propertiesCodec()).apply(it, ::GalaricaWreathBlock)
}
val FLOOR_NORTH = Shapes.box(0.125, 0.0, 0.125, 0.875, 0.25, 0.875)
val WALL_NORTH = Shapes.box(0.125, 0.125, 0.75, 0.875, 0.875, 1.0)
val CEILING_NORTH = Shapes.box(0.125, 0.125, 0.375, 0.875, 0.625, 0.5625)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(FACE, AttachFace.WALL)
.setValue(WATERLOGGED, false)
)
}
override fun codec(): MapCodec<GalaricaWreathBlock> = CODEC
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
val direction = state.getValue(FACING)
return when (state.getValue(FACE)) {
AttachFace.FLOOR ->
if (direction.axis === Direction.Axis.X) {
rotateShape(Direction.NORTH, Direction.WEST, FLOOR_NORTH)
} else FLOOR_NORTH
AttachFace.WALL ->
when (direction) {
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_NORTH
else -> rotateShape(Direction.NORTH, direction, WALL_NORTH)
}
AttachFace.CEILING ->
if (direction.axis === Direction.Axis.X) {
rotateShape(Direction.NORTH, Direction.WEST, CEILING_NORTH)
} else CEILING_NORTH
}
}
}
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.AttachFace
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class MagnetBlock(properties: Properties) : SixFaceRotationalBlock(properties) {
companion object {
val CODEC: MapCodec<MagnetBlock> = RecordCodecBuilder.mapCodec {
it.group(propertiesCodec()).apply(it, ::MagnetBlock)
}
val FLOOR_NORTH = Shapes.box(0.25, 0.0, 0.25, 0.75, 0.1875, 0.75)
val WALL_NORTH = Shapes.box(0.25, 0.25, 0.8125, 0.75, 0.75, 1.0)
val CEILING_NORTH = Shapes.box(0.25, 0.8125, 0.25, 0.75, 1.0, 0.75)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(FACE, AttachFace.WALL)
.setValue(WATERLOGGED, false)
)
}
override fun codec(): MapCodec<MagnetBlock> = CODEC
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
val direction = state.getValue(FACING)
return when (state.getValue(FACE)) {
AttachFace.FLOOR ->
if (direction.axis === Direction.Axis.X) {
rotateShape(Direction.NORTH, Direction.WEST, FLOOR_NORTH)
} else FLOOR_NORTH
AttachFace.WALL ->
when (direction) {
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_NORTH
else -> rotateShape(Direction.NORTH, direction, WALL_NORTH)
}
AttachFace.CEILING ->
if (direction.axis === Direction.Axis.X) {
rotateShape(Direction.NORTH, Direction.WEST, CEILING_NORTH)
} else CEILING_NORTH
}
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return true
}
}
@@ -103,15 +103,15 @@ class MintBlock(private val mintType: MintType, settings: Properties) : CropBloc
GREEN -> CobblemonItems.GREEN_MINT_SEEDS
WHITE -> CobblemonItems.WHITE_MINT_SEEDS
}
fun getLeaf(): MintLeafItem = when (this) {
RED -> CobblemonItems.RED_MINT_LEAF
BLUE -> CobblemonItems.BLUE_MINT_LEAF
CYAN -> CobblemonItems.CYAN_MINT_LEAF
PINK -> CobblemonItems.PINK_MINT_LEAF
GREEN -> CobblemonItems.GREEN_MINT_LEAF
WHITE -> CobblemonItems.WHITE_MINT_LEAF
}
// These don't seem to get called to anywhere besides the direct item, and since I'm trying to do funny stuff there, just commenting this out
// fun getLeaf(): MintLeafItem = when (this) {
// RED -> CobblemonItems.RED_MINT_LEAF
// BLUE -> CobblemonItems.BLUE_MINT_LEAF
// CYAN -> CobblemonItems.CYAN_MINT_LEAF
// PINK -> CobblemonItems.PINK_MINT_LEAF
// GREEN -> CobblemonItems.GREEN_MINT_LEAF
// WHITE -> CobblemonItems.WHITE_MINT_LEAF
// }
fun getCropBlock(): MintBlock = when (this) {
RED -> CobblemonBlocks.RED_MINT
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.util.rotateShape
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.AttachFace
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class OrbBlock(properties: Properties) : SixFaceRotationalBlock(properties) {
companion object {
val CODEC: MapCodec<OrbBlock> = RecordCodecBuilder.mapCodec {
it.group(propertiesCodec()).apply(it, ::OrbBlock)
}
val FLOOR_NORTH = Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875)
val WALL_NORTH = Shapes.box(0.3125, 0.3125, 0.625, 0.6875, 0.6875, 1.0)
val CEILING_NORTH = Shapes.box(0.3125, 0.625, 0.375, 0.6875, 1.0, 0.6875)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(FACE, AttachFace.WALL)
.setValue(WATERLOGGED, false)
)
}
override fun codec(): MapCodec<OrbBlock> = CODEC
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
val direction = state.getValue(FACING)
return when (state.getValue(FACE)) {
AttachFace.FLOOR ->
if (direction.axis === Direction.Axis.X) {
rotateShape(Direction.NORTH, Direction.WEST, FLOOR_NORTH)
} else FLOOR_NORTH
AttachFace.WALL ->
when (direction) {
Direction.NORTH, Direction.UP, Direction.DOWN -> WALL_NORTH
else -> rotateShape(Direction.NORTH, direction, WALL_NORTH)
}
AttachFace.CEILING ->
if (direction.axis === Direction.Axis.X) {
rotateShape(Direction.NORTH, Direction.WEST, CEILING_NORTH)
} else CEILING_NORTH
}
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return true
}
}
@@ -13,25 +13,15 @@ import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.FaceAttachedHorizontalDirectionalBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.AttachFace
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class PlaqueBlock(properties : Properties) : FaceAttachedHorizontalDirectionalBlock(properties), SimpleWaterloggedBlock {
class PlaqueBlock(properties : Properties) : SixFaceRotationalBlock(properties) {
companion object {
val CODEC: MapCodec<PlaqueBlock> = RecordCodecBuilder.mapCodec {
it.group(propertiesCodec()).apply(it, ::PlaqueBlock)
@@ -54,40 +44,6 @@ class PlaqueBlock(properties : Properties) : FaceAttachedHorizontalDirectionalBl
)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING, FACE, WATERLOGGED)
}
override fun getFluidState(blockState: BlockState): FluidState? {
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState);
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val fluidState = context.level.getFluidState(context.clickedPos)
for (direction in context.nearestLookingDirections) {
val blockState: BlockState
if (direction.getAxis() === Direction.Axis.Y) {
blockState = (defaultBlockState()
.setValue(FACE, if (direction == Direction.UP) AttachFace.CEILING else AttachFace.FLOOR) as BlockState)
.setValue(FACING, context.horizontalDirection.opposite)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
} else {
blockState = (defaultBlockState()
.setValue(FACE, AttachFace.WALL) as BlockState)
.setValue(FACING, direction.opposite)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
}
if (blockState.canSurvive(context.getLevel(), context.getClickedPos())) {
return blockState
}
}
return null
}
override fun codec(): MapCodec<PlaqueBlock> = CODEC
override fun getShape(state: BlockState, blockGetter: BlockGetter, pos: BlockPos, collisionContext: CollisionContext): VoxelShape {
@@ -108,21 +64,4 @@ class PlaqueBlock(properties : Properties) : FaceAttachedHorizontalDirectionalBl
} else CEILING_NORTH
}
}
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (getConnectedDirection(state).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return if (state.getValue(FACE) == AttachFace.CEILING) {
val direction = getConnectedDirection(state)
canSupportCenter(level, pos.relative(direction.opposite), direction)
} else {
super.canSurvive(state, level, pos)
}
}
}
@@ -0,0 +1,116 @@
/*
* 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.block.decor
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.HorizontalDirectionalBlock.FACING
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.block.state.properties.Property
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class RingTargetBlock(settings: Properties) : TargetBlock(settings), SimpleWaterloggedBlock {
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(WATERLOGGED, false)
.setValue(OUTPUT_POWER, 0))
}
override fun getStateForPlacement(ctx: BlockPlaceContext): BlockState? {
var blockState = defaultBlockState()
.setValue(WATERLOGGED, ctx.level.getFluidState(ctx.clickedPos).type == Fluids.WATER)
val worldView = ctx.level
val blockPos = ctx.clickedPos
ctx.nearestLookingDirections.forEach { direction ->
if (direction.axis.isHorizontal) {
blockState = blockState.setValue(FACING, direction.opposite) as BlockState
if (blockState.canSurvive(worldView, blockPos)) {
return blockState
}
}
}
return null
}
override fun getFluidState(blockState: BlockState): FluidState? {
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING)
builder.add(WATERLOGGED)
builder.add(*arrayOf<Property<*>>(OUTPUT_POWER))
}
override fun getShape(
state: BlockState,
blockGetter: BlockGetter,
pos: BlockPos,
collisionContext: CollisionContext
): VoxelShape {
return when (state.getValue(FACING)) {
Direction.SOUTH -> FULL_PLANE_SOUTH
Direction.WEST -> FULL_PLANE_WEST
Direction.EAST -> FULL_PLANE_EAST
else -> FULL_PLANE_NORTH
}
}
override fun updateShape(
state: BlockState,
direction: Direction,
neighborState: BlockState,
world: LevelAccessor,
pos: BlockPos,
neighborPos: BlockPos
): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (direction.opposite == state.getValue(FACING) && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun rotate(state: BlockState, rotation: Rotation): BlockState {
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)))
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return level.getBlockState(pos.relative(state.getValue(FACING).opposite)).isSolid
}
companion object {
val CODEC: MapCodec<RingTargetBlock> = RecordCodecBuilder.mapCodec {
it.group(
propertiesCodec()
).apply(it, ::RingTargetBlock) }
val OUTPUT_POWER = BlockStateProperties.POWER;
val FULL_PLANE_NORTH = Shapes.box(0.0, 0.0, 0.925, 1.0, 1.0, 1.0)
val FULL_PLANE_WEST = Shapes.box(0.925, 0.0, 0.0, 1.0, 1.0, 1.0)
val FULL_PLANE_SOUTH = Shapes.box(0.0, 0.0, 0.0, 1.0, 1.0, 0.075)
val FULL_PLANE_EAST = Shapes.box(0.0, 0.0, 0.0, 0.075, 1.0, 1.0)
}
}
@@ -34,7 +34,9 @@ import net.minecraft.world.item.alchemy.Potions
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.BonemealableBlock
import net.minecraft.world.level.block.DispenserBlock
import net.minecraft.world.level.block.LeavesBlock
import net.minecraft.world.level.block.state.BlockState
@@ -52,7 +54,7 @@ import net.minecraft.world.phys.shapes.EntityCollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings), BonemealableBlock {
companion object {
val AGE: IntegerProperty = BlockStateProperties.AGE_2
val DISTANCE: IntegerProperty = BlockStateProperties.DISTANCE
@@ -181,6 +183,15 @@ class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
super.randomTick(state, world, pos, random)
}
override fun isValidBonemealTarget(level: LevelReader, pos: BlockPos, state: BlockState): Boolean =
state.getValue(AGE) < MAX_AGE && !state.getValue(BlockStateProperties.WATERLOGGED)
override fun isBonemealSuccess(level: Level, random: RandomSource, pos: BlockPos, state: BlockState) = true
override fun performBonemeal(level: ServerLevel, random: RandomSource, pos: BlockPos, state: BlockState) {
level.setBlock(pos, changeAge(state, 1), UPDATE_CLIENTS)
}
override fun placeLiquid(level: LevelAccessor, pos: BlockPos, state: BlockState, fluidState: FluidState): Boolean {
if (!state.getValue(BlockStateProperties.WATERLOGGED) && fluidState.type === Fluids.WATER) {
val hasHoney = state.getValue(AGE) > 0
@@ -0,0 +1,87 @@
/*
* 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.block
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.FaceAttachedHorizontalDirectionalBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.AttachFace
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
open class SixFaceRotationalBlock(properties : Properties) : FaceAttachedHorizontalDirectionalBlock(properties), SimpleWaterloggedBlock {
companion object {
val CODEC = simpleCodec(::SixFaceRotationalBlock)
}
override fun codec(): MapCodec<out SixFaceRotationalBlock> = CODEC
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING, FACE, WATERLOGGED)
}
override fun getFluidState(blockState: BlockState): FluidState? {
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState);
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
val fluidState = context.level.getFluidState(context.clickedPos)
for (direction in context.nearestLookingDirections) {
val blockState: BlockState
if (direction.getAxis() === Direction.Axis.Y) {
blockState = (defaultBlockState()
.setValue(FACE, if (direction == Direction.UP) AttachFace.CEILING else AttachFace.FLOOR) as BlockState)
.setValue(FACING, context.horizontalDirection.opposite)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
} else {
blockState = (defaultBlockState()
.setValue(FACE, AttachFace.WALL) as BlockState)
.setValue(FACING, direction.opposite)
.setValue(WATERLOGGED, fluidState.type === Fluids.WATER
) as BlockState
}
if (blockState.canSurvive(context.getLevel(), context.getClickedPos())) {
return blockState
}
}
return null
}
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (getConnectedDirection(state).opposite == direction && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun canSurvive(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
return if (state.getValue(FACE) == AttachFace.CEILING) {
val direction = getConnectedDirection(state)
canSupportCenter(level, pos.relative(direction.opposite), direction)
} else if (state.getValue(FACE) == AttachFace.WALL) {
level.getBlockState(pos.relative(state.getValue(FACING).opposite)).isSolid
} else {
super.canSurvive(state, level, pos)
}
}
}
@@ -39,7 +39,15 @@ class StackableItemBlock(settings: Properties, val type: StackableItemBlockType)
ETHER,
MAX_POTION,
POTION,
STATUS_RESTORE;
STATUS_RESTORE,
VITAMIN,
METAL_COAT,
IRON_BALL,
THROAT_SPRAY,
UPGRADE,
DICE,
GLASS_BOTTLE,
EXTENDER;
override fun getSerializedName(): String = this.name.lowercase()
@@ -57,98 +65,90 @@ class StackableItemBlock(settings: Properties, val type: StackableItemBlockType)
StackableItemBlockType.CODEC.fieldOf("type").forGetter(StackableItemBlock::type)
).apply(it, ::StackableItemBlock) }
val northShapeMap: Map<StackableItemBlockType, List<VoxelShape>> = mapOf(
StackableItemBlockType.BATTLE_ITEM to listOf(
Shapes.box(0.34375, 0.0, 0.34375, 0.65625, 0.4375, 0.65625),
Shapes.or(
Shapes.box(0.09375, 0.0, 0.34375, 0.40625, 0.4375, 0.65625),
Shapes.box(0.59375, 0.0, 0.40625, 0.90625, 0.4375, 0.71875)
val northShapeMap: Map<StackableItemBlockType, List<VoxelShape>>
get() = mapOf(
StackableItemBlockType.BATTLE_ITEM to listOf(
Shapes.box(0.34375, 0.0, 0.34375, 0.65625, 0.4375, 0.65625),
Shapes.box(0.09375, 0.0, 0.34375, 0.90625, 0.4375, 0.71875),
Shapes.box(0.09375, 0.0, 0.09375, 0.90625, 0.4375, 0.90625),
Shapes.box(0.09375, 0.0, 0.09375, 0.96875, 0.4375, 0.90625)
),
Shapes.or(
Shapes.box(0.59375, 0.0, 0.59375, 0.90625, 0.4375, 0.90625),
Shapes.box(0.09375, 0.0, 0.53125, 0.40625, 0.4375, 0.84375),
Shapes.box(0.34375, 0.0, 0.09375, 0.65625, 0.4375, 0.40625)
StackableItemBlockType.ETHER to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5, 0.6875),
Shapes.box(0.125, 0.0, 0.3125, 0.875, 0.5, 0.75),
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.5, 1.0),
Shapes.box(0.125, 0.0, 0.0625, 0.9375, 0.5, 1.0)
),
Shapes.or(
Shapes.box(0.09375, 0.0, 0.09375, 0.40625, 0.4375, 0.40625),
Shapes.box(0.59375, 0.0, 0.15625, 0.90625, 0.4375, 0.46875),
Shapes.box(0.65625, 0.0, 0.59375, 0.96875, 0.4375, 0.90625),
Shapes.box(0.15625, 0.0, 0.53125, 0.46875, 0.4375, 0.84375)
)
),
StackableItemBlockType.ETHER to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5, 0.6875),
Shapes.or(
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.5, 0.75),
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.5, 0.6875)
StackableItemBlockType.MAX_POTION to listOf(
Shapes.box(0.34375, 0.0, 0.3125, 0.65625, 0.5, 0.6875),
Shapes.box(0.09375, 0.0, 0.3125, 0.90625, 0.5, 0.75),
Shapes.box(0.09375, 0.0, 0.0625, 0.90625, 0.5, 1.0),
Shapes.box(0.09375, 0.0, 0.0625, 0.96875, 0.5, 1.0)
),
Shapes.or(
Shapes.box(0.375, 0.0, 0.125, 0.625, 0.5, 0.5),
Shapes.box(0.125, 0.0, 0.5625, 0.375, 0.5, 0.9375),
Shapes.box(0.625, 0.0, 0.625, 0.875, 0.5, 1.0)
StackableItemBlockType.POTION to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5625, 0.75),
Shapes.box(0.125, 0.0, 0.3125, 0.875, 0.5625, 0.8125),
Shapes.box(0.125, 0.0, 0.0625, 0.875, 0.5625, 1.0),
Shapes.box(0.125, 0.0, 0.0, 0.9375, 0.5625, 1.0)
),
Shapes.or(
Shapes.box(0.125, 0.0, 0.0625, 0.375, 0.5, 0.4375),
Shapes.box(0.625, 0.0, 0.125, 0.875, 0.5, 0.5),
Shapes.box(0.6875, 0.0, 0.625, 0.9375, 0.5, 1.0),
Shapes.box(0.1875, 0.0, 0.5625, 0.4375, 0.5, 0.9375)
)
),
StackableItemBlockType.MAX_POTION to listOf(
Shapes.box(0.34375, 0.0, 0.3125, 0.65625, 0.5, 0.6875),
Shapes.or(
Shapes.box(0.09375, 0.0, 0.3125, 0.40625, 0.5, 0.6875),
Shapes.box(0.59375, 0.0, 0.375, 0.90625, 0.5, 0.75)
StackableItemBlockType.STATUS_RESTORE to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.4375, 0.6875),
Shapes.box(0.125, 0.0, 0.3125, 0.875, 0.4375, 0.75),
Shapes.box(0.125, 0.0, 0.0625, 0.875, 0.4375, 0.9375),
Shapes.box(0.125, 0.0, 0.0, 0.9375, 0.4375, 0.9375)
),
Shapes.or(
Shapes.box(0.34375, 0.0, 0.0625, 0.65625, 0.5, 0.4375),
Shapes.box(0.09375, 0.0, 0.5625, 0.40625, 0.5, 0.9375),
Shapes.box(0.59375, 0.0, 0.625, 0.90625, 0.5, 1.0)
StackableItemBlockType.METAL_COAT to listOf(
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875),
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.375, 0.875),
Shapes.box(0.125, 0.0, 0.0625, 0.9375, 0.375, 0.875),
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375)
),
Shapes.or(
Shapes.box(0.09375, 0.0, 0.0625, 0.40625, 0.5, 0.4375),
Shapes.box(0.15625, 0.0, 0.5625, 0.46875, 0.5, 0.9375),
Shapes.box(0.65625, 0.0, 0.625, 0.96875, 0.5, 1.0),
Shapes.box(0.59375, 0.0, 0.125, 0.90625, 0.5, 0.5)
).singleEncompassing()
),
StackableItemBlockType.POTION to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.5625, 0.75),
Shapes.or(
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.5625, 0.75),
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.5625, 0.8125)
StackableItemBlockType.VITAMIN to listOf(
Shapes.box(0.375, 0.0, 0.375, 0.625, 0.4375, 0.625),
Shapes.box(0.1875, 0.0, 0.3125, 0.8125, 0.4375, 0.6875),
Shapes.box(0.1875, 0.0, 0.1875, 0.8125, 0.4375, 0.8125),
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.4375, 0.875)
),
Shapes.or(
Shapes.box(0.375, 0.0, 0.0625, 0.625, 0.5625, 0.5),
Shapes.box(0.625, 0.0, 0.5625, 0.875, 0.5625, 1.0),
Shapes.box(0.125, 0.0, 0.5, 0.375, 0.5625, 0.9375)
StackableItemBlockType.IRON_BALL to listOf(
Shapes.box(0.25, 0.0, 0.25, 0.75, 0.4375, 0.75),
Shapes.box(0.0, 0.0, 0.1875, 1.0, 0.4375, 0.8125),
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.4375, 1.0),
Shapes.or(
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.4375, 1.0),
Shapes.box(0.25, 0.4375, 0.3125, 0.6875, 0.875, 0.75)
)
),
Shapes.or(
Shapes.box(0.125, 0.0, 0.0, 0.375, 0.5625, 0.4375),
Shapes.box(0.625, 0.0, 0.0625, 0.875, 0.5625, 0.5),
Shapes.box(0.6875, 0.0, 0.5625, 0.9375, 0.5625, 1.0),
Shapes.box(0.1875, 0.0, 0.5, 0.4375, 0.5625, 0.9375)
)
),
StackableItemBlockType.STATUS_RESTORE to listOf(
Shapes.box(0.375, 0.0, 0.3125, 0.625, 0.4375, 0.6875),
Shapes.or(
Shapes.box(0.125, 0.0, 0.3125, 0.375, 0.4375, 0.75),
Shapes.box(0.625, 0.0, 0.375, 0.875, 0.4375, 0.75)
StackableItemBlockType.THROAT_SPRAY to listOf(
Shapes.box(0.375, 0.0, 0.1875, 0.625, 0.5625, 0.75),
Shapes.box(0.125, 0.0, 0.1875, 0.875, 0.5625, 0.8125),
Shapes.box(0.125, 0.0, 0.0, 0.875, 0.5625, 1.0),
Shapes.box(0.075, 0.0, 0.0, 0.9375, 0.5625, 1.0)
),
Shapes.or(
Shapes.box(0.125, 0.0, 0.5, 0.375, 0.4375, 0.875),
Shapes.box(0.625, 0.0, 0.5625, 0.875, 0.4375, 0.9375),
Shapes.box(0.375, 0.0, 0.0625, 0.625, 0.4375, 0.4375)
StackableItemBlockType.UPGRADE to listOf(
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.125, 0.6875),
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.25, 0.6875),
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.375, 0.6875),
Shapes.box(0.3125, 0.0, 0.3125, 0.6875, 0.5, 0.6875)
),
Shapes.or(
Shapes.box(0.1875, 0.0, 0.5, 0.4375, 0.4375, 0.875),
Shapes.box(0.125, 0.0, 0.0, 0.375, 0.4375, 0.375),
Shapes.box(0.625, 0.0, 0.0625, 0.875, 0.4375, 0.4375),
Shapes.box(0.6875, 0.0, 0.5625, 0.9375, 0.4375, 0.9375)
StackableItemBlockType.DICE to listOf(
Shapes.box(0.125, 0.0, 0.6875, 0.3125, 0.1875, 0.875),
Shapes.box(0.125, 0.0, 0.625, 0.5625, 0.1875, 0.875),
Shapes.box(0.125, 0.0, 0.4375, 0.5625, 0.1875, 0.875),
Shapes.box(0.125, 0.0, 0.4375, 0.5625, 0.375, 0.875)
),
StackableItemBlockType.GLASS_BOTTLE to listOf(
Shapes.box(0.375, 0.0, 0.375, 0.625, 0.375, 0.625),
Shapes.box(0.1875, 0.0, 0.1875, 0.8125, 0.375, 0.6875),
Shapes.box(0.125, 0.0, 0.125, 0.8125, 0.375, 0.875),
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.375, 0.875)
),
StackableItemBlockType.EXTENDER to listOf(
Shapes.box(0.25, 0.0, 0.375, 0.625, 0.5625, 0.625),
Shapes.box(0.125, 0.0, 0.25, 0.875, 0.5625, 0.75),
Shapes.box(0.0625, 0.0, 0.0625, 0.875, 0.5625, 0.875),
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.5625, 0.9375)
)
)
)
fun getShape(type: StackableItemBlockType, count: Int): VoxelShape = northShapeMap[type]?.let { list -> list[(count - 1).coerceIn(0, list.lastIndex)] } ?: Shapes.block()
fun getMaxAmount(type: StackableItemBlockType): Int = min(northShapeMap[type]?.size ?: 1 , MAX_AMOUNT)
@@ -184,7 +184,7 @@ class StackableItemBlock(settings: Properties, val type: StackableItemBlockType)
}
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
return getCollisionShape(state, level, pos, context).singleEncompassing()
return getCollisionShape(state, level, pos, context)
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {

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