Compare commits

...
Author SHA1 Message Date
tmetcalfe89 8621315fc9 merged main in 2024-05-28 22:49:27 -07:00
Paul df8f078d13 Fixed Wooloo Dubwool issues and fixed sync issues with Gimmighoul 2024-05-27 15:39:21 +10:00
Paul 309b223da9 Changelog update 4 [skip-ci] 2024-05-27 11:53:03 +10:00
Paul 75f688fa30 Changelog update 3 [skip-ci] 2024-05-27 11:48:22 +10:00
Paul d7fadd58ed Changelog update 2 [skip-ci] 2024-05-27 11:42:56 +10:00
Paul 996c7189b6 Changelog update [skip-ci] 2024-05-27 11:31:38 +10:00
Paul e1d5f07aed Fixed root part animations on JSON posers, clean up changelog, no snapshot, remove bubblebeam effect for now 2024-05-27 11:14:05 +10:00
Paul 6afe77ef0c Fixed confusion particle timing 2024-05-27 10:23:00 +10:00
Hiroku bf54f28b58 Merge branch 'move-effects' into 'main'
A bunch of particles, now with sound!

See merge request cable-mc/cobblemon!1049
2024-05-27 00:08:36 +00:00
Paul aac10e967f Reverted protect change to camera mode 2024-05-27 10:06:39 +10:00
Sterrezo 0cd9da80d7 Update sounds.json 2024-05-27 02:06:27 +02:00
Sterrezo d33f1150d2 Adjusted assets for powder moves, protect, swords dance and thunder wave. Rebalanced volumes of all moves. 2024-05-27 01:39:41 +02:00
Hiroku 216ed85da1 Merge branch 'IllusionThrownPokeballRevealFix' into 'main'
IllusionThrownPokeballRevealFix

See merge request cable-mc/cobblemon!1046
2024-05-26 23:35:07 +00:00
Paul 4b70b20ab7 Fixed using confusion breaking battles lol 2024-05-27 09:26:40 +10:00
Segfault Guy 125e193b81 Broadcast messages with CLIENT species on pokeball hit. 2024-05-26 19:17:16 -04:00
Paul 1ebd249351 Fixed sleeppowder effect, added redundancy to action effects in case they run into an exception 2024-05-27 09:08:22 +10:00
Segfault Guy 74727cda48 Merge branch 'main' into IllusionThrownPokeballRevealFix 2024-05-26 18:47:53 -04:00
Paul 6a8a2489d1 Fixed structure reference to cave_support not working 2024-05-27 08:27:59 +10:00
Paul 3524931596 Fixed dyeable Pokemon not being dyeable away from white unless spawned via command, fixed creative mode players still losing the dye 2024-05-27 07:06:57 +10:00
Paul 43d9347c9c Updated zh and es_es translations 2024-05-27 06:47:56 +10:00
Tyzillion 60303504cd Changelog update. 2024-05-26 11:52:30 -04:00
Tyzillion 63db0066d3 more sounds! 2024-05-26 11:49:50 -04:00
apion 6e0270ac80 Fully moved mulch into block entity 2024-05-26 11:31:21 -04:00
Tyzillion 333e1fe9ae Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into move-effects 2024-05-26 09:35:42 -04:00
Tyzillion c0c8c103f4 Updated changelog 2024-05-26 09:11:47 -04:00
Tyzillion a414784cca Added sounds to sand attack and the spore mushrooms, implemented confusion cuz I forgot to last night. 2024-05-26 09:10:41 -04:00
Apion b4b01ef0b6 Merge branch 'sound' into 'main'
Updated all generic impact assets

See merge request cable-mc/cobblemon!1048
2024-05-26 12:45:16 +00:00
SterrezoandApion 04f725d72e Updated all generic impact assets 2024-05-26 12:45:16 +00:00
Hiroku 0257f591de Merge branch 'implement_pokemon' into 'main'
Shedinja T Pose & Little guys sliding

See merge request cable-mc/cobblemon!1047
2024-05-26 11:40:35 +00:00
Frank The FarmerandHiroku dfeeed8796 Shedinja T Pose & Little guys sliding 2024-05-26 11:40:35 +00:00
Paul 0e304a81e8 Changelog, deleted unneeded caves template pool, licenses 2024-05-26 21:33:57 +10:00
Hiroku 03acc21af1 Merge branch 'move-effects' into 'main'
Implemented Action Effects

Closes #814, #773, #840, and #841

See merge request cable-mc/cobblemon!1045
2024-05-26 11:31:20 +00:00
Hiroku 2233e6903b Merge branch '1.5-structure-fixes' into 'main'
added loot to the trapped chest in unstable caves so you can't tell which one...

See merge request cable-mc/cobblemon!1042
2024-05-26 11:25:36 +00:00
Hiroku ac281faa3b Merge branch 'main' into 'move-effects'
# Conflicts:
#   CHANGELOG.md
2024-05-26 11:07:24 +00:00
JazzMcNade 6891272516 Throwing a ball at a player owned pokemon no longer reveals Illusion 2024-05-26 03:02:49 -05:00
Hiroku eba784071a Merge branch 'Primary-Quirks-for-JSON' into 'main'
Primary quirks for json

See merge request cable-mc/cobblemon!1044
2024-05-26 04:10:33 +00:00
Hiroku a4435af9c0 Merge branch 'GildedChestLockAnimFix' into 'main'
GildedChestLockAnimFix

Closes #811

See merge request cable-mc/cobblemon!1043
2024-05-26 04:08:22 +00:00
JazzMcNade 4b0b0f813e Merge branch 'main' into GildedChestLockAnimFix 2024-05-25 22:59:01 -05:00
JazzMcNade c32da0825c Updated Changelog 2024-05-25 22:47:39 -05:00
Tyzillion 803703b6a6 Updated changelog 2024-05-25 23:43:50 -04:00
Tyzillion 668914275e Added sounds to all the powder attacks. Tweaks and fixes. 2024-05-25 23:42:02 -04:00
FrankTheFarmer af90ff6733 Added excludedLabels 2024-05-26 13:07:16 +10:00
FrankTheFarmer c657c2ea68 CHANGELOG with example. 2024-05-26 11:27:58 +10:00
FrankTheFarmer 2f567830d4 Temporary function for bedrock quirks intended for 1.5.1 2024-05-26 11:07:14 +10:00
Tyzillion 569f3c624e Implemented Cotton guard. 2024-05-25 20:38:31 -04:00
Tyzillion f30be418f9 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into move-effects
# Conflicts:
#	CHANGELOG.md
2024-05-25 20:38:13 -04:00
Tyzillion 2c50dc2d0c Implemented Ember, Powder moves, and updated other particles. Implemented Infatuation 2024-05-25 20:34:07 -04:00
Hiroku dde2e9f52a Merge branch 'fix/form-sync' into 'main'
Form Sync on Form Change Fix

See merge request cable-mc/cobblemon!1041
2024-05-26 00:02:40 +00:00
NickandHiroku 50a21d91e8 Form Sync on Form Change Fix 2024-05-26 00:02:40 +00:00
Tyzillion 58f3de13fa Implemented Ember, Powder moves, and updated other particles. 2024-05-25 13:55:44 -04:00
Tyzillion 8d67a3a909 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into move-effects
# Conflicts:
#	CHANGELOG.md
2024-05-25 10:54:51 -04:00
JazzMcNade ad4cb8a290 Gilded Chest no longer animates to the open state on the client when the client fails to open a locked chest 2024-05-24 21:30:54 -05:00
Hiroku d9e5de5f97 Fixed pluralize usage for pose conditions 2024-05-24 22:35:48 +00:00
Hiroku e4a47af06d Merge branch 'sendout-bug' into 'main'
Fixed tiny bug, fixes #867

Closes #867

See merge request cable-mc/cobblemon!1037
2024-05-24 22:03:50 +00:00
tmetcalfe89 5a37693101 you weren't supposed to be in there 2024-05-24 14:41:18 -07:00
tmetcalfe89 6c7cd05f10 updated baits 2024-05-24 14:36:24 -07:00
tmetcalfe89 79627395c2 multiple ivs are now possible 2024-05-24 14:28:42 -07:00
tmetcalfe89 280bcb5bf3 semantics and cleanup 2024-05-24 14:21:21 -07:00
tmetcalfe89 b2bc425fc9 final cleanup 2024-05-24 14:11:59 -07:00
tmetcalfe89 a025d852af a little cleanup for the new format 2024-05-24 13:58:09 -07:00
tmetcalfe89 20076d0d73 moved bait effects to obj vs arr 2024-05-24 12:54:08 -07:00
tmetcalfe89 290a549911 housekeeping 2024-05-24 12:23:07 -07:00
tmetcalfe89 c878fbdade timnote 2024-05-24 12:23:00 -07:00
tmetcalfe89 f9217ac284 readability 2024-05-24 12:22:49 -07:00
tmetcalfe89 cf88c88d1d typo 2024-05-24 11:58:27 -07:00
Apion cc741c0478 Merge branch 'remulched' into 'main'
Cheap Mulch!

See merge request cable-mc/cobblemon!1039
2024-05-24 12:28:03 +00:00
dhi_holo 6887887d6b Cheap Mulch!
Made Mulch cheaper, as outlined by #866
2024-05-24 20:28:45 +10:00
Hiroku cf1100a20e Merge branch 'implement_pokemon' into 'main'
Wailord, Seedot line, and apostrophes in the changelog

See merge request cable-mc/cobblemon!1038
2024-05-24 09:46:27 +00:00
Frank The FarmerandHiroku ebc7b97e4f Wailord, Seedot line, and apostrophes in the changelog 2024-05-24 09:46:26 +00:00
apion d65ce9222a Fixed tiny bug, fixes #867 2024-05-24 05:04:36 -04:00
Apion 706221aead Merge branch 'LangFixes' into 'main'
A Couple of Lang Fixes

See merge request cable-mc/cobblemon!1035
2024-05-24 08:15:24 +00:00
Apion 26bfca893b Merge branch 'main' into 'LangFixes'
# Conflicts:
#   CHANGELOG.md
2024-05-24 07:01:46 +00:00
tmetcalfe89 21df4a71ec licenses 2024-05-23 23:54:32 -07:00
JazzMcNade 5f65f3c20c Updated Changelog 2024-05-24 01:47:23 -05:00
JazzMcNade 1e5239da10 Added English language keys for Rough Skin and Iron Barbs 2024-05-24 01:26:30 -05:00
JazzMcNade 7d8ec3470b Fix for Hyper Cutter / Big Pecks incorrectly stating they prevented accuracy from being lowered 2024-05-24 01:25:58 -05:00
tmetcalfe89 34cb074fa8 moved things to influences 2024-05-23 23:19:41 -07:00
Apion 389d2a0adc Merge branch '1.5-fixes-112313' into 'main'
1.5 fixes 112313

See merge request cable-mc/cobblemon!1034
2024-05-24 04:45:10 +00:00
Paul 7fa0022095 Fixed species sync failing when madmen give pokemon more than 255 level up moves on the same level 2024-05-24 14:21:03 +10:00
Paul 9fde7a7618 Merge remote-tracking branch 'origin/main' into 1.5-fixes-112313 2024-05-24 14:18:04 +10:00
Paul 6c1f7ddd70 Putted the AttackDefenceRatioRequirement back for backcompat 2024-05-24 14:15:45 +10:00
tmetcalfe89 c9639c681c a little cleanup 2024-05-23 18:20:28 -07:00
tmetcalfe89 58f8edb4a2 refactored to use a single influence 2024-05-23 18:19:03 -07:00
tmetcalfe89 4e2d6abbee removed old shiny logic 2024-05-23 17:41:49 -07:00
tmetcalfe89 4b492fca2e shiny bait 2024-05-23 17:38:00 -07:00
tmetcalfe89 92e32b3857 start the move toward influences 2024-05-23 17:25:41 -07:00
tmetcalfe89 d68f62a808 moved nocatch to lang 2024-05-23 16:20:38 -07:00
tmetcalfe89 b8a6bb2650 influence initialization area 2024-05-23 15:46:04 -07:00
tmetcalfe89 55c9c9aa9c it's a surprise tool that will help us later 2024-05-23 15:39:45 -07:00
tmetcalfe89 bec467f32e no reason to be that specific 2024-05-23 15:31:27 -07:00
tmetcalfe89 0f794690ea ocd 2024-05-23 15:27:29 -07:00
Hiroku 241b83cec9 Merge branch 'implement_pokemon' into 'main'
Implementation Stuff

See merge request cable-mc/cobblemon!1027
2024-05-23 21:57:39 +00:00
Frank The FarmerandHiroku f0feb81a16 Implementation Stuff 2024-05-23 21:57:38 +00:00
tmetcalfe89 cb27ae8449 a little simplification by using Kotlin stuff 2024-05-23 14:43:01 -07:00
tmetcalfe89 21033f6527 exception cases simplify tests 2024-05-23 14:40:26 -07:00
tmetcalfe89 e95159f9f9 no nibble? bye 2024-05-23 14:35:05 -07:00
Hiroku 00f4052b75 Merge branch 'MoreFossilMachineFixes' into 'main'
Many Fossil Machine Fixes

See merge request cable-mc/cobblemon!1033
2024-05-23 11:02:07 +00:00
Hiroku 407c24b7c5 Merge branch 'asset-updates' into 'main'
Updated wooloo/dubwool textures and passho berry stuff

Closes #854

See merge request cable-mc/cobblemon!1031
2024-05-23 10:56:56 +00:00
JazzMcNade c8904c3e38 Removed commented code, updated changelog 2024-05-23 00:24:22 -05:00
JazzMcNade 6bfc711129 Update FossilMultiblockStructure.kt 2024-05-22 23:25:46 -05:00
JazzMcNade 3d914f47ee Merge branch 'main' into MoreFossilMachineFixes 2024-05-22 23:25:26 -05:00
JazzMcNade 61891d8fe4 Refactored blockState check to for the blockstate property (credit to Charzard4261) , fixed typo 2024-05-22 21:26:49 -05:00
apion 1b66ee8ce3 Updated wooloo/dubwool textures and passho berry stuff 2024-05-22 03:57:34 -04:00
Tyzillion fc03682140 Implemented quick attack 2024-05-21 22:30:41 -04:00
JazzMcNade 07fbffaa71 Fixed refactor mistake in previous commit. 2024-05-21 20:45:54 -05:00
Tyzillion 5e2f5dadb4 Fixed some errors I ran into. 2024-05-21 19:36:11 -04:00
Tyzillion fc682d1cd1 Implemented action effects for Sand Attack, Protect, Minimize, and Swords Dance. 2024-05-21 19:13:06 -04:00
Tyzillion 7759c91f3b Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into move-effects
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/pokemon/evolution/Evolution.kt
2024-05-21 19:11:04 -04:00
JazzMcNade c13654f60c FossilMultiblockStructure no longer stores a created pokemon, instead the pokemon is created when the pokemon is retrieved from the machine 2024-05-21 02:36:39 -05:00
JazzMcNade 289e9df52b Fixed Restoration Tank over consuming items when interacting with Create blocks 2024-05-20 21:23:13 -05:00
Tyzillion 5dca5a1579 Merge remote-tracking branch 'origin/move-effects' into move-effects 2024-05-20 15:57:38 -04:00
JazzMcNade 3ebad9dc9d Fixed a Create crash when a chute attempts to access the inventory of the top half of the Restoration Tank when the bottom half has been destoryed 2024-05-19 21:27:03 -05:00
apion c93fa02616 Fixed dispatch timeline 2024-05-19 21:13:58 -04:00
apion 5897fff954 Added boost/unboost general particles, removed the particle from growl 2024-05-19 18:05:11 -04:00
apion 2fd2137d78 Added poison (this particle is BROKEN) 2024-05-19 17:22:09 -04:00
apion 42c4d6625e Added poison (this particle is BROKEN) 2024-05-19 17:21:52 -04:00
apion b0381d1685 We do a little refactoring 2024-05-19 16:23:41 -04:00
apion 3b00043b13 Got confusion kinda working, needs tweaks 2024-05-19 02:44:05 -04:00
JazzMcNade 2b403df6ef Removed client side mutations in FossilMultiblockStructure's onUse and enabled the client to enter the function 2024-05-19 01:37:04 -05:00
apion f0d5622bab Added volatile status stuff 2024-05-19 02:20:12 -04:00
apion ec28ca0c45 Added action effect for paralysis + action effect triggers for the cant command 2024-05-19 01:11:41 -04:00
Tyzillion 0d68eedc74 Merge remote-tracking branch 'origin/move-effects' into move-effects 2024-05-18 22:24:56 -04:00
Paul c969e1731b Merge remote-tracking branch 'origin/move-effects' into move-effects 2024-05-19 09:13:29 +10:00
Paul bb77c6d4a9 Fixed particle velocity not respecting drag as much as it should oops sorry 2024-05-19 09:13:17 +10:00
Tyzillion 5dfc90c0bd Merge remote-tracking branch 'origin/move-effects' into move-effects 2024-05-18 18:52:14 -04:00
apion 26a72fe6a9 I hope this works (pt. 2) 2024-05-18 18:46:58 -04:00
apion 4bb9547822 I hope this works 2024-05-18 17:08:09 -04:00
Tyzillion ffe88d75b5 Merge remote-tracking branch 'origin/move-effects' into move-effects 2024-05-18 16:08:32 -04:00
apion d8980fabbe Fix entity_scale var 2024-05-18 14:56:47 -04:00
apion 7598413e93 Merge branch 'refs/heads/particle-rave' into move-effects 2024-05-18 13:55:24 -04:00
apion b0cc4714fc Merge branch 'refs/heads/main' into move-effects
# Conflicts:
#	CHANGELOG.md
2024-05-18 13:53:45 -04:00
JazzMcNade 5aa3f34880 Added block type checks before attempting to update the blockstate from the fossilmultiblockstructure 2024-05-18 12:15:50 -05:00
Slippy 21363405fc console log spam fix for real this time 2024-05-18 12:00:11 -05:00
JazzMcNade 340be0b2a1 Fixed an issue where the lights on the restoration tank would not animate if it was facing east 2024-05-18 11:57:16 -05:00
JazzMcNade 933e29b134 Fix for TankEntityRenderer desync 2024-05-18 11:52:54 -05:00
Tyzillion 41b27e5d75 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into move-effects
# Conflicts:
#	CHANGELOG.md
2024-05-18 10:55:29 -04:00
Tyzillion e80bf0e331 Merge remote-tracking branch 'origin/move-effects' into move-effects 2024-05-18 10:54:32 -04:00
Paul 683aa354ed Fixed particle event triggers spawning particles on the emitter, fixes #814 2024-05-18 11:10:48 +10:00
apion aa19ff2c95 Added entity scale as a particle param 2024-05-17 20:57:10 -04:00
Paul 7d5e790f75 Fixed particle rotations being inverted 2024-05-18 10:55:28 +10:00
Tyzillion 76f17c9760 Merge remote-tracking branch 'origin/move-effects' into move-effects 2024-05-17 18:33:17 -04:00
Paul 3b0d2cd980 Added parametric particle support (motion and rotation), closes #773 2024-05-17 16:43:35 +10:00
Tyzillion f5a3c93039 Merge remote-tracking branch 'origin/move-effects' into move-effects 2024-05-16 21:35:16 -04:00
Paul 00dc501df7 Fixed timed particle ages being a tick too long, fixes #840 2024-05-17 11:28:49 +10:00
Paul 39fee0bcea Fixed particle effects generated from molang not having access to entity_radius and stuff. Fixes #841 2024-05-17 10:26:18 +10:00
Tyzillion 1162689172 Merge remote-tracking branch 'origin/move-effects' into move-effects 2024-05-16 20:08:03 -04:00
Paul b295644070 Cleaned imports 2024-05-17 09:37:29 +10:00
Paul 21df0085e2 Merge remote-tracking branch 'origin/main' into move-effects 2024-05-17 09:08:19 +10:00
Slippy 08c4f4a0ff added loot to the trapped chest in unstable caves so you can't tell which one is trapped if you're using lootr muwahahahaha 2024-05-16 01:57:41 -05:00
Slippy c4a1704a8b unstable cave loot table fixes 2024-05-15 20:35:24 -05:00
Tyzillion 51af37b37a Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into move-effects 2024-05-15 18:09:53 -04:00
Slippy 837aa5acf5 missing loot table in deserted tower 2024-05-14 16:58:55 -05:00
Slippy 969d6ac7fc console spam fix 2024-05-12 09:23:39 -05:00
Slippy 46bb9e1aec console fix 2024-05-11 21:58:50 -05:00
Slippy 9792ff3392 console spam fix 2024-05-11 21:36:29 -05:00
Paul 7ebe0551e1 Fixed build issue caused by unnecessary AW 2024-05-11 18:54:29 +10:00
Paul 6e1d4e0c4d Merge remote-tracking branch 'origin/main' into sega_bass_pro_fishing_2 2024-05-11 18:32:10 +10:00
Tyzillion 978d9e76a1 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into move-effects 2024-05-04 17:48:57 -04:00
Tyzillion 1e067cb7e4 Implemented simple elemental punches. Implemented sweetscent effects, and stat increase and decrease particles. 2024-04-27 22:00:56 -04:00
Paul 7aef56c610 license 2024-04-27 14:28:30 +10:00
Paul 27e1a34097 Merge remote-tracking branch 'origin/main' into sega_bass_pro_fishing_2
# Conflicts:
#	common/src/main/resources/assets/cobblemon/textures/item/poke_balls/beast_ball.png
2024-04-27 12:43:42 +10:00
Tyzillion 2f464f82b1 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into move-effects 2024-04-26 21:01:38 -04:00
Tyzillion 28c0e17a48 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into move-effects 2024-04-25 18:21:18 -04:00
Paul 31136435fe Fixed autogeneration of target and special_attack locators 2024-04-25 10:20:19 +10:00
Paul bdeb83c62c Fixed entity_particles parsing of the effect ID when it's a simple string 2024-04-25 10:20:01 +10:00
Paul 15bf726421 Merge remote-tracking branch 'origin/main' into move-effects 2024-04-24 15:13:23 +10:00
DoctorWafflePhD 7f1889db7b bubblebeam VFX uploaded for review by hiroku (thanks oomfie) 2024-04-23 21:42:16 -05:00
DoctorWafflePhD 548c18bea4 hit particle fix(?) 2024-04-23 21:41:46 -05:00
DoctorWafflePhD d9203bef2a hit particle fix(?) 2024-04-23 21:06:57 -05:00
Tyzillion 5e24799295 move effects are in the works now. :) 2024-04-21 23:05:41 -04:00
Paul b25dc6610e Fixed bobber spawning not working 2024-04-22 11:20:54 +10:00
Paul fa67bba631 Merge remote-tracking branch 'origin/main' into sega_bass_pro_fishing_2
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItems.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonNetwork.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonSounds.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/advancement/CobblemonCriteria.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/CobblemonClient.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/block/DisplayCaseRenderer.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/models/blockbench/pokemon/gen1/MagikarpModel.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/item/group/CobblemonItemGroups.kt
#	common/src/main/resources/assets/cobblemon/bedrock/pokemon/animations/0129_magikarp/magikarp.animation.json
2024-04-22 11:07:52 +10:00
Tyzillion a7243102d5 Evolution particle tweaks and Pokemon now stop moving if evolution is started. 2024-04-21 21:06:47 -04:00
Tyzillion de6a84a75b Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into evolution-effect 2024-04-21 19:37:16 -04:00
Tyzillion be3c35eb43 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into evolution-effect 2024-04-21 11:21:57 -04:00
Lily e6590bef08 moved most baits to the new system 2024-03-19 15:00:59 +00:00
Lily 60b0e70651 updated some baits to use the new system 2024-03-19 14:40:17 +00:00
Lily 0d9f8bbb36 refactored the fishing bait system to allow for multiple effects per item 2024-03-18 22:24:24 -04:00
Tyzillion b4508bf87e More tweaks to evolution particles. 2024-03-10 06:29:27 -04:00
Tyzillion dcb4f52cdb Added sounds to evolution. 2024-03-10 00:32:08 -05:00
Tyzillion 1e9b390f38 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into evolution-effect 2024-03-09 23:34:36 -05:00
Tyzillion 7edd47f995 Evolution tweaks. 2024-03-08 19:12:46 -05:00
Tyzillion dd220c5c85 Merge remote-tracking branch 'origin/evolution-effect' into evolution-effect 2024-03-07 22:02:04 -05:00
Paul bea84a37d4 Fixed a bunch of evolution animation locator issues 2024-03-08 13:01:42 +10:00
Tyzillion ac30da3c15 Merge remote-tracking branch 'origin/evolution-effect' into evolution-effect 2024-03-07 20:58:08 -05:00
Paul 3861835d47 Test command spawns bedrock entity for evo 2024-03-06 21:06:38 +10:00
Sterrezo e75dcd63d1 Merge branch 'sega_bass_pro_fishing_2' of https://gitlab.com/cable-mc/cobblemon into sega_bass_pro_fishing_2 2024-03-04 17:46:49 +01:00
Sterrezo a4fc1442bf Updated fishing sounds 2024-03-04 17:46:06 +01:00
Tyzillion d62fb5c4c7 Merge remote-tracking branch 'origin/evolution-effect' into evolution-effect 2024-03-03 20:58:44 -05:00
Paul ca9dde92cd Merge remote-tracking branch 'origin/main' into evolution-effect 2024-03-04 11:32:33 +10:00
Tyzillion 6ba264ddcb Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into evolution-effect 2024-03-03 20:26:59 -05:00
Tyzillion ab6c088ddf Evolution particle edits. 2024-03-03 17:56:50 -05:00
Tyzillion 18105a8b3f Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into evolution-effect 2024-03-03 17:56:44 -05:00
Tyzillion acb5984a9e Evolution particles electric boogaloo. 2024-03-03 03:50:33 -05:00
Paul 0a3ba8d4fa Fixed generic bedrock animations not being loaded 2024-03-03 17:37:43 +10:00
Paul 15259f0a95 Merge remote-tracking branch 'origin/main' into evolution-effect 2024-03-03 17:32:04 +10:00
Tyzillion 9f66c053e1 Evolution Particles BB. 2024-03-03 02:25:10 -05:00
Plastered_Crab fb85ceacb0 - More adjustments to the reel-in trajectory 2024-03-01 12:40:14 -08:00
jeffw773 7d9644ba51 - tweak Gyarados spawn for testing 2024-02-29 08:58:05 -08:00
Plastered_Crab 39c856b69a - Added in all Ancient Rod predicates so they look right when casting 2024-02-28 15:49:56 -08:00
Plastered_Crab 4a4c375f66 - Added in new test sounds for fishing 2024-02-28 14:56:28 -08:00
Plastered_Crab d22df3e554 - tweaked reel in logic and trajectory 2024-02-27 16:36:09 -08:00
Plastered_Crab ed4dadc3f6 - cleaned up code 2024-02-26 16:21:44 -08:00
Plastered_Crab c2e5e39019 - Tweaked IV bait logic 2024-02-26 16:11:35 -08:00
Plastered_Crab f383e5a52f - Fixed some bait logic 2024-02-26 15:33:28 -08:00
Plastered_Crab 914b7c63c9 - Hooking entities no longer crashes the game :D 2024-02-26 14:37:57 -08:00
Plastered_Crab 5a17a5ed68 - Glow/Sweet Berries can be used as bait + fixes for particles for fishing trails 2024-02-26 12:04:54 -08:00
jeffw773 bb1a46950f - added comment to bobber entity 2024-02-26 06:27:39 -08:00
jeffw773 679d3eae58 - edit to vanilla bait list 2024-02-26 06:08:04 -08:00
jeffw773 a9cd80e496 - remove junk test fishing loot table. Finalize % chance to get Smithing Template odds 2024-02-26 05:38:50 -08:00
apion a96cc307bb Fixed loot table injector 2024-02-25 23:39:06 -05:00
jeffw773 f2b2a282e5 Merge remote-tracking branch 'origin/sega_bass_pro_fishing_2' into sega_bass_pro_fishing_2 2024-02-25 19:56:20 -08:00
jeffw773 5bd7695ce8 - players can define their own bait now 2024-02-25 19:56:05 -08:00
Lily b42387a23f Merge remote-tracking branch 'origin/sega_bass_pro_fishing_2' into sega_bass_pro_fishing_2 2024-02-25 17:40:43 -05:00
Lily 6aa1e6f9ad advancement + tag changes 2024-02-25 17:39:55 -05:00
jeffw773 414fcf8278 - Removed additional crafting recipe 2024-02-24 21:27:26 -08:00
jeffw773 a3c26484a4 - Inejected Pokerod Smithing Template into Treasure Fishing table 2024-02-24 21:25:06 -08:00
Lily fbc8883c96 advancement fixes, removed crafting table recipes, fixed fishing bait not registering 2024-02-23 23:57:50 -05:00
jeffw773 baba3f669d Merge remote-tracking branch 'origin/sega_bass_pro_fishing_2' into sega_bass_pro_fishing_2 2024-02-23 17:45:38 -08:00
jeffw773 2fe098adac - added in recipes for ancient rods 2024-02-23 17:45:23 -08:00
Lily 1c770049b4 Merge remote-tracking branch 'origin/sega_bass_pro_fishing_2' into sega_bass_pro_fishing_2
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/item/interactive/PokerodItem.kt
2024-02-23 20:21:53 -05:00
Lily 71b854f36a ancient rods + advancements 2024-02-23 20:21:26 -05:00
jeffw773 61b944a293 - reworked fishing baits to be datapackable (now you can fish with steak if you wanted) 2024-02-23 16:16:48 -08:00
jeffw773 46ed6d0492 - more bait swapping logic 2024-02-23 08:53:46 -08:00
jeffw773 72058bb56f - Fixed some bait swapping logic 2024-02-22 23:57:05 -08:00
jeffw773 a7b28ed936 - Added some wip fishing sounds 2024-02-22 23:19:25 -08:00
jeffw773 96fce55688 - added some additional fishing spawn pool pokemon 2024-02-22 22:15:27 -08:00
Lily 85daf496d8 changes to pokerod item lang 2024-02-22 23:16:09 -05:00
Plastered_Crab 7b4b498877 - All but 6 berries are implemented for bait (save me) 2024-02-22 15:52:17 -08:00
Plastered_Crab 0ee32c7fd3 - Most of the baits work now (I am so tired) 2024-02-22 14:44:47 -08:00
Plastered_Crab 2c2c1b0cf4 - More groundwork for bait 2024-02-22 09:30:46 -08:00
jeffw773 218edf680b - Ground work for baits having effects on fishing 2024-02-21 21:35:21 -08:00
Plastered_Crab 7598a0dacc - Updated assets for rods 2024-02-21 18:33:25 -08:00
Plastered_Crab 7327a4ddb5 - Bait can be removed and swapped out on the bobber 2024-02-21 11:44:21 -08:00
Plastered_Crab f1d3e7b12b - Bait can now be applied to the fishing bobbers 2024-02-21 11:09:53 -08:00
jeffw773 410ea6be87 - Tweaked line colors and started on bait testing 2024-02-20 23:08:48 -08:00
Plastered_Crab 399dfc33c5 - Added Beast Rod (blue string variant) 2024-02-20 17:05:20 -08:00
Plastered_Crab 26e39bb37d - Altered the trajectory of reeled in pokemon 2024-02-20 15:17:11 -08:00
Plastered_Crab 4ff7df63c0 - Fixed colored fishing lines for cosmetic rods and fixed line not breaking when swapping to another rod 2024-02-20 14:45:39 -08:00
jeffw773 1d1f749d6d - Added cast predicates and JSON features to each remaining cosmetic rod 2024-02-20 01:36:14 -08:00
Plastered_Crab d6e4b9c1a6 - Added other rods to the CobblemonItems list 2024-02-19 17:19:56 -08:00
apion 15d3ee0d49 Data-packable fishing rods kind of 2024-02-19 19:56:22 -05:00
Plastered_Crab de0ed1ab41 - Added crafting table recipes for cosmetic rods 2024-02-19 15:12:47 -08:00
Lily 81d930afae poke rod sync stuff (not fully working atm) 2024-02-19 15:37:30 -05:00
Plastered_Crab b83d75f30f - More Attempts at setting ball used for bobber and line color for rod during registry 2024-02-19 11:42:04 -08:00
Plastered_Crab 5814c1a5d6 - Attempts to get each ball rendered correctly for each rod (still needs work) 2024-02-19 10:10:57 -08:00
jeffw773 a45fec50a4 - Added fancy cosmetic Rods for each pokeball type 2024-02-18 22:51:05 -08:00
jeffw773 576b6ea767 - implemented test Magikarp animations + added test hook to Bobber 2024-02-18 18:05:16 -08:00
jeffw773 792287cb5a - added new Magikarp models/textures/animations 2024-02-18 14:44:11 -08:00
Lily 42ad084dbe pokerod assets 2024-02-18 16:24:20 -05:00
jeffw773 b9bd61f3dc - fishing line connects to bobber much better now 2024-02-18 01:04:41 -08:00
jeffw773 c6d6f98d03 - fishing line connects to bobber much better now 2024-02-18 01:03:28 -08:00
jeffw773 abbd17faf7 - Pokeball Bobber now casts with more controlled random pitch/yaw amounts and also rotates upright when it hits water 2024-02-18 00:29:21 -08:00
jeffw773 c159137a70 Merge remote-tracking branch 'origin/display_case' into sega_bass_pro_fishing_2 2024-02-17 22:54:08 -08:00
jeffw773 03628631f7 - Pokeball bobber now rotates upwards when bobbing in water 2024-02-17 11:50:37 -08:00
jeffw773 e5e61df4b2 Merge remote-tracking branch 'origin/sega_bass_pro_fishing_2' into sega_bass_pro_fishing_2
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/entity/PokeBobberEntityRenderer.kt
2024-02-17 10:31:26 -08:00
Lily f07c4bf5b1 refactored fishing bobber variable names to be more readable instead of the single-letter names that mc gives us 2024-02-17 11:31:38 -05:00
Lily 0e3fd520f0 fixed naming to be more consistent with vanilla textures 2024-02-17 11:23:08 -05:00
jeffw773 935f3100d2 - Pokeball Bobber now slows down fast in water (due to water friction) and has a random spin applied when casting 2024-02-16 22:12:07 -08:00
Lily 8b32764ce3 pokerod smithing template assets 2024-02-17 00:14:26 -05:00
Lily 3e5a57ea20 pokerod now slowly stops spinning 2024-02-16 23:35:34 -05:00
Plastered_Crab 6ee94aa55b - Pokeball Bobber now spins when cast and stops spinning on land, needs fixed for water surface 2024-02-16 17:01:07 -08:00
Lily bd5185e69e Merge remote-tracking branch 'origin/sega_bass_pro_fishing_2' into sega_bass_pro_fishing_2 2024-02-16 19:31:40 -05:00
Lily 5e8df4609b started bobber rendering 2024-02-16 19:31:21 -05:00
Plastered_Crab 289d1474a6 - Beeeeg Whiscash has been added to fishing spawn chances 2024-02-16 14:54:52 -08:00
Plastered_Crab e4604c0d13 - Wooper (Heart variant) is now fully implemented for fishing 2024-02-16 14:24:31 -08:00
Tyzillion d4f91fa945 Wooper Heart Fix. <3 2024-02-16 16:53:53 -05:00
Plastered_Crab 5c52a9a31a - Started Implemented Wooper (heart form) to be added to the fishing chance pool 2024-02-16 13:19:57 -08:00
Plastered_Crab db2efcb48e - added new spawn conditional for lureLevel. Variants of Karp can now only have a chance to appear at certain Lure levels 2024-02-16 10:32:55 -08:00
Plastered_Crab 3bcbe2fb45 - Incorporated Pokerod Treasure loot table and player will physically reel in pokemon under 200 lbs 2024-02-16 09:32:01 -08:00
Paul c7202cc869 Fixed magikarp jump pattern spawn file 2024-02-16 11:34:40 +10:00
Lily 269e18786b Merge remote-tracking branch 'origin/sega_bass_pro_fishing_2' into sega_bass_pro_fishing_2 2024-02-15 19:40:14 -05:00
Lily bd79a4ecfb pokerod smithing template (needs some more assets but should be functional) 2024-02-15 19:39:49 -05:00
Plastered_Crab 3c3a8b1af9 - Pokerod now cannot stack and has same durability as vanilla fishing rod 2024-02-15 16:07:54 -08:00
Lily 11e2ed34e5 Merge branch 'main' into sega_bass_pro_fishing_2
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonEntities.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItems.kt
#	fabric/src/main/kotlin/com/cobblemon/mod/fabric/client/CobblemonFabricClient.kt
#	forge/src/main/kotlin/com/cobblemon/mod/forge/client/CobblemonForgeClient.kt
2024-02-15 18:54:42 -05:00
Plastered_Crab 1500d6cdf1 - All Magikarp Jump variants implemented and new item loot tables started 2024-02-15 14:59:18 -08:00
Plastered_Crab acbbe2a1ba - Start to JUMP variants spawning properly when fishing 2024-02-14 16:42:58 -08:00
Plastered_Crab 0fe6bc918c - Adding Magikarp JUMP addon variants into Base mod 2024-02-14 14:57:27 -08:00
Plastered_Crab d399b3e29a - Magikarps now get reeled in at good distances in front of player 2024-02-14 14:20:32 -08:00
Plastered_Crab acc6f119e1 - Magikarps getting reeled in now land in front of the player instead of behind the player (for battle reasons) 2024-02-13 16:45:22 -08:00
Plastered_Crab 932a2c375c - Begin different reel-in trajectories for fishing up Magikarps 2024-02-13 14:46:42 -08:00
Nick 142eebe9e6 Apply model predicates, starting with Pokerod 2024-02-09 22:07:43 -08:00
jeffw773 36f19c9180 - Xp is rewarded to player for fishing up Pokemon 2024-02-09 21:15:10 -08:00
jeffw773 beb8fed6bd - Pokeball Bobber now renders correctly when cast out with Pokerod
- Luck of the Sea and Lure get read correctly from the rod and factor into the spawn bucket chosen
2024-02-09 14:17:13 -08:00
jeffw773 142284a17c - Pokemon rarity is altered by Luck of the Sea 2024-02-08 21:18:41 -08:00
jeffw773 f1ea86362a - Fixed fishing spawns being able to check if sky is actually visible.
- added isOcean to biomes to Magikarp
2024-02-07 18:28:42 -08:00
Paul ec3361ad91 Added basic fishing spawn detail for Magikarp and use the world pool for fishing because ehhh 2024-02-03 12:16:53 +10:00
Paul ca7c36ce15 Added trigger spawning with a fishing implementation 2024-02-03 12:13:38 +10:00
jeffw773 f7c73728d7 - Added some newer spawning stuff and added battle initiation after fished up mon 2024-02-02 16:38:15 -08:00
jeffw773 1992a23edc Merge branch 'main' into sega_bass_pro_fishing_2 2024-02-02 13:39:02 -08:00
jeffw773 dad28135cc - Brief changes to particle count based on rarity reeled in 2024-01-18 14:32:43 -08:00
jeffw773 e41d13dd53 - Pokerod now uses config values for rarity buckets and access biome spawn pools 2024-01-14 23:02:33 -08:00
jeffw773 64cae53d82 - Pokerod foundation laid out for having chance to spawn Pokemon instead of getting item as well as rarity chance started 2024-01-14 00:11:12 -08:00
dhi_holo c9bf0bd8d7 Merge branch 'sega_bass_pro_fishing_2' of https://gitlab.com/cable-mc/cobblemon into sega_bass_pro_fishing_2 2024-01-14 13:36:53 +10:00
dhi_holo 97d2047aee It's a Tool, It's a Rod
makes pokerod be held correctly, and adds second model file for the cast variation, when that is implemented.
2024-01-14 13:36:04 +10:00
jeffw773 811f8abed7 - Added override for model of pokerod when cast 2024-01-13 19:27:22 -08:00
jeffw773 ab02a54f28 - Finally the Pokerod is starting to work! (it is backwards tho) 2024-01-13 19:08:36 -08:00
jeffw773 6e0c1aefcc - More testing fishing code for pokerod 2024-01-13 13:55:02 -08:00
jeffw773 e887b6c43c - Some code for fishing testing again 2024-01-13 12:16:38 -08:00
jeffw773 25172c2bc9 - Some code for fishing testing 2024-01-13 11:23:38 -08:00
jeffw773 929b90173a - Some code for fishing testing 2024-01-12 11:58:02 -08:00
jeffw773 70a65f6ffe - Testing code 2024-01-12 07:39:21 -08:00
jeffw773 db7d3f01d8 - Starting implementation on fishing 2024-01-12 07:39:20 -08:00
944 changed files with 16332 additions and 1360 deletions
+91 -21
View File
@@ -1,44 +1,114 @@
# Changelog
## [1.5.1](#1-5-1)
## [1.5.2 (May 27th, 2024)](#1-5-2)
### Fixes
- Fixed Wooloo and Dubwool dyeing making your game start dying. Only if you do it on a server.
- Fixed Gimmighoul causing PCs and parties to look glitched out, also only in a server.
- Fixed players being able to dye other people's sheep Pokémon.
## [1.5.1 (May 27th, 2024)](#1-5-1)
### Additions
- Added unique send out particles for Cherish, Dream, Beast, and Ancient Origin balls.
- Made Wooloo and Dubwool dye-able like sheep
- Made Wooloo and Dubwool dye-able like sheep. So cute!
- Added stat up and down particles.
- Most status effects now have particles! These include: Paralysis, Poison, Sleep, Confusion, Infatuation.
#### Move Particle Effects
- Confusion
- Cotton Guard
- Growl
- Ice Punch
- Fire Punch
- Thunder Punch
- Minimize
- Quick Attack
- Protect
- Swords Dance
- Sand Attack
- Poison Powder
- Sleep Powder
- Stun Spore
- Powder
- Rage Powder
- Spore
- Thunder Wave
### Changes
- Sounds for Relic Coin Sacks have been correctly renamed. Relic Coin Pouches received new sounds for breaking and placing.
- Readjusted Petilil portraits
- Changed "dimensions" spawn condition to check with dimension IDs instead of effects, so custom dimension IDs can be used.
- Readjusted Petilil portraits so they fit a bit better.
- Improved handling of Pokémon taken from the Restoration Tank block to be a bit more stable.
- Made Mulch cheaper to craft.
### Fixes
- Fixed a bug in which adding organic material to the fossil tank via right click was adding the full count of the stack currently in hand
- Fixed a bug in which adding organic material to the restoration tank via right click was adding the full count of the stack currently in hand - but only taking 1 of the item.
- Fixed a niche issue where some properties of entities were not initialized correctly, causing Pokémon that appeared to be level 1 until you battle them.
- Fixed Fossilized Drake being missing from the Fossils item tag.
- Fixed Gilded Chest block entity not being cleared on block break
- Fixed sherd brokenness on Forge/Supplementaries incompat
- Fixed Fossil Compartment crash with Jade / WAILA forks
- Fixed PCStore#resize not allowing PC boxes size reduction.
- Fixed Gilded Chest block entity not being cleared on block break, creating spooky ghost blocks. Old ones can be fixed by placing something like a furnace where it was, then breaking the furnace.
- Fixed sherd brokenness on Forge.
- Fixed Supplementaries incompatibility.
- Fixed Fossil Compartment crash with Jade / WAILA forks.
- Fixed pasture block PC lookups when the player is offline.
- Fixed an untranslated battle message that occurs when using a move that just ran out of PP (e.g. Fire Blast that just got spited mid-turn).
- Fixed held items being eaten even when the held item evolutions are already unlocked.
- Fixed Hisuian Decidueye not being Grass/Fighting
- Fixed both Decidueye forms learning both Triple Arrows and Spirit Shackle
- Fixed Pineco being unable to evolve into Shulker Forretress
- Fixed Kabutops T-posing when underwater. It still does not have proper swimming animations yet though.
- Fixed Hisuian Decidueye not being Grass/Fighting.
- Fixed both Decidueye forms learning both Triple Arrows and Spirit Shackle.
- Fixed Pineco being unable to evolve into Shulker Forretress.
- Fixed Kabutops T-posing when underwater. It doesn't have proper swimming animations yet, though.
- Fixed Pidgey's missing walk animation.
- Fixed recoil evo condition.
- Fixed issue where poser debug tools didnt work on JSON posers
- Fixed issue where gilded chests dont close when going far away
- Fixed Cyndaquil's hidden flames clipping if it was swimming.
- Fixed Chimecho and Chingling being unable to spawn near bells. They are meant to!
- Fixed Tyrantrum and Wailord Party Overlay models peeking through the chat box. It was kinda funny though.
- Fixed hitbox sizes for Seedot, Nuzleaf, and Shiftry.
- Fixed Budew and Lechonk sliding if they walked for too long.
- Fixed Shedinja T-posing in battle.
- Fixed recoil evolution condition not working, making things like Basculegion unobtainable.
- Fixed issue where poser debug tools didn't work on JSON posers.
- Fixed issue where gilded chests don't close when going far away.
- Fixed issue where the restoration tank's renderer was reading old data, making it appear wrong.
- Fixed issue where the lights on the restoration tank would not animate if it was facing east. Very specific.
- Fixed client crash with the fossil machine when updating block state on a chunk that is unloaded in the client. I don't understand this but the devs are sure that all of those are real words.
- Fixed Restoration Tank crash with Create upon the tank block's destruction.
- Fixed Restoration Tank over consuming items when interacting with Create blocks.
- Fixed addons that add very many moves to a learn-set causing disappearing Pokémon (visually) issues on servers.
- Fixed Hyper Cutter and Big Pecks incorrectly stating that it prevented accuracy from being lowered in battle.
- Fixed missing messages for Rough Skin and Iron Barbs in battle.
- Fixed a bug where sometimes Pokémon sendouts wouldn't create an entity, or the entity would spawn at 0 0 0 which is not a good place for a Pokémon to be. Or any of us, really.
- Fixed issue in which a locked gilded chest would animate to the open state when the client fails to open it, such as when it is locked.
- Fixed a bug where aspects of a form would not be properly reflected on form changes (eg. Normal -> Hisui).
- Fixed generic battle effect sounds not sounding the way they were intended to.
- Fixed particle effects often not having access to some specific entity functions from MoLang.
- Fixed particles sometimes lasting a single tick too long, causing (very quick) visual glitches.
- Fixed particle rotations being inverted.
- Fixed particle events not spawning at the instigating particle's location.
- Fixed a bunch of spam during world generation.
- Fixed a bug in which throwing a Poké Ball at a player owned Pokémon with the ability Illusion would reveal its true species. Hilarious meta strategy.
- Fixed root-part animations not working for JSON posed Pokémon. You didn't notice this but if we didn't fix this in this update then if you use Quick Attack a lot you'd have seen a whole lot of [this](https://cdn.discordapp.com/attachments/1076993968803434627/1242660506783715369/Minecraft__1.20.1_-_Singleplayer_2024-05-21_22-08-17.mp4?ex=66549408&is=66534288&hm=ff95ee293eb15634fd63e6546534ea279540a1c892605e8d561593ca2c5600c5&) which is damn funny but very unintended.
### Developer
- Changed SpawnAction#complete to return a nullable generic R (the spawn action result) instead of a boolean. More information this way.
- Added an event when a fossil is revived, with or without a player.
- Changed SpawnAction#complete to return a nullable generic R (the spawn action result) instead of a boolean. Provides more information this way.
- Added an event that fires when a fossil is revived, with or without a player.
- Added IVS and EVS property extractors.
- Fixed PCStore#resize not allowing PC boxes size reduction.
### Data Pack & Resource Pack Creators
- Added support for MoLang conditions for quirks and poses.
- Changed the AttackDefenceRatio requirement to StatCompare and StatEqual.
- Changed the AttackDefenceRatio requirement to StatCompare and StatEqual. There is some backwards compatibility for AttackDefenceRatio, though.
- Changed "dimensions" spawn condition to check with dimension IDs instead of effects, so custom dimension IDs can be used.
- Added parametric motion and rotation support to particle effects.
- Added entity_scale as a molang var for particles (likely only applicable to Pokemon)
- Added support for primary quirk animations using the following format:
```JSON
{
"quirks": [
"q.bedrock_primary_quirk('<pokemon>', '<animation>', <minSeconds>, <maxSeconds>, <loopTimes>, '<excludedLabels>', q.curve('<waveFunction>'))"
]
}
```
### Localization
- Updated translations for:
- Simplified and Traditional Chinese.
- Spanish.
## [1.5.0 - The Ruins and Revival Update (May 12th, 2024)](#1-5-0)
#### "You're telling me that Mojang has added archaeology to the game? Hmm... that gives me an idea."
@@ -49,8 +49,13 @@ import com.cobblemon.mod.common.api.scheduling.ServerRealTimeTaskTracker
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.CobblemonSpawningProspector
import com.cobblemon.mod.common.api.spawning.WorldSlice
import com.cobblemon.mod.common.api.spawning.context.AreaContextResolver
import com.cobblemon.mod.common.api.spawning.context.AreaSpawningContext
import com.cobblemon.mod.common.api.spawning.context.calculators.AreaSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.AreaSpawningInput
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningProspector
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.starter.StarterHandler
import com.cobblemon.mod.common.api.storage.PokemonStoreManager
import com.cobblemon.mod.common.api.storage.adapter.conversions.ReforgedConversion
@@ -104,13 +109,7 @@ import com.cobblemon.mod.common.pokemon.properties.tags.PokemonFlagProperty
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
import com.cobblemon.mod.common.trade.TradeManager
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.ifDedicatedServer
import com.cobblemon.mod.common.util.isLaterVersion
import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.util.removeAmountIf
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.world.feature.CobblemonPlacedFeatures
import com.cobblemon.mod.common.world.feature.ore.CobblemonOrePlacedFeatures
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
@@ -125,6 +124,7 @@ import net.minecraft.item.Items
import net.minecraft.item.NameTagItem
import net.minecraft.registry.RegistryKey
import net.minecraft.util.WorldSavePath
import net.minecraft.util.math.BlockPos
import net.minecraft.world.World
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.entity.boat.CobblemonBoatEntity
import com.cobblemon.mod.common.entity.boat.CobblemonChestBoatEntity
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.entity.generic.GenericBedrockEntity
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -66,6 +67,15 @@ object CobblemonEntities : PlatformRegistry<Registry<EntityType<*>>, RegistryKey
.build(CHEST_BOAT_KEY.toString())
)
@JvmField
val POKE_BOBBER_KEY = cobblemonResource("poke_bobber")
@JvmField
val POKE_BOBBER: EntityType<PokeRodFishingBobberEntity> = this.create(
POKE_BOBBER_KEY.path,
EntityType.Builder.create(::PokeRodFishingBobberEntity, SpawnGroup.MISC).setDimensions(0.5F, 0.5F).maxTrackingRange(10)
.build(POKE_BOBBER_KEY.toString())
)
@JvmField
val GENERIC_BEDROCK_ENTITY_KEY = cobblemonResource("generic_bedrock")
@JvmField
@@ -14,6 +14,8 @@ import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.text.blue
import com.cobblemon.mod.common.api.text.gray
import com.cobblemon.mod.common.block.BerryBlock
import com.cobblemon.mod.common.block.MintBlock
import com.cobblemon.mod.common.block.MintBlock.MintType
@@ -45,6 +47,7 @@ import net.minecraft.registry.Registries
import net.minecraft.registry.Registry
import net.minecraft.registry.RegistryKey
import net.minecraft.registry.RegistryKeys
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import net.minecraft.world.World
@@ -932,12 +935,121 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
@JvmField
val BLACK_TUMBLESTONE_BLOCK = blockItem("black_tumblestone_block", CobblemonBlocks.BLACK_TUMBLESTONE_BLOCK)
@JvmField
val POKEROD_SMITHING_TEMPLATE = create("pokerod_smithing_template", SmithingTemplateItem(
Text.translatable("item.minecraft.fishing_rod").blue(),
Text.translatable("item.cobblemon.smithing_template.pokerod.ingredients").blue(),
Text.translatable("upgrade.cobblemon.pokerod").gray(),
Text.translatable("item.cobblemon.smithing_template.pokerod.base_slot_description"),
Text.translatable("item.cobblemon.smithing_template.pokerod.additions_slot_description"),
listOf(cobblemonResource("item/empty_slot_fishing_rod")),
listOf(cobblemonResource("item/empty_slot_pokeball"))
))
@JvmField
val AUTOMATON_ARMOR_TRIM_SMITHING_TEMPLATE: SmithingTemplateItem = this.create(
"automaton_armor_trim_smithing_template",
SmithingTemplateItem.of(CobblemonArmorTrims.AUTOMATON)
)
val pokeRods = mutableListOf<PokerodItem>()
@JvmField
val POKE_ROD = pokerodItem(cobblemonResource("poke_rod"))
@JvmField
val CITRINE_ROD = pokerodItem(cobblemonResource("citrine_rod"))
@JvmField
val VERDANT_ROD = pokerodItem(cobblemonResource("verdant_rod"))
@JvmField
val AZURE_ROD = pokerodItem(cobblemonResource("azure_rod"))
@JvmField
val ROSEATE_ROD = pokerodItem(cobblemonResource("roseate_rod"))
@JvmField
val SLATE_ROD = pokerodItem(cobblemonResource("slate_rod"))
@JvmField
val PREMIER_ROD = pokerodItem(cobblemonResource("premier_rod"))
@JvmField
val GREAT_ROD = pokerodItem(cobblemonResource("great_rod"))
@JvmField
val ULTRA_ROD = pokerodItem(cobblemonResource("ultra_rod"))
@JvmField
val SAFARI_ROD = pokerodItem(cobblemonResource("safari_rod"))
@JvmField
val FAST_ROD = pokerodItem(cobblemonResource("fast_rod"))
@JvmField
val LEVEL_ROD = pokerodItem(cobblemonResource("level_rod"))
@JvmField
val LURE_ROD = pokerodItem(cobblemonResource("lure_rod"))
@JvmField
val HEAVY_ROD = pokerodItem(cobblemonResource("heavy_rod"))
@JvmField
val LOVE_ROD = pokerodItem(cobblemonResource("love_rod"))
@JvmField
val FRIEND_ROD = pokerodItem(cobblemonResource("friend_rod"))
@JvmField
val MOON_ROD = pokerodItem(cobblemonResource("moon_rod"))
@JvmField
val SPORT_ROD = pokerodItem(cobblemonResource("sport_rod"))
@JvmField
val PARK_ROD = pokerodItem(cobblemonResource("park_rod"))
@JvmField
val NET_ROD = pokerodItem(cobblemonResource("net_rod"))
@JvmField
val DIVE_ROD = pokerodItem(cobblemonResource("dive_rod"))
@JvmField
val NEST_ROD = pokerodItem(cobblemonResource("nest_rod"))
@JvmField
val REPEAT_ROD = pokerodItem(cobblemonResource("repeat_rod"))
@JvmField
val TIMER_ROD = pokerodItem(cobblemonResource("timer_rod"))
@JvmField
val LUXURY_ROD = pokerodItem(cobblemonResource("luxury_rod"))
@JvmField
val DUSK_ROD = pokerodItem(cobblemonResource("dusk_rod"))
@JvmField
val HEAL_ROD = pokerodItem(cobblemonResource("heal_rod"))
@JvmField
val QUICK_ROD = pokerodItem(cobblemonResource("quick_rod"))
@JvmField
val DREAM_ROD = pokerodItem(cobblemonResource("dream_rod"))
@JvmField
val BEAST_ROD = pokerodItem(cobblemonResource("beast_rod"))
@JvmField
val MASTER_ROD = pokerodItem(cobblemonResource("master_rod"))
@JvmField
val CHERISH_ROD = pokerodItem(cobblemonResource("cherish_rod"))
@JvmField
val ANCIENT_POKE_ROD = pokerodItem(cobblemonResource("ancient_poke_rod"))
@JvmField
val ANCIENT_CITRINE_ROD = pokerodItem(cobblemonResource("ancient_citrine_rod"))
@JvmField
val ANCIENT_VERDANT_ROD = pokerodItem(cobblemonResource("ancient_verdant_rod"))
@JvmField
val ANCIENT_AZURE_ROD = pokerodItem(cobblemonResource("ancient_azure_rod"))
@JvmField
val ANCIENT_ROSEATE_ROD = pokerodItem(cobblemonResource("ancient_roseate_rod"))
@JvmField
val ANCIENT_SLATE_ROD = pokerodItem(cobblemonResource("ancient_slate_rod"))
@JvmField
val ANCIENT_IVORY_ROD = pokerodItem(cobblemonResource("ancient_ivory_rod"))
@JvmField
val ANCIENT_GREAT_ROD = pokerodItem(cobblemonResource("ancient_great_rod"))
@JvmField
val ANCIENT_ULTRA_ROD = pokerodItem(cobblemonResource("ancient_ultra_rod"))
@JvmField
val ANCIENT_FEATHER_ROD = pokerodItem(cobblemonResource("ancient_feather_rod"))
@JvmField
val ANCIENT_WING_ROD = pokerodItem(cobblemonResource("ancient_wing_rod"))
@JvmField
val ANCIENT_JET_ROD = pokerodItem(cobblemonResource("ancient_jet_rod"))
@JvmField
val ANCIENT_HEAVY_ROD = pokerodItem(cobblemonResource("ancient_heavy_rod"))
@JvmField
val ANCIENT_LEADEN_ROD = pokerodItem(cobblemonResource("ancient_leaden_rod"))
@JvmField
val ANCIENT_GIGATON_ROD = pokerodItem(cobblemonResource("ancient_gigaton_rod"))
@JvmField
val ANCIENT_ORIGIN_ROD = pokerodItem(cobblemonResource("ancient_origin_rod"))
// Misc
@JvmField
val POKEMON_MODEL = this.create("pokemon_model", PokemonItem())
@@ -1003,6 +1115,13 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, RegistryKey<Registry<It
private fun candyItem(name: String, calculator: CandyItem.Calculator): CandyItem = this.create(name, CandyItem(calculator))
private fun pokerodItem(pokeRodId: Identifier): PokerodItem {
val settings = Item.Settings().maxCount(1).maxDamage(Items.FISHING_ROD.maxDamage)
val item = create(pokeRodId.path, PokerodItem(pokeRodId, settings))
pokeRods.add(item)
return item
}
private fun heldItem(name: String, remappedName: String? = null): CobblemonItem = create(
name,
CobblemonItem(Item.Settings()).also {
@@ -62,15 +62,7 @@ import com.cobblemon.mod.common.net.messages.client.battle.*
import com.cobblemon.mod.common.net.messages.client.callback.OpenMoveCallbackPacket
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyCallbackPacket
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyMoveCallbackPacket
import com.cobblemon.mod.common.net.messages.client.data.AbilityRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.BerryRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.GlobalSpeciesFeatureSyncPacket
import com.cobblemon.mod.common.net.messages.client.data.MovesRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.PropertiesCompletionRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.SpeciesFeatureAssignmentSyncPacket
import com.cobblemon.mod.common.net.messages.client.data.SpeciesRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.data.StandardSpeciesFeatureSyncPacket
import com.cobblemon.mod.common.net.messages.client.data.UnlockReloadPacket
import com.cobblemon.mod.common.net.messages.client.data.*
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
@@ -235,6 +227,7 @@ object CobblemonNetwork : NetworkManager {
this.createClientBound(TradeableUpdatePacket.ID, TradeableUpdatePacket::decode, PokemonUpdatePacketHandler())
this.createClientBound(SpeciesFeatureUpdatePacket.ID, SpeciesFeatureUpdatePacket::decode, PokemonUpdatePacketHandler())
this.createClientBound(OriginalTrainerUpdatePacket.ID, OriginalTrainerUpdatePacket::decode, PokemonUpdatePacketHandler())
this.createClientBound(FormUpdatePacket.ID, FormUpdatePacket::decode, PokemonUpdatePacketHandler())
// Evolution start
this.createClientBound(AddEvolutionPacket.ID, AddEvolutionPacket::decode, PokemonUpdatePacketHandler())
@@ -307,6 +300,7 @@ object CobblemonNetwork : NetworkManager {
this.createClientBound(SpeciesFeatureAssignmentSyncPacket.ID, SpeciesFeatureAssignmentSyncPacket::decode, DataRegistrySyncPacketHandler())
this.createClientBound(NaturalMaterialRegistrySyncPacket.ID, NaturalMaterialRegistrySyncPacket::decode, DataRegistrySyncPacketHandler())
this.createClientBound(FossilRegistrySyncPacket.ID, FossilRegistrySyncPacket::decode, DataRegistrySyncPacketHandler())
this.createClientBound(PokeRodRegistrySyncPacket.ID, PokeRodRegistrySyncPacket::decode, DataRegistrySyncPacketHandler())
// Effects
this.createClientBound(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler)
@@ -134,6 +134,15 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, RegistryKey<Regi
@JvmField
val RELIC_COIN_POUCH_PLACE = this.create("relic_coin_pouch.place")
val COIN_POUCH_PLACE = this.create("coin_pouch.place")
@JvmField
val FISHING_NOTIFICATION = this.create("fishing.notification")
@JvmField
val FISHING_SPLASH_BIG = this.create("fishing.splash_big")
@JvmField
val FISHING_SPLASH_SMALL = this.create("fishing.splash_small")
@JvmField
val TUMBLESTONE_BREAK = this.create("tumblestone.break")
@JvmField
@@ -32,7 +32,14 @@ object CobblemonTradeOffers {
* @return The resulting list.
*/
fun tradeOffersFor(profession: VillagerProfession): List<VillagerTradeOffer> = when (profession) {
VillagerProfession.FARMER -> listOf(VillagerTradeOffer(VillagerProfession.FARMER, 3, listOf(TradeOffers.SellItemFactory(CobblemonItems.VIVICHOKE_SEEDS, 24, 1, 1, 6))))
VillagerProfession.FARMER -> listOf(
VillagerTradeOffer(VillagerProfession.FARMER, 3, listOf(TradeOffers.SellItemFactory(CobblemonItems.VIVICHOKE_SEEDS, 24, 1, 1, 6)))
)
VillagerProfession.FISHERMAN -> listOf(
VillagerTradeOffer(VillagerProfession.FISHERMAN, 5, listOf(
TradeOffers.SellItemFactory(CobblemonItems.POKEROD_SMITHING_TEMPLATE, 12, 3, 30)
))
)
else -> emptyList()
}
@@ -113,6 +113,13 @@ object CobblemonCriteria {
)
)
val CAST_POKE_ROD = this.create(
SimpleCriterionTrigger(
cobblemonResource("cast_poke_rod"),
CastPokeRodCriterionCondition::class.java
)
)
// Advancement criteria for [grow_tumblestone.json]
val PLANT_TUMBLESTONE = this.create(
SimpleCriterionTrigger(
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.advancement.criterion
import com.google.gson.JsonObject
import net.minecraft.predicate.entity.LootContextPredicate
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
class CastPokeRodCriterionCondition(id: Identifier, predicate: LootContextPredicate): SimpleCriterionCondition<Boolean>(id, predicate) {
var hasBait: Boolean = false
override fun toJson(json: JsonObject) {
json.addProperty("hasBait", hasBait)
}
override fun fromJson(json: JsonObject) {
hasBait = json.get("hasBait").asBoolean
}
override fun matches(player: ServerPlayerEntity, context: Boolean): Boolean {
return hasBait == context
}
}
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.fishing
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.util.Identifier
data class FishingBait(
val item: Identifier,
val effects: Map<Identifier, Effect>,
) {
data class Effect(
val nature: Identifier?,
val ivs: List<Identifier>?,
val useAll: Boolean?,
val gender: Identifier?,
val tera: Identifier?,
val chance: Double = 0.0,
val value: Double = 0.0
)
object Effects {
val NATURE = cobblemonResource("nature")
val IV = cobblemonResource("iv")
val EV = cobblemonResource("ev")
val BITE_TIME = cobblemonResource("bite_time")
val GENDER_CHANCE = cobblemonResource("gender_chance")
val LEVEL_RAISE = cobblemonResource("level_raise")
val TERA = cobblemonResource("tera")
val SHINY_REROLL = cobblemonResource("shiny_reroll")
val HIDDEN_ABILITY_CHANCE = cobblemonResource("ha_chance")
val POKEMON_CHANCE = cobblemonResource("pokemon_chance")
}
}
@@ -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.fishing
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.fishing.FishingBaitRegistrySyncPacket
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.item.ItemStack
import net.minecraft.registry.Registries
import net.minecraft.resource.ResourceType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
object FishingBaits : JsonDataRegistry<FishingBait>{
override val id = cobblemonResource("fishing_baits")
override val type = ResourceType.SERVER_DATA
override val observable = SimpleObservable<FishingBaits>()
override val typeToken: TypeToken<FishingBait> = TypeToken.get(FishingBait::class.java)
override val resourcePath = "fishing_baits"
override val gson: Gson = GsonBuilder()
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
.setPrettyPrinting()
.create()
private val itemMap = mutableMapOf<Identifier, FishingBait>()
override fun sync(player: ServerPlayerEntity) {
FishingBaitRegistrySyncPacket(this.itemMap.values.toList()).sendToPlayer(player)
}
override fun reload(data: Map<Identifier, FishingBait>) {
itemMap.clear()
data.forEach { id, bait ->
itemMap[id] = bait
}
}
fun getFromItemStack(stack: ItemStack): FishingBait? {
return itemMap[Registries.ITEM.getId(stack.item)]
}
fun isFishingBait(stack: ItemStack) = itemMap.containsKey(Registries.ITEM.getId(stack.item))
//
//
// fun getBaitSubcategory(item: ItemStack): String? {
// val itemId = Registries.ITEM.getId(item.item)
// if (itemId in itemMap.keys) {
// return itemMap[itemId]?.subcategory
// }
// val tag = tagMap.keys.firstOrNull { it.fits(item.item, Registries.ITEM) }
// if (tag != null) {
// return tagMap[tag]?.subcategory
// }
// return null
// }
//
// fun getBaitSuccessChance(item: ItemStack): Double? {
// val itemId = Registries.ITEM.getId(item.item)
// if (itemId in itemMap.keys) {
// return itemMap[itemId]?.chance
// }
// val tag = tagMap.keys.firstOrNull { it.fits(item.item, Registries.ITEM) }
// if (tag != null) {
// return tagMap[tag]?.chance
// }
// return null
// }
//
// fun getBaitEffectValue(item: ItemStack): Double? {
// val itemId = Registries.ITEM.getId(item.item)
// if (itemId in itemMap.keys) {
// return itemMap[itemId]?.value
// }
// val tag = tagMap.keys.firstOrNull { it.fits(item.item, Registries.ITEM) }
// if (tag != null) {
// return tagMap[tag]?.value
// }
// return null
// }
//
//
// fun getBaitNote(item: ItemStack): String? {
// val itemId = Registries.ITEM.getId(item.item)
// if (itemId in itemMap.keys) {
// return itemMap[itemId]?.note
// }
// val tag = tagMap.keys.firstOrNull { it.fits(item.item, Registries.ITEM) }
// if (tag != null) {
// return tagMap[tag]?.note
// }
// return null
// }
}
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.fishing
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.pokeball.PokeBall
import net.minecraft.network.PacketByteBuf
import net.minecraft.util.Identifier
/**
* Base poke rod object
* It is intended that there is one poke rod object initialized for a given poke rod type.
*
* @property name the poke rod registry name
* @property pokeBallId The [Identifier] of the pokeball that is used as the bobber for this rod
* @property lineColor list of [RGB] values that apply to the fishing line of the Pokérod
*/
data class PokeRod(
val pokeBallId: Identifier,
//Hex string of color
val lineColor: String,
var name: Identifier?
) {
internal fun encode(buffer: PacketByteBuf) {
buffer.writeIdentifier(name)
buffer.writeIdentifier(pokeBallId)
buffer.writeString(lineColor)
}
companion object {
internal fun decode(buffer: PacketByteBuf): PokeRod {
val name = buffer.readIdentifier()
val pokeBallId = buffer.readIdentifier()
val lineColor = buffer.readString()
return PokeRod(pokeBallId, lineColor, name)
}
}
fun getPokeBall(): PokeBall? {
return PokeBalls.getPokeBall(pokeBallId)
}
}
@@ -0,0 +1,65 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.fishing
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.PokeRodRegistrySyncPacket
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resource.ResourceType
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
/**
* The data registry for [PokeRod]s.
* All the pokerod fields are guaranteed to exist
*/
object PokeRods : JsonDataRegistry<PokeRod> {
override val id = cobblemonResource("pokerods")
override val type = ResourceType.SERVER_DATA
override val observable = SimpleObservable<PokeRods>()
// ToDo once datapack pokerod is implemented add required adapters here
override val gson: Gson = GsonBuilder()
.disableHtmlEscaping()
.registerTypeAdapter(Identifier::class.java, IdentifierAdapter)
.setPrettyPrinting()
.create()
override val typeToken: TypeToken<PokeRod> = TypeToken.get(PokeRod::class.java)
override val resourcePath = "pokerods"
private val rods = mutableMapOf<Identifier, PokeRod>()
override fun reload(data: Map<Identifier, PokeRod>) {
{
data.forEach {
it.value.name = it.key
rods[it.key] = it.value
}
}
rods.putAll(data)
this.observable.emit(this)
}
override fun sync(player: ServerPlayerEntity) {
PokeRodRegistrySyncPacket(rods.values).sendToPlayer(player)
}
/**
* Gets a Pokerod from registry name.
* @return the pokerod object if found otherwise null.
*/
fun getPokeRod(name : Identifier): PokeRod? = rods[name]
}
@@ -10,9 +10,7 @@ package com.cobblemon.mod.common.api.moves.animations
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.struct.VariableStruct
import com.cobblemon.mod.common.api.moves.animations.keyframes.ActionEffectKeyframe
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolveBoolean
import java.util.concurrent.CompletableFuture
@@ -43,6 +41,9 @@ class ActionEffectTimeline(
// .toList copy because I'm paranoid about iterators being trying to share between identical effects playing
chainKeyframes(context, timeline.toList().iterator(), finalFuture)
finalFuture
}.exceptionallyCompose {
it.printStackTrace()
CompletableFuture.completedFuture(Unit)
}
}
@@ -55,6 +56,7 @@ class ActionEffectTimeline(
keyframe.play(context)
.thenRun { context.currentKeyframes.remove(keyframe) }
.thenApply { chainKeyframes(context, iterator, finalFuture) }
.exceptionally { finalFuture.completeExceptionally(it) }
}
}
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.moves.animations.keyframes
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.EntityProvider
@@ -40,7 +39,7 @@ class EntityParticlesActionEffectKeyframe : ConditionalActionEffectKeyframe(), E
.flatMap { prov -> prov.entities.filter { test(context, it, isUser = prov is UsersProvider) } }
val effectIdentifier = try {
effect?.asExpressionLike()?.resolveString(context.runtime)
effect?.asExpressionLike()?.resolveString(context.runtime)?.takeIf { it != "0.0" } ?: effect
} catch (e: Exception) {
effect
}?.asIdentifierDefaultingNamespace() ?: return skip()
@@ -52,11 +52,9 @@ abstract class MultiblockBlock(properties: Settings) : BlockWithEntity(propertie
if(hand == Hand.OFF_HAND) {
return ActionResult.SUCCESS
}
if (!world.isClient) {
val entity = world.getBlockEntity(pos) as MultiblockEntity?
if (entity?.multiblockStructure != null) {
return entity.multiblockStructure!!.onUse(state, world, pos, player, hand, hit)
}
val entity = world.getBlockEntity(pos) as MultiblockEntity?
if (entity?.multiblockStructure != null) {
return entity.multiblockStructure!!.onUse(state, world, pos, player, hand, hit)
}
return super.onUse(state, world, pos, player, hand, hit)
}
@@ -168,7 +168,7 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
createDefault("fast_ball", BaseStatModifier(Stats.SPEED, { it >= 100 }, 4F))
createDefault("level_ball", CatchRateModifiers.LEVEL)
// ToDo we will need fishing context here once fishing is implemented for a multiplier
createDefault("lure_ball", CatchRateModifiers.typeBoosting(2F, ElementalTypes.WATER))
createDefault("lure_ball", CatchRateModifiers.LURE)
createDefault("heavy_ball", CatchRateModifiers.WEIGHT_BASED)
createDefault("love_ball", CatchRateModifiers.LOVE)
createDefault("friend_ball", effects = listOf(CaptureEffects.friendshipSetter(150)))
@@ -121,6 +121,18 @@ object CatchRateModifiers {
1F
}
/**
* Used by [PokeBalls.LURE_BALL].
* Checks if the entity has an aspect for being spawned from fishing.
* If yes boosts the catch rate by *4
*/
val LURE = WorldStateModifier { _, entity ->
if (entity.pokemon.aspects.contains("fished")) // todo make aspect about this
4f
else
1F
}
/**
* Used by [PokeBalls.HEAVY_BALL].
* The base implementation of the heavy ball modifier mechanics.
@@ -47,6 +47,10 @@ fun interface PokemonPropertyExtractor {
val NICKNAME = add { pokemon, properties -> properties.nickname = pokemon.nickname }
@JvmField
val STATUS = add { pokemon, properties -> properties.status = pokemon.status?.status?.showdownName }
@JvmField
val IVS = add { pokemon, properties -> properties.ivs = pokemon.ivs }
@JvmField
val EVS = add { pokemon, properties -> properties.evs = pokemon.evs }
@JvmField
val ILLUSION = mutableListOf(SPECIES, FORM, ASPECTS, GENDER, NICKNAME, SHINY)
@@ -101,7 +101,7 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
repeat(times = buffer.readSizedInt(IntSize.U_BYTE)) {
val level = buffer.readSizedInt(IntSize.U_SHORT)
val moves = mutableListOf<MoveTemplate>()
repeat(times = buffer.readSizedInt(IntSize.U_BYTE)) {
repeat(times = buffer.readSizedInt(IntSize.U_SHORT)) {
Moves.getByNumericalId(buffer.readInt())?.let(moves::add)
}
levelUpMoves[level] = moves
@@ -112,7 +112,7 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
buffer.writeSizedInt(IntSize.U_BYTE, levelUpMoves.size)
for ((level, moves) in levelUpMoves) {
buffer.writeSizedInt(IntSize.U_SHORT, level)
buffer.writeSizedInt(IntSize.U_BYTE, moves.size)
buffer.writeSizedInt(IntSize.U_SHORT, moves.size)
for (move in moves) {
buffer.writeInt(move.num)
}
@@ -51,8 +51,8 @@ enum class Stats(override val identifier: Identifier, override val displayName:
/** Gets the [Stat] from the respective Showdown id. */
fun getStat(statKey: String) = when(statKey) {
"atk" -> ATTACK
"def" -> DEFENCE
"atk", "Attack" -> ATTACK // Hyper Cutter states the full stat name "Attack"
"def", "Defense" -> DEFENCE // Big Pecks states the full stat name "Defense"
"spa" -> SPECIAL_ATTACK
"spd" -> SPECIAL_DEFENCE
"spe" -> SPEED
@@ -8,6 +8,9 @@
package com.cobblemon.mod.common.api.pokemon.status
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import net.minecraft.util.Identifier
/**
@@ -20,4 +23,6 @@ open class Status(
val showdownName: String = "",
val applyMessage: String,
val removeMessage: String
)
) {
fun getActionEffect() = ActionEffects.actionEffects[name]
}
@@ -9,21 +9,29 @@
package com.cobblemon.mod.common.api.pokemon.status
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
import com.cobblemon.mod.common.pokemon.status.statuses.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.SleepStatus
import com.cobblemon.mod.common.pokemon.status.VolatileStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.SleepStatus
import com.cobblemon.mod.common.pokemon.status.statuses.nonpersistent.ConfuseStatus
import com.cobblemon.mod.common.pokemon.status.statuses.nonpersistent.AttractStatus
import net.minecraft.util.Identifier
/**
* Main API point for Statuses
* Get or register Statuses
*
* NOTE: May seem weird to have so many things called volatile statuses but the package is called nonpersistent.
* Its because volatile is a reserved keyword in Java. Cant use it in a package name
*
* @author Deltric
*/
object Statuses {
private val persistentStatuses = mutableListOf<Status>()
private val volatileStatuses = mutableListOf<Status>()
private val allStatuses = mutableListOf<Status>()
val POISON = registerStatus(PoisonStatus())
@@ -32,13 +40,23 @@ object Statuses {
val SLEEP = registerStatus(SleepStatus())
val FROZEN = registerStatus(FrozenStatus())
val BURN = registerStatus(BurnStatus())
val ATTRACT = registerStatus(AttractStatus())
val CONFUSE = registerStatus(ConfuseStatus())
fun <T: Status> registerStatus(status: T) : T {
if (status is PersistentStatus) {
persistentStatuses.add(status)
}
else if (status is VolatileStatus) {
volatileStatuses.add(status)
}
allStatuses.add(status)
return status
}
fun getStatus(name: Identifier) = allStatuses.find { status -> status.name == name }
fun getStatus(showdownName: String) = allStatuses.find { it.showdownName == showdownName }
fun getPersistentStatuses() = allStatuses.filterIsInstance<PersistentStatus>()
fun getPersistentStatuses() = persistentStatuses
}
@@ -79,15 +79,15 @@ class ParticleEvent(
particleEffect?.let { effect ->
val bedrockParticleEffect = BedrockParticleEffectRepository.getEffect(effect.effect) ?: return@let
val rootMatrix = when (effect.type) {
EventParticleEffect.EventParticleType.EMITTER -> MatrixWrapper().updatePosition(storm.matrixWrapper.getOrigin())
EventParticleEffect.EventParticleType.EMITTER_BOUND -> storm.matrixWrapper
EventParticleEffect.EventParticleType.EMITTER,// -> MatrixWrapper().updatePosition(storm.matrixWrapper.getOrigin())
EventParticleEffect.EventParticleType.EMITTER_BOUND,// -> storm.matrixWrapper
EventParticleEffect.EventParticleType.PARTICLE,
EventParticleEffect.EventParticleType.PARTICLE_WITH_VELOCITY -> (particle?.let { Vec3d(it.getX(), it.getY(), it.getZ()) } ?: Vec3d(storm.getX(), storm.getY(), storm.getZ())).let { MatrixWrapper().updatePosition(it) }
}
val sourceVelocity = when (effect.type) {
EventParticleEffect.EventParticleType.EMITTER -> storm.sourceVelocity().let { { it } }
EventParticleEffect.EventParticleType.EMITTER_BOUND -> storm.sourceVelocity
EventParticleEffect.EventParticleType.EMITTER,// -> storm.sourceVelocity().let { { it } }
EventParticleEffect.EventParticleType.EMITTER_BOUND,// -> storm.sourceVelocity
EventParticleEffect.EventParticleType.PARTICLE -> { { Vec3d.ZERO } }
EventParticleEffect.EventParticleType.PARTICLE_WITH_VELOCITY -> (particle?.let { Vec3d(it.getVelocityX(), it.getVelocityY(), it.getVelocityZ()) } ?: Vec3d.ZERO).let { { it } }
}
@@ -15,10 +15,12 @@ import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.codec.CodecMapped
import com.cobblemon.mod.common.api.data.ArbitrarilyMappedSerializableCompanion
import com.cobblemon.mod.common.client.particle.ParticleStorm
import com.cobblemon.mod.common.client.render.SnowstormParticle
import com.cobblemon.mod.common.util.codec.EXPRESSION_CODEC
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.math.geometry.transformDirection
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveVec3d
import com.mojang.serialization.Codec
import com.mojang.serialization.DynamicOps
import com.mojang.serialization.codecs.PrimitiveCodec
@@ -35,20 +37,79 @@ interface ParticleMotion : CodecMapped {
init {
registerSubtype(ParticleMotionType.DYNAMIC, DynamicParticleMotion::class.java, DynamicParticleMotion.CODEC)
registerSubtype(ParticleMotionType.STATIC, StaticParticleMotion::class.java, StaticParticleMotion.CODEC)
registerSubtype(ParticleMotionType.PARAMETRIC, ParametricParticleMotion::class.java, ParametricParticleMotion.CODEC)
}
}
val type: ParticleMotionType
fun getInitialVelocity(runtime: MoLangRuntime, storm: ParticleStorm, particlePos: Vec3d, emitterPos: Vec3d): Vec3d
fun getAcceleration(runtime: MoLangRuntime, velocity: Vec3d): Vec3d
fun getVelocity(runtime: MoLangRuntime, particle: SnowstormParticle, velocity: Vec3d): Vec3d
fun getParticleDirection(runtime: MoLangRuntime, storm: ParticleStorm, velocity: Vec3d, minSpeed: Float): Vec3d
}
enum class ParticleMotionType {
DYNAMIC,
PARAMETRIC, // TODO figure out how that even works
PARAMETRIC,
STATIC
}
class ParametricParticleMotion(
var offset: Triple<Expression, Expression, Expression> = Triple(
NumberExpression(0.0),
NumberExpression(0.0),
NumberExpression(0.0)
),
var direction: Triple<Expression, Expression, Expression> = Triple(
NumberExpression(0.0),
NumberExpression(0.0),
NumberExpression(0.0)
)
) : ParticleMotion {
companion object {
val CODEC: Codec<ParametricParticleMotion> = RecordCodecBuilder.create { instance ->
instance.group(
PrimitiveCodec.STRING.fieldOf("type").forGetter { it.type.name },
EXPRESSION_CODEC.listOf().fieldOf("offset").forGetter { listOf(it.offset.first, it.offset.second, it.offset.third) },
EXPRESSION_CODEC.listOf().fieldOf("direction").forGetter { listOf(it.direction.first, it.direction.second, it.direction.third) }
).apply(instance) { _, offset, direction -> ParametricParticleMotion(Triple(offset[0], offset[1], offset[2]), Triple(direction[0], direction[1], direction[2])) }
}
}
override val type = ParticleMotionType.PARAMETRIC
override fun getInitialVelocity(runtime: MoLangRuntime, storm: ParticleStorm, particlePos: Vec3d, emitterPos: Vec3d) = Vec3d.ZERO
override fun getVelocity(runtime: MoLangRuntime, particle: SnowstormParticle, velocity: Vec3d): Vec3d {
val stormPosition = Vec3d(particle.storm.getX(), particle.storm.getY(), particle.storm.getZ())
val offset = runtime.resolveVec3d(this.offset)
val particlePosition = Vec3d(particle.getX(), particle.getY(), particle.getZ())
val desiredPosition = stormPosition.add(offset)
return desiredPosition.subtract(particlePosition)
}
override fun getParticleDirection(runtime: MoLangRuntime, storm: ParticleStorm, velocity: Vec3d, minSpeed: Float) = runtime.resolveVec3d(direction).normalize()
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
override fun readFromBuffer(buffer: PacketByteBuf) {
offset = Triple(
MoLang.createParser(buffer.readString()).parseExpression(),
MoLang.createParser(buffer.readString()).parseExpression(),
MoLang.createParser(buffer.readString()).parseExpression()
)
direction = Triple(
MoLang.createParser(buffer.readString()).parseExpression(),
MoLang.createParser(buffer.readString()).parseExpression(),
MoLang.createParser(buffer.readString()).parseExpression()
)
}
override fun writeToBuffer(buffer: PacketByteBuf) {
buffer.writeString(offset.first.getString())
buffer.writeString(offset.second.getString())
buffer.writeString(offset.third.getString())
buffer.writeString(direction.first.getString())
buffer.writeString(direction.second.getString())
buffer.writeString(direction.third.getString())
}
}
class DynamicParticleMotion(
var direction: ParticleMotionDirection = InwardsMotionDirection(),
var speed: Expression = NumberExpression(0.0),
@@ -79,14 +140,23 @@ class DynamicParticleMotion(
return direction.getDirectionVector(runtime, storm, emitterPos, particlePos).normalize().multiply(runtime.resolveDouble(speed))
}
override fun getAcceleration(runtime: MoLangRuntime, velocity: Vec3d): Vec3d {
return Vec3d(
override fun getVelocity(runtime: MoLangRuntime, particle: SnowstormParticle, velocity: Vec3d): Vec3d {
val acceleration = Vec3d(
runtime.resolveDouble(acceleration.first),
runtime.resolveDouble(acceleration.second),
runtime.resolveDouble(acceleration.third)
).subtract(velocity.multiply(runtime.resolveDouble(drag)))
)
.subtract(velocity.multiply(20 * runtime.resolveDouble(drag)))
.multiply(1 / 20.0).multiply(1 / 20.0) // blocks per second per second -> blocks per tick per tick
return Vec3d(
velocity.x + acceleration.x,
velocity.y + acceleration.y,
velocity.z + acceleration.z
)
}
override fun getParticleDirection(runtime: MoLangRuntime, storm: ParticleStorm, velocity: Vec3d, minSpeed: Float) = velocity.normalize()
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
override fun readFromBuffer(buffer: PacketByteBuf) {
@@ -234,7 +304,8 @@ class StaticParticleMotion : ParticleMotion {
override val type = ParticleMotionType.STATIC
override fun getInitialVelocity(runtime: MoLangRuntime, storm: ParticleStorm, particlePos: Vec3d, emitterPos: Vec3d) = Vec3d.ZERO
override fun getAcceleration(runtime: MoLangRuntime, velocity: Vec3d) = Vec3d.ZERO
override fun getVelocity(runtime: MoLangRuntime, particle: SnowstormParticle, velocity: Vec3d) = velocity
override fun getParticleDirection(runtime: MoLangRuntime, storm: ParticleStorm, velocity: Vec3d, minSpeed: Float) = velocity.normalize()
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
override fun readFromBuffer(buffer: PacketByteBuf) {}
override fun writeToBuffer(buffer: PacketByteBuf) {}
@@ -32,6 +32,7 @@ interface ParticleRotation : CodecMapped {
) {
init {
registerSubtype(ParticleRotationType.DYNAMIC, DynamicParticleRotation::class.java, DynamicParticleRotation.CODEC)
registerSubtype(ParticleRotationType.PARAMETRIC, ParametricParticleRotation::class.java, ParametricParticleRotation.CODEC)
}
}
@@ -39,7 +40,34 @@ interface ParticleRotation : CodecMapped {
fun getInitialRotation(runtime: MoLangRuntime): Double
fun getInitialAngularVelocity(runtime: MoLangRuntime): Double
fun getAngularAcceleration(runtime: MoLangRuntime, angularVelocity: Double): Double
fun getAngularVelocity(runtime: MoLangRuntime, angle: Double, angularVelocity: Double): Double
}
class ParametricParticleRotation(var expression: Expression = NumberExpression(0.0)): ParticleRotation {
companion object {
val CODEC: Codec<ParametricParticleRotation> = RecordCodecBuilder.create { instance ->
instance.group(
PrimitiveCodec.STRING.fieldOf("type").forGetter { it.type.name },
EXPRESSION_CODEC.fieldOf("expression").forGetter { it.expression }
).apply(instance) { _, expression ->
ParametricParticleRotation(expression)
}
}
}
override val type = ParticleRotationType.PARAMETRIC
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
override fun getInitialRotation(runtime: MoLangRuntime) = runtime.resolveDouble(expression)
override fun getInitialAngularVelocity(runtime: MoLangRuntime) = 0.0
override fun getAngularVelocity(runtime: MoLangRuntime, angle: Double, angularVelocity: Double) = runtime.resolveDouble(expression) - angle
override fun readFromBuffer(buffer: PacketByteBuf) {
expression = MoLang.createParser(buffer.readString()).parseExpression()
}
override fun writeToBuffer(buffer: PacketByteBuf) {
buffer.writeString(expression.getString())
}
}
class DynamicParticleRotation(
@@ -66,15 +94,15 @@ class DynamicParticleRotation(
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
override fun getInitialRotation(runtime: MoLangRuntime) = runtime.resolveDouble(startRotation)
override fun getInitialAngularVelocity(runtime: MoLangRuntime) = runtime.resolveDouble(speed) / 20
override fun getAngularAcceleration(runtime: MoLangRuntime, angularVelocity: Double): Double {
override fun getAngularVelocity(runtime: MoLangRuntime, angle: Double, angularVelocity: Double): Double {
val acceleration = runtime.resolveDouble(acceleration)
val nextVelocity = angularVelocity + acceleration
val nextVelocity = angularVelocity * 20 + acceleration
val drag = nextVelocity * runtime.resolveDouble(drag)
return if (abs(drag) > abs(nextVelocity)) {
return angularVelocity + (if (abs(drag) > abs(nextVelocity)) {
0.0
} else {
nextVelocity - drag - angularVelocity
}
nextVelocity - drag - angularVelocity * 20
})
}
override fun readFromBuffer(buffer: PacketByteBuf) {
@@ -12,11 +12,13 @@ import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.entity.Despawner
import com.cobblemon.mod.common.api.spawning.condition.AreaSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.BasicSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.FishingSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.GroundedSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SubmergedSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SurfaceSpawningCondition
import com.cobblemon.mod.common.api.spawning.context.AreaContextResolver
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.api.spawning.context.GroundedSpawningContext
import com.cobblemon.mod.common.api.spawning.context.LavafloorSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SeafloorSpawningContext
@@ -32,6 +34,7 @@ import com.cobblemon.mod.common.api.spawning.context.calculators.SurfaceSpawning
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawner
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.preset.BasicSpawnDetailPreset
import com.cobblemon.mod.common.api.spawning.preset.BestSpawnerConfig
@@ -83,6 +86,7 @@ object BestSpawner {
var config = BestSpawnerConfig()
val spawnerManagers = mutableListOf<SpawnerManager>(CobblemonWorldSpawnerManager)
var defaultPokemonDespawner: Despawner<PokemonEntity> = CobblemonAgingDespawner(getAgeTicks = { it.ticksLived })
lateinit var fishingSpawner: FishingSpawner
fun loadConfig() {
LOGGER.info("Starting the Best Spawner...")
@@ -91,6 +95,7 @@ object BestSpawner {
SpawningCondition.register(SubmergedSpawningCondition.NAME, SubmergedSpawningCondition::class.java)
SpawningCondition.register(GroundedSpawningCondition.NAME, GroundedSpawningCondition::class.java)
SpawningCondition.register(SurfaceSpawningCondition.NAME, SurfaceSpawningCondition::class.java)
SpawningCondition.register(FishingSpawningCondition.NAME, FishingSpawningCondition::class.java)
LOGGER.info("Loaded ${SpawningCondition.conditionTypes.size} spawning condition types.")
SpawningContextCalculator.register(GroundedSpawningContextCalculator)
@@ -104,6 +109,7 @@ object BestSpawner {
SpawningContext.register(name = "lavafloor", clazz = LavafloorSpawningContext::class.java, defaultCondition = GroundedSpawningCondition.NAME)
SpawningContext.register(name = "submerged", clazz = SubmergedSpawningContext::class.java, defaultCondition = SubmergedSpawningCondition.NAME)
SpawningContext.register(name = "surface", clazz = SurfaceSpawningContext::class.java, defaultCondition = SurfaceSpawningCondition.NAME)
SpawningContext.register(name = "fishing", clazz = FishingSpawningContext::class.java, defaultCondition = FishingSpawningCondition.NAME)
LOGGER.info("Loaded ${SpawningContext.contexts.size} spawning context types.")
@@ -118,5 +124,7 @@ object BestSpawner {
fun onServerStarted() {
spawnerManagers.forEach(SpawnerManager::onServerStarted)
fishingSpawner = FishingSpawner()
fishingSpawner.onServerStarted()
}
}
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.block.Block
import net.minecraft.item.Item
/**
* A spawning condition that applies to [FishingSpawningContext]s.
*
* @author Hiroku
* @since February 3rd, 2024
*/
class FishingSpawningCondition: SpawningCondition<FishingSpawningContext>() {
override fun contextClass() = FishingSpawningContext::class.java
var rod: RegistryLikeCondition<Item>? = null
var neededNearbyBlocks: MutableList<RegistryLikeCondition<Block>>? = null
override fun fits(ctx: FishingSpawningContext): Boolean {
if (!super.fits(ctx)) {
return false
} else if (rod != null && !rod!!.fits(ctx.rodItem ?: return false, ctx.world.itemRegistry)) {
return false
} else if (neededNearbyBlocks != null && neededNearbyBlocks!!.none { cond -> ctx.nearbyBlockTypes.any { cond.fits(it, ctx.blockRegistry) } }) {
return false
} else {
return true
}
}
companion object {
const val NAME = "fishing"
}
}
@@ -11,12 +11,16 @@ package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.MoonPhaseRange
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.util.Merger
import com.cobblemon.mod.common.util.math.orMax
import com.cobblemon.mod.common.util.math.orMin
import com.mojang.datafixers.util.Either
import net.minecraft.enchantment.EnchantmentHelper
import net.minecraft.item.ItemStack
import net.minecraft.registry.Registries
import net.minecraft.registry.tag.TagKey
import net.minecraft.util.Identifier
import net.minecraft.world.biome.Biome
@@ -56,6 +60,9 @@ abstract class SpawningCondition<T : SpawningContext> {
var isThundering: Boolean? = null
var timeRange: TimeRange? = null
var structures: MutableList<Either<Identifier, TagKey<Structure>>>? = null
var lureLevel: Int? = null
var bait: String? = null
var rodType: String? = null
@Transient
var appendages = mutableListOf<AppendageCondition>()
@@ -108,8 +115,32 @@ abstract class SpawningCondition<T : SpawningContext> {
}
) {
return false
} else if (lureLevel != null && ctx is FishingSpawningContext) { // check for the lureLevel of the rod
val pokerodStack = (ctx as FishingSpawningContext).rodStack
if (EnchantmentHelper.getLure(pokerodStack) < lureLevel!!)
return false
} else if (bait != null && ctx is FishingSpawningContext) { // check for the bait on the bobber
val pokerodItem = (ctx as FishingSpawningContext).rodItem
if (Registries.ITEM.getId(pokerodItem?.bait?.item).path != bait)
return false
} else if (rodType != null && ctx is FishingSpawningContext) { // check for the type of pokerod being used
val pokerodItem = (ctx as FishingSpawningContext).rodItem
if (pokerodItem?.pokeRodId?.path != rodType)
return false
}
/*else if (ctx is FishingSpawningContext && (ctx as FishingSpawningContext).rodItem != null) { // check if the bait attracts certain EV yields
val pokerodItem = (ctx as FishingSpawningContext).rodItem
// todo check if the EV yield of the berry matches the bait EV attract maybe?
if (// todo if bait EV yield != EV yield of pokemon consideration //Registries.ITEM.getId(pokerodItem?.bait?.item).path == )
return false
}*/
return true
}
@@ -76,4 +76,51 @@ interface AreaContextResolver {
return contexts
}
// fun resolveFishing(
// spawner: Spawner,
// contextCalculators: List<AreaSpawningContextCalculator<*>>,
// slice: WorldSlice
// ): List<AreaSpawningContext> {
// var pos = BlockPos.Mutable(1, 2, 3)
// val input = AreaSpawningInput(spawner, pos, slice)
// val contexts = mutableListOf<AreaSpawningContext>()
//
// var x = slice.baseX
// var y = slice.baseY
// var z = slice.baseZ
//
// while (x < slice.baseX + slice.length) {
// while (y < slice.baseY + slice.height) {
// while (z < slice.baseZ + slice.width) {
// pos.set(x, y, z)
// val vec = pos.toVec3d()
// if (slice.nearbyEntityPositions.none { it.isInRange(vec, 50.0) && it != slice.cause.entity }) {
// val fittedContextCalculator = contextCalculators
// .firstOrNull { calc -> calc.fits(input) && input.spawner.influences.none { !it.isAllowedPosition(input.world, input.position, calc) } }
// if (fittedContextCalculator != null) {
// val context = fittedContextCalculator.calculate(input)
// if (context != null) {
// contexts.add(context)
// // The position BlockPos has been used in a context, editing the same one
// // will cause entities to spawn at the wrong location (buried in walls, usually)
// // I made it so that our context calculators specifically take a copy of the
// // BlockPos but it'd still be exposed in custom contexts so fixing it here too.
// pos = BlockPos.Mutable(1, 2, 3)
// input.position = pos
// }
// }
// }
// z++
// }
// y++
// z = slice.baseZ
// }
// x++
// y = slice.baseY
// z = slice.baseZ
// }
//
// return contexts
// }
}
@@ -0,0 +1,46 @@
/*
* 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.spawning.context
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawnCause
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.util.getBlockStates
import com.cobblemon.mod.common.util.toVec3d
import net.minecraft.block.Block
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Box
/**
* A spawning context that was generated for a fishing action.
*
* @author Hiroku
* @since February 3rd, 2024
*/
class FishingSpawningContext(
cause: FishingSpawnCause,
world: ServerWorld,
pos: BlockPos,
influences: MutableList<SpawningInfluence>
) : TriggerSpawningContext(
cause = cause,
world = world,
position = pos,
light = world.getLightLevel(pos),
skyLight = world.getLightLevel(pos.up()),
canSeeSky = world.isSkyVisibleAllowingSea(pos),
influences = influences
) {
val nearbyBlocks = world.getBlockStates(Box.of(pos.toVec3d(), 10.0, 10.0, 10.0))
val nearbyBlockTypes: List<Block> by lazy { nearbyBlocks.mapNotNull { it.block }.distinct() }
val rodStack = cause.rodStack
val rodItem = cause.rodItem
val baitStack = cause.baitStack
val baitItem = cause.baitItem
}
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.context
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.math.BlockPos
/**
* A spawning context relating to triggered spawning. Uses a single-point structure cache which is
* the only contribution of this implementation.
*
* @author Hiroku
* @since February 3rd, 2024
*/
open class TriggerSpawningContext(
override val cause: SpawnCause,
override val world: ServerWorld,
override val position: BlockPos,
override val light: Int,
override val skyLight: Int,
override val canSeeSky: Boolean,
override val influences: MutableList<SpawningInfluence>
) : SpawningContext() {
val triggerStructureCache = StructureChunkCache()
override fun getStructureCache(pos: BlockPos) = triggerStructureCache
}
@@ -13,8 +13,10 @@ import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.feature.FishedFeatureHandler
import com.cobblemon.mod.common.pokemon.feature.SeasonFeatureHandler
import com.cobblemon.mod.common.util.weightedSelection
import net.minecraft.world.gen.GenerationStep.Feature
/**
* A [SpawnAction] that will spawn a single [PokemonEntity].
@@ -45,6 +47,8 @@ class PokemonSpawnAction(
null
}?.createStack(ctx)
val entity = props.createEntity(ctx.world)
// add fishing aspect if fished with a pokerod
FishedFeatureHandler.updateFished(entity.pokemon, ctx.spawner.name == "fishing")
SeasonFeatureHandler.updateSeason(entity.pokemon, Cobblemon.seasonResolver(ctx.world, ctx.position))
if (heldItem != null) {
entity.pokemon.swapHeldItem(heldItem)
@@ -0,0 +1,123 @@
/*
* 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.spawning.fishing
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.fishing.FishingBait
import com.cobblemon.mod.common.api.fishing.FishingBaits
import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.IVs
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.util.Identifier
import kotlin.math.min
import kotlin.random.Random.Default.nextInt
class FishingBaitInfluence : SpawningInfluence {
override fun affectAction(action: SpawnAction<*>) {
if (action !is PokemonSpawnAction || action.ctx !is FishingSpawningContext) return
val bait = FishingBaits.getFromItemStack(action.ctx.baitStack) ?: return
bait.effects.entries.forEach { (type, effect) ->
if (Math.random() > effect.chance) return
when (type) {
FishingBait.Effects.SHINY_REROLL -> shinyReroll(action, effect)
FishingBait.Effects.NATURE -> alterNatureAttempt(action, effect)
FishingBait.Effects.IV -> alterIVAttempt(action, effect)
FishingBait.Effects.GENDER_CHANCE -> alterGenderAttempt(action, effect)
FishingBait.Effects.LEVEL_RAISE -> alterLevelAttempt(action, effect)
FishingBait.Effects.TERA -> alterTeraAttempt(action, effect)
FishingBait.Effects.HIDDEN_ABILITY_CHANCE -> alterHAAttempt(action)
}
}
}
private fun shinyReroll(action: PokemonSpawnAction, effect: FishingBait.Effect) {
if (action.props.shiny != null) return
val shinyOdds = Cobblemon.config.shinyRate.toInt()
val randomNumber = nextInt(0, shinyOdds + 1)
if (randomNumber <= (effect.value).toInt()) {
action.props.shiny = true
}
}
private fun alterNatureAttempt(action: PokemonSpawnAction, effect: FishingBait.Effect) {
if (action.props.nature != null) return
if (effect.nature == null) return
// TIMNOTE: This replaces the static lists. It's less performant because it's being reviewed every time,
// but also it's not something that goes off too often.
val possibleNatures = Natures.all().filter { it.increasedStat?.identifier == effect.nature }
if (possibleNatures.isEmpty()) return
val takenNature = possibleNatures.random()
action.props.nature = takenNature.name.namespace
}
private fun alterIVAttempt(action: PokemonSpawnAction, effect: FishingBait.Effect) {
val ivs = effect.ivs ?: return
val useAll = effect.useAll ?: return
if (action.props.ivs == null) {
action.props.ivs = IVs.createRandomIVs()
}
if (useAll) {
ivs.forEach { iv -> boostIv(iv, action, effect) }
} else {
boostIv(ivs.random(), action, effect)
}
}
private fun boostIv(iv: Identifier, action: PokemonSpawnAction, effect: FishingBait.Effect) {
val targetedStat = Stats.getStat(iv.path)
val currentIv = action.props.ivs!![targetedStat] ?: 0
action.props.ivs!![targetedStat] = min(currentIv + effect.value.toInt(), IVs.MAX_VALUE)
}
private fun alterGenderAttempt(action: PokemonSpawnAction, effect: FishingBait.Effect) {
if (effect.gender == null) return
if (action.props.gender != null) return
when (effect.gender) {
cobblemonResource("male") -> if (action.props.gender != Gender.MALE) action.props.gender = Gender.MALE
cobblemonResource("female") -> if (action.props.gender != Gender.FEMALE) action.props.gender = Gender.FEMALE
}
}
private fun alterLevelAttempt(action: PokemonSpawnAction, effect: FishingBait.Effect) {
action.props.level =
(action.props.level ?: action.detail.getDerivedLevelRange().random()) + effect.value.toInt()
}
private fun alterTeraAttempt(action: PokemonSpawnAction, effect: FishingBait.Effect) {
if (effect.tera == null) return
if (action.props.teraType != null) return
action.props.teraType = effect.tera.path
}
private fun alterHAAttempt(action: PokemonSpawnAction) {
if (action.props.ability != null) return
val species = action.props.species?.let { PokemonSpecies.getByName(it) } ?: return
val ability = species.abilities.mapping[Priority.LOW]?.first()?.template?.name ?: return
action.props.ability = ability
}
}
@@ -0,0 +1,35 @@
/*
* 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.spawning.fishing
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.item.interactive.PokerodItem
import net.minecraft.entity.Entity
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
/**
* A spawning cause that is embellished with fishing information. Could probably also
* have the bobber entity or something.
*
* @author Hiroku
* @since February 3rd, 2024
*/
class FishingSpawnCause(
spawner: Spawner,
bucket: SpawnBucket,
entity: Entity?,
val rodStack: ItemStack,
val baitStack: ItemStack
) : SpawnCause(spawner, bucket, entity) {
val rodItem = rodStack.item as? PokerodItem
val baitItem = baitStack.item as Item
}
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.fishing
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.api.spawning.spawner.TriggerSpawner
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.math.BlockPos
/**
* A spawner that takes in a [FishingSpawnCause] and spawns things.
*
* @author Hiroku
* @since February 3rd, 2024
*/
open class FishingSpawner(
override val name: String = "fishing",
pool: SpawnPool = CobblemonSpawnPools.WORLD_SPAWN_POOL
) : TriggerSpawner<FishingSpawnCause>(name, pool) {
override fun parseContext(cause: FishingSpawnCause, world: ServerWorld, pos: BlockPos): FishingSpawningContext? {
// Maybe confirm that it's water we're fishing in?
val ctx = FishingSpawningContext(
cause,
world,
pos,
influences
)
return ctx
}
fun onServerStarted() {
// TODO: Register influences here
influences.add(FishingBaitInfluence())
}
}
@@ -0,0 +1,51 @@
/*
* 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.spawning.spawner
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.selection.FlatContextWeightedSelector
import com.cobblemon.mod.common.api.spawning.selection.SpawningSelector
import java.util.concurrent.CompletableFuture
import net.minecraft.server.world.ServerWorld
import net.minecraft.util.math.BlockPos
/**
* A type of spawner that occurs at a single point and provides its own means of generating a
* context from a particular cause and position. The trigger spawner must be directly called to
* spawn things (almost as if it was being triggered) as opposed to being scheduled on a ticker.
*
* @author Hiroku
* @since February 3rd, 2024
*/
abstract class TriggerSpawner<T : SpawnCause>(override val name: String, var spawns: SpawnPool) : Spawner {
private var selector: SpawningSelector = FlatContextWeightedSelector()
override val influences = mutableListOf<SpawningInfluence>()
open fun run(cause: T, world: ServerWorld, pos: BlockPos): CompletableFuture<*>? {
val context = parseContext(cause, world, pos) ?: return null
selector.select(this, listOf(context))?.let { (_, spawn) ->
val action = spawn.doSpawn(context)
action.complete()
return action.future
}
return null
}
/** Parses a context, if possible, from the given cause and position. */
abstract fun parseContext(cause: T, world: ServerWorld, pos: BlockPos): SpawningContext?
override fun canSpawn() = true
override fun getSpawningSelector() = selector
override fun setSpawningSelector(selector: SpawningSelector) { this.selector = selector }
override fun getSpawnPool() = spawns
override fun setSpawnPool(spawnPool: SpawnPool) { spawns = spawnPool }
}
@@ -0,0 +1,51 @@
/*
* 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.battles.dispatch
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import net.minecraft.util.Identifier
import java.util.concurrent.CompletableFuture
/**
* An [InterpreterInstruction] that triggers an [ActionEffectTimeline]
* I think it might be better to have this as an abstract class so we can default the fields
*
* @author Apion
* @since May 19, 2024
*/
interface ActionEffectInstruction : InterpreterInstruction {
var future: CompletableFuture<*>
var holds: MutableSet<String>
//To expose via molang, so action effects can do different stuff in different instructions
//e.g. "x is confused" vs "x hit itself in confusion"
val id: Identifier
override fun invoke(battle: PokemonBattle) {
preActionEffect(battle)
val runtime = MoLangRuntime()
battle.addQueryFunctions(runtime.environment.getQueryStruct())
runtime.environment.getQueryStruct().addStandardFunctions()
addMolangQueries(runtime)
runActionEffect(battle, runtime)
postActionEffect(battle)
}
fun preActionEffect(battle: PokemonBattle)
fun runActionEffect(battle: PokemonBattle, runtime: MoLangRuntime)
fun postActionEffect(battle: PokemonBattle)
fun addMolangQueries(runtime: MoLangRuntime) {
runtime.environment.getQueryStruct()
.addFunction("instruction_id") { StringValue(id.toString()) }
}
}
@@ -8,16 +8,28 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.ActionEffectInstruction
import com.cobblemon.mod.common.battles.dispatch.CauserInstruction
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import java.util.concurrent.CompletableFuture
/**
* Format: |-activate|POKEMON|EFFECT
@@ -28,19 +40,50 @@ import net.minecraft.text.Text
* @author Hunter
* @since September 25th, 2022
*/
class ActivateInstruction(val instructionSet: InstructionSet, val message: BattleMessage) : InterpreterInstruction, CauserInstruction {
override fun invoke(battle: PokemonBattle) {
class ActivateInstruction(val instructionSet: InstructionSet, val message: BattleMessage) : ActionEffectInstruction, CauserInstruction {
override var future: CompletableFuture<*> = CompletableFuture.completedFuture(Unit)
override var holds = mutableSetOf<String>()
override val id = cobblemonResource("activate")
override fun preActionEffect(battle: PokemonBattle) {
val pokemon = message.battlePokemon(0, battle) ?: return
val effect = message.effectAt(1) ?: return
val extraEffect = message.effectAt(2)?.typelessData ?: Text.literal("UNKNOWN")
ShowdownInterpreter.broadcastOptionalAbility(battle, effect, pokemon)
battle.dispatch{
val pokemonName = pokemon.getName()
val sourceName = message.battlePokemonFromOptional(battle)?.getName() ?: Text.literal("UNKNOWN")
ShowdownInterpreter.lastCauser[battle.battleId] = message
battle.minorBattleActions[pokemon.uuid] = message
GO
}
}
override fun runActionEffect(battle: PokemonBattle, runtime: MoLangRuntime) {
battle.dispatch {
val effect = message.effectAt(1) ?: return@dispatch GO
val status = Statuses.getStatus(effect.id)
val actionEffect = status?.getActionEffect() ?: return@dispatch GO
val providers = mutableListOf<Any>(battle)
val pokemon = message.battlePokemon(0, battle) ?: return@dispatch GO
pokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
val context = ActionEffectContext(
actionEffect = actionEffect,
runtime = runtime,
providers = providers
)
this.future = actionEffect.run(context)
holds = context.holds // Reference so future things can check on this action effect's holds
future.thenApply { holds.clear() }
return@dispatch GO
}
}
override fun postActionEffect(battle: PokemonBattle) {
battle.dispatch {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatch GO
val extraEffect = message.effectAt(2)?.typelessData ?: Text.literal("UNKNOWN")
val effect = message.effectAt(1) ?: return@dispatch GO
val pokemonName = pokemon.getName()
val sourceName = message.battlePokemonFromOptional(battle)?.getName() ?: Text.literal("UNKNOWN")
val lang = when (effect.id) {
// Includes a 3rd argument being the magnitude level as a number
"magnitude" -> battleLang("activate.magnitude", message.argumentAt(2)?.toIntOrNull() ?: 1)
@@ -59,7 +102,8 @@ class ActivateInstruction(val instructionSet: InstructionSet, val message: Battl
else -> battleLang("activate.${effect.id}", pokemonName, sourceName, extraEffect)
}
battle.broadcastChatMessage(lang)
WaitDispatch(1F)
//We check holds here so the chat msg + particle effect happen more concurrently, instead of sequentially
UntilDispatch { "effects" !in holds}
}
}
}
@@ -8,14 +8,25 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.ActionEffectInstruction
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.util.Identifier
import java.util.concurrent.CompletableFuture
/**
* Format: |-boost|POKEMON|STAT|AMOUNT or |-unboost|POKEMON|STAT|AMOUNT
@@ -25,9 +36,33 @@ import com.cobblemon.mod.common.util.battleLang
* @author Hiroku
* @since August 20th, 2022
*/
class BoostInstruction(val instructionSet: InstructionSet, val message: BattleMessage, val remainingLines: Iterator<BattleMessage>, val isBoost: Boolean = true): InterpreterInstruction {
class BoostInstruction(val instructionSet: InstructionSet, val message: BattleMessage, val remainingLines: Iterator<BattleMessage>, val isBoost: Boolean = true): ActionEffectInstruction {
override var future: CompletableFuture<*> = CompletableFuture.completedFuture(Unit)
override var holds = mutableSetOf<String>()
override val id = cobblemonResource("boost")
override fun preActionEffect(battle: PokemonBattle) {
override fun invoke(battle: PokemonBattle) {
}
override fun runActionEffect(battle: PokemonBattle, runtime: MoLangRuntime) {
battle.dispatch {
val actionEffect = if (isBoost) BOOST_EFFECT else UNBOOST_EFFECT
val providers = mutableListOf<Any>(battle)
val pokemon = message.battlePokemon(0, battle) ?: return@dispatch GO
pokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
val context = ActionEffectContext(
actionEffect = actionEffect,
runtime = runtime,
providers = providers
)
this.future = actionEffect.run(context)
holds = context.holds // Reference so future things can check on this action effect's holds
future.thenApply { holds.clear() }
return@dispatch GO
}
}
override fun postActionEffect(battle: PokemonBattle) {
val pokemon = message.battlePokemon(0, battle) ?: return
val statKey = message.argumentAt(1) ?: return
val stages = message.argumentAt(2)?.toInt() ?: return
@@ -35,7 +70,7 @@ class BoostInstruction(val instructionSet: InstructionSet, val message: BattleMe
val severity = Stats.getSeverity(stages)
val rootKey = if (isBoost) "boost" else "unboost"
battle.dispatchWaiting {
battle.dispatch {
val lang = when {
message.hasOptionalArgument("zeffect") -> battleLang("$rootKey.$severity.zeffect", pokemon.getName(), stat)
else -> battleLang("$rootKey.$severity", pokemon.getName(), stat)
@@ -47,6 +82,13 @@ class BoostInstruction(val instructionSet: InstructionSet, val message: BattleMe
// TODO: replace with context that tracks detailed information such as # of stages
repeat(stages) { pokemon.contextManager.add(context) }
battle.minorBattleActions[pokemon.uuid] = message
return@dispatch UntilDispatch { "effects" !in holds }
}
}
companion object {
val BOOST_EFFECT = ActionEffects.actionEffects[cobblemonResource("boost")]!!
val UNBOOST_EFFECT = ActionEffects.actionEffects[cobblemonResource("unboost")]!!
}
}
@@ -8,14 +8,27 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.TargetsProvider
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.dispatch.ActionEffectInstruction
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.util.Identifier
import java.util.concurrent.CompletableFuture
/**
* Formats: |cant|POKEMON|REASON and |cant|POKEMON|REASON|MOVE
@@ -24,12 +37,40 @@ import com.cobblemon.mod.common.util.lang
* @author Deltric
* @since January 22nd, 2022
*/
class CantInstruction(val message: BattleMessage): InterpreterInstruction {
class CantInstruction(val message: BattleMessage): ActionEffectInstruction {
override var future: CompletableFuture<*> = CompletableFuture.completedFuture(Unit)
override var holds = mutableSetOf<String>()
override val id = cobblemonResource("cant")
override fun preActionEffect(battle: PokemonBattle) {
override fun invoke(battle: PokemonBattle) {
battle.dispatchWaiting {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val effectID = message.effectAt(1)?.id ?: return@dispatchWaiting
}
override fun runActionEffect(battle: PokemonBattle, runtime: MoLangRuntime) {
battle.dispatch {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatch GO
val effectID = message.effectAt(1)?.id ?: return@dispatch GO
val name = pokemon.getName()
val status = Statuses.getStatus(effectID)
val actionEffect = status?.getActionEffect() ?: return@dispatch GO
val providers = mutableListOf<Any>(battle)
pokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
val context = ActionEffectContext(
actionEffect = actionEffect,
runtime = runtime,
providers = providers
)
this.future = actionEffect.run(context)
holds = context.holds // Reference so future things can check on this action effect's holds
future.thenApply { holds.clear() }
GO
}
}
override fun postActionEffect(battle: PokemonBattle) {
battle.dispatch {
val pokemon = message.battlePokemon(0, battle) ?: return@dispatch GO
val effectID = message.effectAt(1)?.id ?: return@dispatch GO
val name = pokemon.getName()
// Move may be null as it's not always given
val moveName = message.moveAt(2)?.displayName ?: run { "(Unrecognized: ${message.argumentAt(2)})".text() }
@@ -38,7 +79,7 @@ class CantInstruction(val message: BattleMessage): InterpreterInstruction {
// TODO: in the games they use a generic image because there is a popup of the ability and the sprite of the mon, it may be good to have a similar system here
"armortail", "damp", "dazzling", "queenlymajesty" -> battleLang("cant.generic", name, moveName)
"par", "slp", "frz" -> {
val status = Statuses.getStatus(effectID)?.name?.path ?: return@dispatchWaiting
val status = Statuses.getStatus(effectID)?.name?.path ?: return@dispatch GO
lang("status.$status.is", name)
}
else -> battleLang("cant.$effectID", name, moveName)
@@ -46,6 +87,10 @@ class CantInstruction(val message: BattleMessage): InterpreterInstruction {
battle.broadcastChatMessage(lang.red())
battle.minorBattleActions[pokemon.uuid] = message
UntilDispatch { "effects" !in holds }
}
}
}
@@ -8,15 +8,21 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
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.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.ShowdownInterpreter.lastCauser
import com.cobblemon.mod.common.battles.dispatch.ActionEffectInstruction
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
@@ -25,11 +31,13 @@ import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePac
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.pokemon.evolution.progress.DamageTakenEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.progress.RecoilEvolutionProgress
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import java.util.concurrent.CompletableFuture
import kotlin.math.roundToInt
import net.minecraft.text.Text
import net.minecraft.util.Identifier
/**
* Format: |-damage|POKEMON|HP STATUS
@@ -43,45 +51,87 @@ class DamageInstruction(
val actor: BattleActor,
val publicMessage: BattleMessage,
val privateMessage: BattleMessage
) : InterpreterInstruction {
var future = CompletableFuture.completedFuture(Unit)
) : ActionEffectInstruction {
val expectedTarget = publicMessage.battlePokemon(0, actor.battle)
override var future: CompletableFuture<*> = CompletableFuture.completedFuture(Unit)
override var holds = mutableSetOf<String>()
override val id = cobblemonResource("damage")
override fun invoke(battle: PokemonBattle) {
override fun preActionEffect(battle: PokemonBattle) {
val battlePokemon = publicMessage.battlePokemon(0, actor.battle) ?: return
val recoiling = privateMessage.optionalArgument("from")?.equals("recoil", true) == true
val lastCauser = instructionSet.getMostRecentCauser(comparedTo = this)
if (recoiling) {
battlePokemon.effectedPokemon.let { pokemon ->
if (RecoilEvolutionProgress.supports(pokemon)) {
val healthStr = privateMessage.argumentAt(1) ?: throw UnsupportedOperationException("Cant get recoil string")
val newHealth = "([0-9]+).*".toRegex().find(healthStr)?.groups?.get(1)?.value?.toIntOrNull() ?: throw UnsupportedOperationException("Cant get recoil string")
val difference = pokemon.currentHealth - newHealth
if (difference > 0) {
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is RecoilEvolutionProgress }) { RecoilEvolutionProgress() }
progress.updateProgress(RecoilEvolutionProgress.Progress(progress.currentProgress().recoil + difference))
}
doRecoilEvoChecks(battlePokemon)
if (lastCauser is MoveInstruction) {
battle.dispatch {
//Is the recoil hold currently used anywhere?
UntilDispatch { "recoil" !in lastCauser.holds }
}
}
}
val newHealth = privateMessage.argumentAt(1)?.split(" ")?.get(0) ?: return
val effect = privateMessage.effect()
val source = privateMessage.battlePokemonFromOptional(battle)
var causedFaint = newHealth == "0"
source?.let { ShowdownInterpreter.broadcastOptionalAbility(battle, effect, it) }
}
battle.dispatch {
if (lastCauser is MoveInstruction && recoiling) {
UntilDispatch { "recoil" !in lastCauser.holds }
} else {
GO
private fun doRecoilEvoChecks(battlePokemon: BattlePokemon) {
battlePokemon.effectedPokemon.let { pokemon ->
if (RecoilEvolutionProgress.supports(pokemon)) {
val healthStr = privateMessage.argumentAt(1) ?: throw UnsupportedOperationException(
"Cant get recoil string"
)
val newHealth =
"([0-9]+).*".toRegex().find(healthStr)?.groups?.get(1)?.value?.toIntOrNull()
?: throw UnsupportedOperationException("Cant get recoil string")
val difference = pokemon.currentHealth - newHealth
if (difference > 0) {
val progress = pokemon.evolutionProxy.current()
.progressFirstOrCreate({ it is RecoilEvolutionProgress }) { RecoilEvolutionProgress() }
progress.updateProgress(RecoilEvolutionProgress.Progress(progress.currentProgress().recoil + difference))
}
}
}
}
source?.let { ShowdownInterpreter.broadcastOptionalAbility(battle, effect, it) }
override fun runActionEffect(battle: PokemonBattle, runtime: MoLangRuntime) {
val effect = privateMessage.effect()
val battlePokemon = publicMessage.battlePokemon(0, actor.battle) ?: return
var status = effect?.id?.let { Statuses.getStatus(it) }
battle.dispatch {
val pokemon = privateMessage.battlePokemon(0, battle) ?: return@dispatch GO
//Showdown doesnt tell us on damage if its poison or toxic so we gotta consult the entity
if (status is PoisonStatus) {
status = pokemon.effectedPokemon.status?.status ?: status
}
val actionEffect = status?.getActionEffect() ?: return@dispatch GO
val providers = mutableListOf<Any>(battle)
battlePokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
val context = ActionEffectContext(
actionEffect = actionEffect,
runtime = runtime,
providers = providers
)
this.future = actionEffect.run(context)
holds = context.holds // Reference so future things can check on this action effect's holds
future.thenApply { holds.clear() }
return@dispatch GO
}
}
override fun postActionEffect(battle: PokemonBattle) {
val newHealth = privateMessage.argumentAt(1)?.split(" ")?.get(0) ?: return
val battlePokemon = publicMessage.battlePokemon(0, actor.battle) ?: return
var causedFaint = newHealth == "0"
val effect = privateMessage.effect()
val source = privateMessage.battlePokemonFromOptional(battle)
val lastCauser = instructionSet.getMostRecentCauser(comparedTo = this)
battle.dispatch {
val pokemonName = battlePokemon.getName()
val pokemonEntity = battlePokemon.entity
//Play recoil animation if the pokemon recoiling isnt dead
if (!causedFaint && pokemonEntity != null) {
val pkt = PlayPoseableAnimationPacket(pokemonEntity.id, setOf("recoil"), emptySet())
pkt.sendToPlayersAround(
@@ -92,7 +142,7 @@ class DamageInstruction(
distance = 50.0
)
}
//Play hit particle
if (pokemonEntity != null) {
RunPosableMoLangPacket(pokemonEntity.id, setOf("q.particle('cobblemon:hit', 'target')")).sendToPlayersAround(
x = pokemonEntity.x,
@@ -157,8 +207,9 @@ class DamageInstruction(
} else if (causedFaint) {
GO
} else {
WaitDispatch(1F)
UntilDispatch {"effects" !in holds}
}
}
}
}
@@ -10,10 +10,10 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.interpreter.Effect
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunction
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.moves.Moves
@@ -29,6 +29,7 @@ import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.pokemon.evolution.progress.UseMoveEvolutionProgress
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import java.util.concurrent.CompletableFuture
/**
@@ -121,6 +122,7 @@ class MoveInstruction(
}
runtime.environment.getQueryStruct().addFunction("move") { move.struct }
runtime.environment.getQueryStruct().addFunction("instruction_id") { StringValue(cobblemonResource("move").toString()) }
this.future = actionEffect.run(context)
holds = context.holds // Reference so future things can check on this action effect's holds
@@ -19,6 +19,7 @@ import net.minecraft.entity.LivingEntity
import net.minecraft.entity.ai.pathing.NavigationType
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.inventory.SidedInventory
import net.minecraft.inventory.SimpleInventory
import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemStack
import net.minecraft.server.world.ServerWorld
@@ -209,9 +210,8 @@ class RestorationTankBlock(properties: Settings) : MultiblockBlock(properties),
): SidedInventory {
val tankEntity =
(if (state.get(PART) == TankPart.TOP) world.getBlockEntity(pos.down())
else world.getBlockEntity(pos)) as RestorationTankBlockEntity
return tankEntity.inv
else world.getBlockEntity(pos))
return if(tankEntity != null && tankEntity is RestorationTankBlockEntity) tankEntity.inv else DummyInventory()
}
enum class TankPart(private val label: String) : StringIdentifiable {
@@ -229,5 +229,18 @@ class RestorationTankBlock(properties: Settings) : MultiblockBlock(properties),
val PART = EnumProperty.of("part", TankPart::class.java)
val TRIGGERED = Properties.TRIGGERED
val ON = BooleanProperty.of("on")
class DummyInventory : SimpleInventory(0), SidedInventory {
override fun getAvailableSlots(side: Direction): IntArray {
return IntArray(0)
}
override fun canInsert(slot: Int, stack: ItemStack, dir: Direction?): Boolean {
return false
}
override fun canExtract(slot: Int, stack: ItemStack, dir: Direction): Boolean {
return false
}
}
}
}
@@ -187,10 +187,6 @@ class GildedChestBlock(settings: Settings, val type: Type = Type.RED) : BlockWit
if (!player.world.isClient) {
PiglinBrain.onGuardedBlockInteracted(player, true)
}
val state = entity.poseableState
state.currentModel?.let {
it.moveToPose(null, state, it.getPose("OPEN")!!)
}
return ActionResult.SUCCESS
}
@@ -62,14 +62,6 @@ class FossilAnalyzerBlockEntity(
}
override fun removeStack(slot: Int): ItemStack {
// if(analyzerEntity.multiblockStructure is FossilMultiblockStructure) {
// val fossilMultiblockStructure = analyzerEntity.multiblockStructure as FossilMultiblockStructure
// if (fossilMultiblockStructure.fossilInventory.size > slot) {
// val itemStack = fossilMultiblockStructure.fossilInventory.removeAt(slot)
// markDirty()
// return itemStack
// }
// }
return ItemStack.EMPTY
}
@@ -111,7 +103,6 @@ class FossilAnalyzerBlockEntity(
}
override fun canExtract(slot: Int, stack: ItemStack?, dir: Direction?): Boolean {
// return analyzerEntity.multiblockStructure != null && analyzerEntity.multiblockStructure is FossilMultiblockStructure && dir == Direction.DOWN
return false
}
}
@@ -38,18 +38,14 @@ open class FossilMultiblockEntity(
}
}
get() {
return if (field != null) {
field
} else if (masterBlockPos != null && masterBlockPos != pos) {
if(masterBlockPos != null && masterBlockPos != pos) {
val chunkPos = ChunkPos(masterBlockPos)
if (world?.chunkManager?.isChunkLoaded(chunkPos.x, chunkPos.z) == true) {
val entity: FossilMultiblockEntity? = world?.getBlockEntity(masterBlockPos) as FossilMultiblockEntity?
field = entity?.multiblockStructure
}
field
} else {
null
}
return field
}
override fun markRemoved() {
@@ -12,13 +12,11 @@ import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.api.fossil.NaturalMaterials
import com.cobblemon.mod.common.api.multiblock.builder.MultiblockStructureBuilder
import com.cobblemon.mod.common.block.multiblock.FossilMultiblockStructure
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.inventory.Inventories
import net.minecraft.inventory.SidedInventory
import net.minecraft.inventory.SimpleInventory
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtList
@@ -55,7 +53,9 @@ class RestorationTankBlockEntity(
constructor(tankEntity: RestorationTankBlockEntity) : this(tankEntity, *Array(8) { ItemStack.EMPTY })
override fun getMaxCountPerStack(): Int {
return 1
}
override fun markDirty() {
super.markDirty()
@@ -64,7 +64,6 @@ class RestorationTankBlockEntity(
if(!itemStack.isEmpty) {
val struct = tankEntity.multiblockStructure as? FossilMultiblockStructure
val returnIdentifier = NaturalMaterials.getReturnItem(itemStack)
if(tankEntity.world != null) {
if(struct?.insertOrganicMaterial(itemStack, tankEntity.world!!) == true) {
removeStack(i)
@@ -72,16 +71,15 @@ class RestorationTankBlockEntity(
val returnItem = Registries.ITEM.get(returnIdentifier)
val returnStack = ItemStack(returnItem, itemStack.count)
var done = false
if (returnStack.maxCount > 1) { // Only search for a merge if merging could be possible
for(j in 0 until size()) {
for(j in 1 until size()) {
// Look for a inventory slot to merge with
val existingStack : ItemStack = getStack(j)
if (Registries.ITEM.getId(existingStack.item) == returnIdentifier && existingStack.count + returnStack.count < existingStack.maxCount) {
if (existingStack.isEmpty
|| (Registries.ITEM.getId(existingStack.item) == returnIdentifier && existingStack.count + returnStack.count < existingStack.maxCount)) {
setStack(j, ItemStack(returnItem, existingStack.count + returnStack.count))
done = true
}
}
}
if (!done) {
setStack(i, ItemStack(returnItem, itemStack.count))
}
@@ -104,7 +102,7 @@ class RestorationTankBlockEntity(
}
override fun getAvailableSlots(side: Direction?): IntArray {
return intArrayOf(0,1,2,3,4,5,6,7)
return if (side == Direction.DOWN) intArrayOf(0,1,2,3,4,5,6,7) else intArrayOf(0)
}
override fun canInsert(slot: Int, stack: ItemStack?, dir: Direction?): Boolean {
@@ -113,16 +111,16 @@ class RestorationTankBlockEntity(
val structure = tankEntity.multiblockStructure as FossilMultiblockStructure
val canUtilize = stack?.let { NaturalMaterials.isNaturalMaterial(it) } == true
&& structure.organicMaterialInside < FossilMultiblockStructure.MATERIAL_TO_START
&& structure.createdPokemon == null
&& !structure.hasCreatedPokemon
val returnItem = NaturalMaterials.getReturnItem(stack!!) ?: return canUtilize
val returnStack = ItemStack(Registries.ITEM.get(returnItem), stack.count)
if(canUtilize && super.canInsert(returnStack)) {
// See if there's room for the return
if(returnStack == ItemStack.EMPTY) {
if(returnStack == ItemStack.EMPTY && getStack(0) == ItemStack.EMPTY) {
return true
}
// See if there's room for the return item
var emptyCount = 0
for(i in 0 until size()) {
for(i in 1 until size()) {
val existingStack : ItemStack = getStack(i)
if(existingStack == ItemStack.EMPTY) {
emptyCount += 1
@@ -130,14 +128,8 @@ class RestorationTankBlockEntity(
// 2 empty slots, safe to insert
return true
}
} else if(returnStack.maxCount > 1) {
if(Registries.ITEM.getId(existingStack.item) == returnItem && existingStack.count + returnStack.count < existingStack.maxCount) {
// Return item can merge with an item already in the inventory
return true
}
}
}
return false
}
}
}
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.block.multiblock
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.advancement.CobblemonCriteria
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.pokemon.FossilRevivedEvent
import com.cobblemon.mod.common.api.fossil.Fossil
@@ -74,7 +73,7 @@ class FossilMultiblockStructure (
var organicMaterialInside = 0
private set
var createdPokemon: Pokemon? = null
var hasCreatedPokemon: Boolean = false
private set
var timeRemaining = -1
private set
@@ -104,8 +103,7 @@ class FossilMultiblockStructure (
if (player !is ServerPlayerEntity) {
return ActionResult.SUCCESS
}
if (this.createdPokemon != null) {
if (this.hasCreatedPokemon) {
if (this.fossilOwnerUUID != null && player.uuid != this.fossilOwnerUUID) {
var ownerName : String = "UNKNOWN_USER" // TODO: lang agnostic fallback
server()?.userCache?.getByUuid(this.fossilOwnerUUID)?.orElse(null)?.name?.let {
@@ -115,25 +113,29 @@ class FossilMultiblockStructure (
return ActionResult.FAIL
}
val ballType = (stack.item as PokeBallItem).pokeBall
if (!player.isCreative) {
stack?.decrement(1)
}
this.createdPokemon!!.caughtBall = ballType
player.party().add(this.createdPokemon!!)
this.fossilState.growthState = "Taken"
player.playSound(CobblemonSounds.FOSSIL_MACHINE_RETRIEVE_POKEMON, SoundCategory.BLOCKS, 1.0F, 1.0F)
CobblemonCriteria.RESURRECT_POKEMON.trigger(player, createdPokemon!!)
CobblemonEvents.FOSSIL_REVIVED.post(FossilRevivedEvent(createdPokemon!!, player))
val pokemon = this.resultingFossil?.result?.create()
if(pokemon != null) {
pokemon.caughtBall = ballType
player.party().add(pokemon)
this.fossilState.growthState = "Taken"
player.playSound(CobblemonSounds.FOSSIL_MACHINE_RETRIEVE_POKEMON, SoundCategory.BLOCKS, 1.0F, 1.0F)
CobblemonEvents.FOSSIL_REVIVED.post(FossilRevivedEvent(pokemon, player))
}
// Turn the monitor off
val monitorState = world.getBlockState(monitorPos)
if(!monitorState.equals(MonitorBlock.MonitorScreen.OFF)) {
if(monitorState.contains(MonitorBlock.SCREEN) && !monitorState.equals(MonitorBlock.MonitorScreen.OFF)) {
world.setBlockState(monitorPos, monitorState.with(MonitorBlock.SCREEN, MonitorBlock.MonitorScreen.OFF))
}
this.createdPokemon = null
this.hasCreatedPokemon = false
this.fossilOwnerUUID = null
this.protectionTime = -1
this.updateFossilType(world)
@@ -145,15 +147,14 @@ class FossilMultiblockStructure (
// Reclaim the last fossil from the machine if their hand is empty
if (player.getStackInHand(interactionHand).isEmpty) {
if(!this.isRunning() && this.createdPokemon == null) {
if(!this.isRunning() && !this.hasCreatedPokemon) {
if (fossilInventory.isEmpty()) {
return ActionResult.CONSUME
}
player.setStackInHand(interactionHand, fossilInventory.last())
// remove last fossil in the fossil machine stack when grabbed out of the machine
this.fossilInventory.removeAt(fossilInventory.size - 1)
if(!world.isClient) {
if(player is ServerPlayerEntity) {
player.setStackInHand(interactionHand, fossilInventory.last())
// remove last fossil in the fossil machine stack when grabbed out of the machine
this.fossilInventory.removeAt(fossilInventory.size - 1)
world.playSound(null, analyzerPos, CobblemonSounds.FOSSIL_MACHINE_RETRIEVE_FOSSIL, SoundCategory.BLOCKS)
this.updateFossilType(world)
this.syncToClient(world)
@@ -165,19 +166,17 @@ class FossilMultiblockStructure (
// Check if the player is holding a fossil and if so insert it into the machine.
if (Fossils.isFossilIngredient(stack)) {
if(!this.isRunning() && this.createdPokemon == null) {
if (!this.isRunning() && !this.hasCreatedPokemon) {
if (fossilInventory.size > Cobblemon.config.maxInsertedFossilItems) {
return ActionResult.FAIL
}
val copyFossilStack = stack.copyWithCount(1)
if (!player.isCreative) {
stack?.decrement(1)
}
fossilOwnerUUID = player.uuid
fossilInventory.add(copyFossilStack)
if(!world.isClient) {
if (player is ServerPlayerEntity) {
val copyFossilStack = stack.copyWithCount(1)
if (!player.isCreative) {
stack?.decrement(1)
}
fossilOwnerUUID = player.uuid
fossilInventory.add(copyFossilStack)
this.updateFossilType(world)
world.playSound(null, analyzerPos, CobblemonSounds.FOSSIL_MACHINE_INSERT_FOSSIL, SoundCategory.BLOCKS)
this.syncToClient(world)
@@ -189,7 +188,11 @@ class FossilMultiblockStructure (
// Check if the player is holding a natural material and if so, feed it to the machine.
if (NaturalMaterials.isNaturalMaterial(stack)) {
if (!this.isRunning() && this.createdPokemon == null && this.organicMaterialInside < MATERIAL_TO_START && insertOrganicMaterial(ItemStack(stack.item, 1), world)) {
if (player is ServerPlayerEntity
&& !this.isRunning()
&& !this.hasCreatedPokemon
&& this.organicMaterialInside < MATERIAL_TO_START
&& insertOrganicMaterial(ItemStack(stack.item, 1), world)) {
this.lastInteraction = world.time
if (!player.isCreative) {
val returnItem = NaturalMaterials.getReturnItem(stack)
@@ -273,7 +276,7 @@ class FossilMultiblockStructure (
return 0
}
if(monitorPos == pos) {
if(createdPokemon != null) {
if(hasCreatedPokemon) {
return 15
}
if(!isRunning()) {
@@ -289,13 +292,13 @@ class FossilMultiblockStructure (
override fun onTriggerEvent(state: BlockState?, world: ServerWorld?, pos: BlockPos?, random: Random?) {
// instantiate the pokemon as a new entity and spawn it at the location of the machine
if(this.protectionTime <= 0) {
val wildPokemon: Pokemon = this.createdPokemon ?: return
val wildPokemon: Pokemon = if (hasCreatedPokemon) resultingFossil?.result?.create() ?: return else return
val direction = state?.get(HorizontalFacingBlock.FACING)?.opposite
if(pos != null && direction != null && world != null) {
val success = this.spawn(world, pos, direction, wildPokemon)
if(success) {
this.fossilState.growthState = "Taken"
this.createdPokemon = null
this.hasCreatedPokemon = false
this.fossilOwnerUUID = null
this.protectionTime = -1
world.playSound(null, tankBasePos, CobblemonSounds.FOSSIL_MACHINE_RETRIEVE_POKEMON, SoundCategory.BLOCKS)
@@ -312,9 +315,9 @@ class FossilMultiblockStructure (
val analyzerEntity = world.getBlockEntity(analyzerPos) as? MultiblockEntity
val tankBaseEntity = world.getBlockEntity(tankBasePos) as? MultiblockEntity
val tankTopEntity = world.getBlockEntity(tankBasePos.up()) as? MultiblockEntity
val state = world.getBlockState(tankBaseEntity?.pos)
val direction = state.get(HorizontalFacingBlock.FACING).getOpposite()
val wildPokemon: Pokemon? = this.createdPokemon
val tankBaseBlockState = world.getBlockState(tankBaseEntity?.pos)
val direction = tankBaseBlockState.get(HorizontalFacingBlock.FACING).opposite
val wildPokemon: Pokemon? = if(hasCreatedPokemon) resultingFossil?.result?.create() else null
monitorEntity?.multiblockStructure = null
analyzerEntity?.multiblockStructure = null
@@ -374,7 +377,7 @@ class FossilMultiblockStructure (
world.playSound(null, tankBasePos, CobblemonSounds.FOSSIL_MACHINE_UNPROTECTED, SoundCategory.BLOCKS)
}
if (this.createdPokemon != null) {
if (this.hasCreatedPokemon) {
return
}
@@ -403,10 +406,7 @@ class FossilMultiblockStructure (
if (this.timeRemaining == 0) {
world.playSound(null, tankBasePos, CobblemonSounds.FOSSIL_MACHINE_FINISHED, SoundCategory.BLOCKS)
fossilInventory.clear()
this.resultingFossil?.let {
this.createdPokemon = it.result.create()
}
this.hasCreatedPokemon = true
if(this.fossilOwnerUUID != null) {
protectionTime = PROTECTION_TIME
}
@@ -477,20 +477,26 @@ class FossilMultiblockStructure (
val upperTankPos = tankBasePos.up()
val analyzerState = world.getBlockState(analyzerPos)
val tankState = world.getBlockState(tankBasePos.up())
world.setBlockState(analyzerPos, analyzerState.with(FossilAnalyzerBlock.ON, timeRemaining >= 0))
world.setBlockState(upperTankPos, tankState.with(RestorationTankBlock.ON, timeRemaining >= 0))
if (analyzerState.contains(FossilAnalyzerBlock.ON)) {
world.setBlockState(analyzerPos, analyzerState.with(FossilAnalyzerBlock.ON, timeRemaining >= 0))
}
if (tankState.contains(RestorationTankBlock.ON)) {
world.setBlockState(upperTankPos, tankState.with(RestorationTankBlock.ON, timeRemaining >= 0))
}
}
fun updateProgress(world: World) {
val screenID = if(protectionTime > 0F ) {
MonitorBlock.MonitorScreen.GREEN_PROGRESS_9
} else if (timeRemaining <= 0) {
MonitorBlock.MonitorScreen.OFF
} else {
getProgressScreen((TIME_TO_TAKE - timeRemaining) / TIME_PER_STAGE)
}
val monitorState = world.getBlockState(monitorPos)
world.setBlockState(monitorPos, monitorState.with(MonitorBlock.SCREEN, screenID))
if (monitorState.contains(MonitorBlock.SCREEN)) {
val screenID = if (protectionTime > 0F) {
MonitorBlock.MonitorScreen.GREEN_PROGRESS_9
} else if (timeRemaining <= 0) {
MonitorBlock.MonitorScreen.OFF
} else {
getProgressScreen((TIME_TO_TAKE - timeRemaining) / TIME_PER_STAGE)
}
world.setBlockState(monitorPos, monitorState.with(MonitorBlock.SCREEN, screenID))
}
}
fun getProgressScreen(progress:Int) : MonitorBlock.MonitorScreen {
@@ -605,9 +611,7 @@ class FossilMultiblockStructure (
result.putString(DataKeys.INSERTED_FOSSIL, this.resultingFossil!!.asString())
}
if (this.createdPokemon != null) {
result.put(DataKeys.CREATED_POKEMON, createdPokemon!!.saveToNBT(NbtCompound()))
}
result.putBoolean(DataKeys.HAS_CREATED_POKEMON, hasCreatedPokemon)
return result
}
@@ -656,7 +660,10 @@ class FossilMultiblockStructure (
}
if (nbt.contains(DataKeys.CREATED_POKEMON)) {
result.createdPokemon = Pokemon.loadFromNBT(nbt.getCompound(DataKeys.CREATED_POKEMON))
// migration of instances that saved the created pokeon in the nbt
result.hasCreatedPokemon = true
} else if (nbt.contains(DataKeys.HAS_CREATED_POKEMON)){
result.hasCreatedPokemon = nbt.getBoolean(DataKeys.HAS_CREATED_POKEMON)
}
result.fillLevel = result.organicMaterialInside * 8 / MATERIAL_TO_START
return result
@@ -29,6 +29,7 @@ import com.cobblemon.mod.common.client.render.block.GildedChestBlockRenderer
import com.cobblemon.mod.common.client.render.block.HealingMachineRenderer
import com.cobblemon.mod.common.client.render.block.*
import com.cobblemon.mod.common.client.render.boat.CobblemonBoatRenderer
import com.cobblemon.mod.common.client.render.entity.PokeBobberEntityRenderer
import com.cobblemon.mod.common.client.render.generic.GenericBedrockRenderer
import com.cobblemon.mod.common.client.render.item.CobblemonBuiltinItemRendererRegistry
import com.cobblemon.mod.common.client.render.item.PokemonItemRenderer
@@ -266,6 +267,8 @@ object CobblemonClient {
this.implementation.registerEntityRenderer(CobblemonEntities.CHEST_BOAT) { ctx -> CobblemonBoatRenderer(ctx, true) }
LOGGER.info("Registering Generic Bedrock Entity renderer")
this.implementation.registerEntityRenderer(CobblemonEntities.GENERIC_BEDROCK_ENTITY, ::GenericBedrockRenderer)
LOGGER.info("Registering PokeRod Bobber renderer")
this.implementation.registerEntityRenderer(CobblemonEntities.POKE_BOBBER) { ctx -> PokeBobberEntityRenderer(ctx) }
}
fun reloadCodedAssets(resourceManager: ResourceManager) {
@@ -15,9 +15,6 @@ import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import net.minecraft.client.MinecraftClient
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.util.math.RotationAxis.NEGATIVE_X
import net.minecraft.util.math.RotationAxis.POSITIVE_X
import net.minecraft.util.math.RotationAxis.POSITIVE_Y
object SpawnSnowstormParticleHandler : ClientNetworkPacketHandler<SpawnSnowstormParticlePacket> {
override fun handle(packet: SpawnSnowstormParticlePacket, client: MinecraftClient) {
@@ -10,16 +10,20 @@ package com.cobblemon.mod.common.client.particle
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunction
import com.cobblemon.mod.common.api.molang.MoLangFunctions.getQueryStruct
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies.species
import com.cobblemon.mod.common.api.snowstorm.BedrockParticleEffect
import com.cobblemon.mod.common.api.snowstorm.ParticleEmitterAction
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.client.render.SnowstormParticle
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.particle.SnowstormParticleEffect
import com.cobblemon.mod.common.util.math.geometry.transformDirection
import kotlin.random.Random
import net.minecraft.client.MinecraftClient
import net.minecraft.client.particle.NoRenderParticle
import net.minecraft.client.util.ParticleUtil.spawnParticle
import net.minecraft.client.world.ClientWorld
import net.minecraft.entity.Entity
import net.minecraft.util.math.Vec3d
@@ -48,12 +52,22 @@ class ParticleStorm(
.addFunction("entity_height") { DoubleValue(entity.boundingBox.yLength) }
.addFunction("entity_size") { DoubleValue(entity.boundingBox.run { if (xLength > yLength) xLength else yLength }) }
.addFunction("entity_radius") { DoubleValue(entity.boundingBox.run { if (xLength > yLength) xLength else yLength } / 2) }
.addFunction("entity_scale") {
val pokeEntity = entity as? PokemonEntity
val pokemon = pokeEntity?.pokemon
//Use form data if available, species as fall back
val baseScale = pokemon?.form?.baseScale ?: pokemon?.species?.baseScale ?: 1.0F
val pokemonScale = pokemon?.scaleModifier ?: 1.0F
val entityScale = pokeEntity?.scaleFactor ?: 1.0F
DoubleValue(baseScale * pokemonScale * entityScale)
}
// TODO replace with a generified call to if (entity is MoLangEntity) entity.applyVariables(env) or w/e
runtime.environment.setSimpleVariable("entity_width", DoubleValue(entity.boundingBox.xLength))
runtime.environment.setSimpleVariable("entity_height", DoubleValue(entity.boundingBox.yLength))
val longerDiameter = entity.boundingBox.run { if (xLength > yLength) xLength else yLength }
runtime.environment.setSimpleVariable("entity_size", DoubleValue(longerDiameter))
runtime.environment.setSimpleVariable("entity_radius", DoubleValue(longerDiameter / 2))
runtime.environment.setSimpleVariable("entity_scale", DoubleValue((entity as? PokemonEntity)?.scaleFactor ?: 1.0))
}
MinecraftClient.getInstance().particleManager.addParticle(this)
}
@@ -118,7 +118,7 @@ class SnowstormParticle(
init {
setVelocity(initialVelocity.x, initialVelocity.y, initialVelocity.z)
angle = storm.effect.particle.rotation.getInitialRotation(storm.runtime).toFloat()
angle = -storm.effect.particle.rotation.getInitialRotation(storm.runtime).toFloat()
prevAngle = angle
angularVelocity = storm.effect.particle.rotation.getInitialAngularVelocity(storm.runtime)
velocityMultiplier = 1F
@@ -256,24 +256,20 @@ class SnowstormParticle(
setParticleAgeInRuntime()
storm.effect.curves.forEach { it.apply(storm.runtime) }
storm.runtime.execute(storm.effect.particle.updateExpressions)
angularVelocity += storm.effect.particle.rotation.getAngularAcceleration(storm.runtime, angularVelocity) / 20
angularVelocity = storm.effect.particle.rotation.getAngularVelocity(storm.runtime, -angle.toDouble(), angularVelocity) / 20
if (age > maxAge || storm.runtime.resolveBoolean(storm.effect.particle.killExpression)) {
if (age >= maxAge || storm.runtime.resolveBoolean(storm.effect.particle.killExpression)) {
runExpirationEvents()
markDead()
return
} else {
val acceleration = storm.effect.particle.motion.getAcceleration(
storm.runtime,
Vec3d(velocityX, velocityY, velocityZ).multiply(20.0) // Uses blocks per second, not blocks per tick
).multiply(1 / 20.0).multiply(1 / 20.0) // blocks per second per second -> blocks per tick per tick
velocityX += acceleration.x
velocityY += acceleration.y
velocityZ += acceleration.z
val velocity = storm.effect.particle.motion.getVelocity(storm.runtime, this, Vec3d(velocityX, velocityY, velocityZ))
velocityX = velocity.x
velocityY = velocity.y
velocityZ = velocity.z
prevAngle = angle
angle = prevAngle + angularVelocity.toFloat()
// Subtract because Bedrock particles are counter-clockwise and Java Edition is clockwise.
angle = prevAngle - angularVelocity.toFloat()
}
viewDirection = storm.effect.particle.viewDirection.getDirection(
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.client.render.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.block.DisplayCaseBlock
import com.cobblemon.mod.common.block.entity.DisplayCaseBlockEntity
import com.cobblemon.mod.common.client.render.models.blockbench.repository.PokemonModelRepository
@@ -136,7 +137,7 @@ class DisplayCaseRenderer(ctx: BlockEntityRendererFactory.Context) : BlockEntity
mobHeads.contains(stack.item) -> PositioningType.MOB_HEAD
stack.item is BedItem -> PositioningType.BED
stack.item is BannerItem -> PositioningType.BANNER
stack.item is PokeBallItem -> PositioningType.POKE_BALL
stack.isIn(CobblemonItemTags.POKE_BALLS) -> PositioningType.POKE_BALL
stack.item == CobblemonItems.RELIC_COIN_POUCH -> PositioningType.COIN_POUCH
stack.item == CobblemonItems.PASTURE -> PositioningType.PASTURE
stack.item == CobblemonItems.POKEMON_MODEL -> PositioningType.ITEM_MODEL
@@ -61,17 +61,17 @@ class RestorationTankRenderer(ctx: BlockEntityRendererFactory.Context) : BlockEn
matrices.pop()
}
val fillLevel = struct.fillLevel
if (fillLevel == 0 && struct.createdPokemon == null) {
if (fillLevel == 0 && !struct.hasCreatedPokemon) {
return
}
if (struct.isRunning() or (struct.createdPokemon != null)) renderFetus(entity, tickDelta, matrices, vertexConsumers, light, overlay)
if (struct.isRunning() or (struct.hasCreatedPokemon)) renderFetus(entity, tickDelta, matrices, vertexConsumers, light, overlay)
matrices.push()
val transparentBuffer = vertexConsumers.getBuffer(RenderLayer.getTranslucent())
val fluidModel = if (struct.isRunning()) FLUID_MODELS[8]
else if (struct.createdPokemon != null) FLUID_MODELS[7]
else if (struct.hasCreatedPokemon) FLUID_MODELS[7]
else FLUID_MODELS[fillLevel.coerceAtMost(FLUID_MODELS.size - 1) - 1]
fluidModel.getQuads(entity.cachedState, null, entity.world?.random).forEach { quad ->
transparentBuffer?.quad(matrices.peek(), quad, 0.75f, 0.75f, 0.75f, light, OverlayTexture.DEFAULT_UV)
@@ -0,0 +1,311 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.entity
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.fishing.PokeRods
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.item.interactive.PokerodItem
import com.cobblemon.mod.common.util.cobblemonResource
import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.OverlayTexture
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.VertexConsumer
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.entity.EntityRenderer
import net.minecraft.client.render.entity.EntityRendererFactory
import net.minecraft.client.render.model.json.ModelTransformationMode
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.item.ItemStack
import net.minecraft.registry.Registries
import net.minecraft.util.Arm
import net.minecraft.util.Identifier
import net.minecraft.util.math.MathHelper
import net.minecraft.util.math.RotationAxis
import org.joml.Matrix3f
import org.joml.Matrix4f
import org.joml.Quaterniond
import org.joml.Quaternionf
import org.joml.Vector3f
import java.awt.Color
@Environment(value = EnvType.CLIENT)
class PokeBobberEntityRenderer(context: EntityRendererFactory.Context?) : EntityRenderer<PokeRodFishingBobberEntity>(context) {
private var lastSpinAngle: Float = 0f
private var randomPitch: Float = 0f
private var randomYaw: Float = 0f
override fun render(fishingBobberEntity: PokeRodFishingBobberEntity, elapsedPartialTicks: Float, tickDelta: Float, matrixStack: MatrixStack, vertexConsumerProvider: VertexConsumerProvider, light: Int) {
var playerPosXWorld: Double
val eyeHeightOffset: Float
val playerPosZWorld: Double
val playerPosYWorld: Double
val playerEyeYWorld: Double
val playerEntity = fishingBobberEntity.playerOwner ?: return
val berryAdjustY = 0.0 // for tweaking height pos of some berry baits
val berryAdjustX = 0.0 // for tweaking width pos of some berry baits
matrixStack.push() // prepare for overall rendering transforms
// Generate controlled random pitch and yaw for each cast, with constraints
if (fishingBobberEntity.age <= 1) {
// Random yaw within a constrained range to allow for limited tilting
randomYaw = (-180 + Math.random() * 360).toFloat() // Example: -180 to +180 degrees
// Random pitch to ensure the top of the Poke Ball leans towards the string
randomPitch = (-40 + Math.random() * 80).toFloat() // Example: -40 to +40 degrees
}
matrixStack.push() // prepare for bobber rendering transforms
// Apply spinning effect only if the bobber is in the air
// Modify spinning effect based on whether the bobber is in open water
if (!fishingBobberEntity.isOnGround) {
// You might not need to change stopRotationAge if you want the slowing to happen faster
val stopRotationAge = 220 // This could remain as your baseline for when rotation completely stops
// Adjust ageFactor calculation for faster slowing in open water
val ageFactor = if (fishingBobberEntity.age < stopRotationAge) {
if (fishingBobberEntity.inOpenWater) {
1 + fishingBobberEntity.age / 15.0 // Slows down much faster in open water
} else {
1 + fishingBobberEntity.age / 100.0 // Standard slowing rate
}
} else {
Double.POSITIVE_INFINITY // Stops spinning
}
// Ensure ageFactor does not reduce the rotation speed to 0 or negative
val adjustedAgeFactor = if (ageFactor > 0) ageFactor else 1.0
// Update and apply the spinning effect, incorporating the slowing factor
// Stop increasing lastSpinAngle once the bobber reaches the stopRotationAge
if (fishingBobberEntity.age < stopRotationAge) {
lastSpinAngle = (((fishingBobberEntity.age + tickDelta) * 20 / adjustedAgeFactor) % 360).toFloat()
}
// After reaching stopRotationAge, lastSpinAngle remains constant, effectively stopping the rotation
}
// When in water, gradually make the Poke Ball upright
if (fishingBobberEntity.state == PokeRodFishingBobberEntity.State.BOBBING && !fishingBobberEntity.isOnGround) {
// Assuming randomPitch is the rotation around X axis that needs to be adjusted
// Simple linear interpolation towards 0 degrees; adjust 'lerpFactor' as needed for speed
val lerpFactor = 0.04f // This controls the speed of the adjustment
randomPitch = MathHelper.lerp(lerpFactor, randomPitch, 0f) // Adjust towards 0 degrees within range
// Consider similar logic for yaw if needed to maintain visual consistency
}
// Apply random pitch and yaw before rendering the Poke Ball
matrixStack.multiply(RotationAxis.NEGATIVE_X.rotationDegrees(randomPitch))
matrixStack.multiply(RotationAxis.NEGATIVE_Y.rotationDegrees(randomYaw))
// Apply rotation based on last spin angle
matrixStack.multiply(RotationAxis.NEGATIVE_Y.rotationDegrees(lastSpinAngle))
// Scale down the Poke Ball to 70% of its original size
matrixStack.scale(0.7f, 0.7f, 0.7f) // Apply the scaling transformation
// try to render the bobber sprite as well to have hook pop out the bottom of the Bobber model
val entry = matrixStack.peek()
val matrix4f = entry.positionMatrix
val matrix3f = entry.normalMatrix
// Scale factor
// Scale factor
val scale = 0.75f
// Additional shift to the leeft
val shiftHook = 0.045f // Adjust this value as needed to shift the geometry
val vertexConsumer = vertexConsumerProvider.getBuffer(PokeBobberEntityRenderer.Companion.LAYER)
/*// Original vertices for one side
PokeBobberEntityRenderer.Companion.vertex(vertexConsumer, matrix4f, matrix3f, light, 0.0f, 0, 0, 1)
PokeBobberEntityRenderer.Companion.vertex(vertexConsumer, matrix4f, matrix3f, light, 1.0f, 0, 1, 1)
PokeBobberEntityRenderer.Companion.vertex(vertexConsumer, matrix4f, matrix3f, light, 1.0f, 1, 1, 0)
PokeBobberEntityRenderer.Companion.vertex(vertexConsumer, matrix4f, matrix3f, light, 0.0f, 1, 0, 0)
// Add vertices for the opposite side in reverse order
PokeBobberEntityRenderer.Companion.vertex(vertexConsumer, matrix4f, matrix3f, light, 0.0f, 1, 0, 0)
PokeBobberEntityRenderer.Companion.vertex(vertexConsumer, matrix4f, matrix3f, light, 1.0f, 1, 1, 0)
PokeBobberEntityRenderer.Companion.vertex(vertexConsumer, matrix4f, matrix3f, light, 1.0f, 0, 1, 1)
PokeBobberEntityRenderer.Companion.vertex(vertexConsumer, matrix4f, matrix3f, light, 0.0f, 0, 0, 1)*/
// Adjusted and flipped vertices for one side, scaled by 75% and shifted
vertex(vertexConsumer, matrix4f, matrix3f, light, (1.0f - 0.0f) * scale + 0.125f + shiftHook, (0.0f * scale + 0.125f), 0, 1)
vertex(vertexConsumer, matrix4f, matrix3f, light, (1.0f - 1.0f) * scale + 0.125f + shiftHook, (0.0f * scale + 0.125f), 1, 1)
vertex(vertexConsumer, matrix4f, matrix3f, light, (1.0f - 1.0f) * scale + 0.125f + shiftHook, (1.0f * scale + 0.125f), 1, 0)
vertex(vertexConsumer, matrix4f, matrix3f, light, (1.0f - 0.0f) * scale + 0.125f + shiftHook, (1.0f * scale + 0.125f), 0, 0)
// Adjusted and flipped vertices for the opposite side in reverse order, scaled by 75% and shifted
vertex(vertexConsumer, matrix4f, matrix3f, light, (1.0f - 0.0f) * scale + 0.125f + shiftHook, (1.0f * scale + 0.125f), 0, 0)
vertex(vertexConsumer, matrix4f, matrix3f, light, (1.0f - 1.0f) * scale + 0.125f + shiftHook, (1.0f * scale + 0.125f), 1, 0)
vertex(vertexConsumer, matrix4f, matrix3f, light, (1.0f - 1.0f) * scale + 0.125f + shiftHook, (0.0f * scale + 0.125f), 1, 1)
vertex(vertexConsumer, matrix4f, matrix3f, light, (1.0f - 0.0f) * scale + 0.125f + shiftHook, (0.0f * scale + 0.125f), 0, 1)
val pokeRodIdStr = fishingBobberEntity.dataTracker.get(PokeRodFishingBobberEntity.POKEROD_ID)
val pokeBobberBaitItemStack = fishingBobberEntity.dataTracker.get(PokeRodFishingBobberEntity.POKEBOBBER_BAIT)
val pokeRodId = Identifier.tryParse(pokeRodIdStr)
val pokeRod = PokeRods.getPokeRod(pokeRodId!!)
val ballItem = PokeBalls.getPokeBall(pokeRod?.pokeBallId!!)!!.item
// render the pokebobber
MinecraftClient.getInstance().itemRenderer.renderItem(
ballItem.defaultStack,
ModelTransformationMode.GROUND,
light,
OverlayTexture.DEFAULT_UV,
matrixStack,
vertexConsumerProvider,
fishingBobberEntity.world,
0
)
matrixStack.push() // prepare for transforms for Bait rendering
// check if any adjustments are needed for the certain kind of bait
val berryAdjust = adjustBerry(pokeBobberBaitItemStack)
// Scale down the bait to 70% of its original size
matrixStack.scale(0.8f, 0.8f, 0.8f) // Apply the scaling transformation
// Translate the bait downwards to be on the hook
matrixStack.translate(0.20 + berryAdjust.first, -0.5 + berryAdjust.second, 0.0); // Move the berry down
// Rotate the bait 90 degrees around the Y-axis
val rotation = Quaternionf().rotateY(Math.toRadians(0.0).toFloat())
matrixStack.multiply(rotation)
// render the bait
MinecraftClient.getInstance().itemRenderer.renderItem(
pokeBobberBaitItemStack,
ModelTransformationMode.GROUND,
light,
OverlayTexture.DEFAULT_UV,
matrixStack,
vertexConsumerProvider,
fishingBobberEntity.world,
0
)
matrixStack.pop() // close bait rendering transforms
matrixStack.pop() // close bobber rendering transforms
var armOffset = if (playerEntity.mainArm == Arm.RIGHT) 1 else -1
val itemStack = playerEntity.mainHandStack
if (itemStack.item !is PokerodItem) {
armOffset = -armOffset
}
val handSwingProgress = playerEntity.getHandSwingProgress(tickDelta)
val swingAngle = MathHelper.sin(MathHelper.sqrt(handSwingProgress) * Math.PI.toFloat())
val bodyYawRadians = MathHelper.lerp(tickDelta, playerEntity.prevBodyYaw, playerEntity.bodyYaw) * (Math.PI.toFloat() / 180)
val sinBodyYaw = MathHelper.sin(bodyYawRadians).toDouble()
val cosBodyYaw = MathHelper.cos(bodyYawRadians).toDouble()
val horizontalOffset = armOffset.toDouble() * 0.35
if (dispatcher.gameOptions != null && !dispatcher.gameOptions.perspective.isFirstPerson || playerEntity !== MinecraftClient.getInstance().player) {
playerEyeYWorld = MathHelper.lerp(tickDelta.toDouble(), playerEntity.prevX, playerEntity.x) - cosBodyYaw * horizontalOffset - sinBodyYaw * 0.8
playerPosYWorld = playerEntity.prevY + playerEntity.standingEyeHeight.toDouble() + (playerEntity.y - playerEntity.prevY) * tickDelta.toDouble() - 0.45
playerPosZWorld = MathHelper.lerp(tickDelta.toDouble(), playerEntity.prevZ, playerEntity.z) - sinBodyYaw * horizontalOffset + cosBodyYaw * 0.8
eyeHeightOffset = if (playerEntity.isInSneakingPose) -0.1875f else 0.0f
} else {
playerPosXWorld = 960.0 / dispatcher.gameOptions.fov.value.toDouble()
var vec3d = dispatcher.camera.projection.getPosition(armOffset.toFloat() * 0.525f, -0.1f)
vec3d = vec3d.multiply(playerPosXWorld)
vec3d = vec3d.rotateY(swingAngle * 0.5f)
vec3d = vec3d.rotateX(-swingAngle * 0.7f)
playerEyeYWorld = MathHelper.lerp(tickDelta.toDouble(), playerEntity.prevX, playerEntity.getX()) + vec3d.x
playerPosYWorld = MathHelper.lerp(tickDelta.toDouble(), playerEntity.prevY, playerEntity.getY()) + vec3d.y
playerPosZWorld = MathHelper.lerp(tickDelta.toDouble(), playerEntity.prevZ, playerEntity.getZ()) + vec3d.z
eyeHeightOffset = playerEntity.getStandingEyeHeight()
}
playerPosXWorld = MathHelper.lerp(tickDelta.toDouble(), fishingBobberEntity.prevX, fishingBobberEntity.x)
val bobberPosY = MathHelper.lerp(tickDelta.toDouble(), fishingBobberEntity.prevY, fishingBobberEntity.y) + 0.25
val bobberPosZ = MathHelper.lerp(tickDelta.toDouble(), fishingBobberEntity.prevZ, fishingBobberEntity.z)
val deltaX = (playerEyeYWorld - playerPosXWorld).toFloat()
val deltaY = (playerPosYWorld - bobberPosY).toFloat() + eyeHeightOffset
val deltaZ = (playerPosZWorld - bobberPosZ).toFloat()
val vertexConsumer2 = vertexConsumerProvider.getBuffer(RenderLayer.getLineStrip())
val entry2 = matrixStack.peek()
for (lineIndex in 0..16) {
renderFishingLine(pokeRod.lineColor, deltaX, deltaY, deltaZ, vertexConsumer2, entry2, percentage(lineIndex, 16), percentage(lineIndex + 1, 16))
}
matrixStack.pop() // close main rendering transforms
super.render(fishingBobberEntity, elapsedPartialTicks, tickDelta, matrixStack, vertexConsumerProvider, light)
}
fun adjustBerry(berryBait: ItemStack): Pair<Double, Double> {
return when (Registries.ITEM.getId(berryBait.item).toString()) {
"wacan_berry" -> Pair(0.03, 0.0)
"cheri_berry" -> Pair(0.03, 0.0)
else -> Pair(0.0, 0.0)
}
}
companion object {
private val TEXTURE = cobblemonResource("textures/item/fishing/bobber_hook.png")
private val LAYER = RenderLayer.getEntityCutout(TEXTURE)
private fun vertex(buffer: VertexConsumer, matrix: Matrix4f, normalMatrix: Matrix3f, light: Int, x: Float, y: Float, u: Int, v: Int) {
buffer.vertex(matrix, x - 0.5f, y.toFloat() - 0.5f, 0.0f).color(255, 255, 255, 255).texture(u.toFloat(), v.toFloat()).overlay(OverlayTexture.DEFAULT_UV).light(light).normal(normalMatrix, 0.0f, 1.0f, 0.0f).next()
}
@JvmStatic
private fun percentage(value: Int, max: Int): Float {
return value.toFloat() / max.toFloat()
}
@JvmStatic
private fun renderFishingLine(color: String, deltaX: Float, deltaY: Float, deltaZ: Float, vertexBuffer: VertexConsumer, matrixEntry: MatrixStack.Entry, segmentStartFraction: Float, segmentEndFraction: Float) {
val colorObj = Color.decode(color)
// Calculate the starting X position of the current segment based on the start fraction
val startX = deltaX * segmentStartFraction
// Calculate the starting Y position of the current segment, adding a curvature effect and a base offset
var startY = deltaY * (segmentStartFraction * segmentStartFraction + segmentStartFraction) * 0.5f + 0.25f
// Calculate the starting Z position of the current segment based on the start fraction
val startZ = deltaZ * segmentStartFraction
// Calculate the change in X position from the start to the end of the current segment
var deltaXSegment = deltaX * segmentEndFraction - startX
// Calculate the change in Y position from the start to the end of the current segment, adjusting for curvature and base offset
var deltaYSegment = deltaY * (segmentEndFraction * segmentEndFraction + segmentEndFraction) * 0.5f + 0.25f - startY
// Calculate the change in Z position from the start to the end of the current segment
var deltaZSegment = deltaZ * segmentEndFraction - startZ
// Normalize the segment vector to use it for the normal vector in the vertex data
val length = MathHelper.sqrt(deltaXSegment * deltaXSegment + deltaYSegment * deltaYSegment + deltaZSegment * deltaZSegment)
deltaXSegment /= length
deltaYSegment /= length
deltaZSegment /= length
// New factor to adjust the curve so it ends lower
// This could be a simple linear adjustment or more complex based on your needs
val loweringFactor = 1.0f - segmentEndFraction // Becomes 0 at the start and approaches 1 towards the end
val additionalLowering = loweringFactor * 0.1f // Adjust this factor to control how much lower the end goes
// Apply the lowering adjustment
startY -= additionalLowering
// Add the vertex for the start of this segment
vertexBuffer.vertex(matrixEntry.positionMatrix, startX, startY, startZ).color(colorObj.red, colorObj.green, colorObj.blue, 255).normal(matrixEntry.normalMatrix, deltaXSegment, deltaYSegment, deltaZSegment).next()
}
}
override fun getTexture(entity: PokeRodFishingBobberEntity): Identifier {
return cobblemonResource("textures/item/fishing/bobber_hook.png")
}
}
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.item
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.item.interactive.PokerodItem
import net.minecraft.client.item.ClampedModelPredicateProvider
import net.minecraft.client.item.ModelPredicateProviderRegistry
import net.minecraft.client.world.ClientWorld
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.ItemStack
import net.minecraft.util.Identifier
object CobblemonModelPredicateRegistry {
fun registerPredicates() {
val rods = listOf(
CobblemonItems.AZURE_ROD,
CobblemonItems.BEAST_ROD,
CobblemonItems.CHERISH_ROD,
CobblemonItems.CITRINE_ROD,
CobblemonItems.DIVE_ROD,
CobblemonItems.DREAM_ROD,
CobblemonItems.DUSK_ROD,
CobblemonItems.FAST_ROD,
CobblemonItems.FRIEND_ROD,
CobblemonItems.GREAT_ROD,
CobblemonItems.HEAL_ROD,
CobblemonItems.HEAVY_ROD,
CobblemonItems.LEVEL_ROD,
CobblemonItems.LOVE_ROD,
CobblemonItems.LURE_ROD,
CobblemonItems.LUXURY_ROD,
CobblemonItems.MASTER_ROD,
CobblemonItems.MOON_ROD,
CobblemonItems.NEST_ROD,
CobblemonItems.NET_ROD,
CobblemonItems.PARK_ROD,
CobblemonItems.POKE_ROD,
CobblemonItems.PREMIER_ROD,
CobblemonItems.QUICK_ROD,
CobblemonItems.REPEAT_ROD,
CobblemonItems.ROSEATE_ROD,
CobblemonItems.SAFARI_ROD,
CobblemonItems.SLATE_ROD,
CobblemonItems.SPORT_ROD,
CobblemonItems.TIMER_ROD,
CobblemonItems.ULTRA_ROD,
CobblemonItems.VERDANT_ROD,
CobblemonItems.ANCIENT_AZURE_ROD,
CobblemonItems.ANCIENT_CITRINE_ROD,
CobblemonItems.ANCIENT_FEATHER_ROD,
CobblemonItems.ANCIENT_GIGATON_ROD,
CobblemonItems.ANCIENT_GREAT_ROD,
CobblemonItems.ANCIENT_HEAVY_ROD,
CobblemonItems.ANCIENT_IVORY_ROD,
CobblemonItems.ANCIENT_JET_ROD,
CobblemonItems.ANCIENT_LEADEN_ROD,
CobblemonItems.ANCIENT_ORIGIN_ROD,
CobblemonItems.ANCIENT_POKE_ROD,
CobblemonItems.ANCIENT_ROSEATE_ROD,
CobblemonItems.ANCIENT_SLATE_ROD,
CobblemonItems.ANCIENT_ULTRA_ROD,
CobblemonItems.ANCIENT_VERDANT_ROD,
CobblemonItems.ANCIENT_WING_ROD
)
rods.forEach { rod ->
ModelPredicateProviderRegistry.register(rod, Identifier("cast"), ClampedModelPredicateProvider { stack, world, entity, seed ->
if (entity == null) {
0.0f
} else {
val isMainHand = entity.mainHandStack == stack
var isOffHand = entity.offHandStack == stack
if (entity.mainHandStack.item is PokerodItem) {
isOffHand = false
}
if ((isMainHand || isOffHand) && entity is PlayerEntity && entity.fishHook != null) 1.0f else 0.0f
}
})
}
}
}
@@ -37,7 +37,7 @@ class JsonPose<T : Entity>(model: PoseableEntityModel<T>, json: JsonObject) {
it.environment.getQueryStruct().addFunctions(model.functions.functions)
}
val condition: ExpressionLike = json.singularToPluralList("condition").get("condition")?.normalizeToArray()?.map { it.asString }?.asExpressionLike() ?: "true".asExpressionLike()
val condition: ExpressionLike = json.singularToPluralList("condition").get("conditions")?.normalizeToArray()?.map { it.asString }?.asExpressionLike() ?: "true".asExpressionLike()
val poseName = json.get("poseName")?.asString ?: "pose"
val poseTypes = (json.get("poseTypes")?.asJsonArray?.map { name ->
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.client.render.models.blockbench
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.Entity
/**
* A storage of locator information for a model, attached to its [joint].
@@ -68,20 +69,45 @@ class LocatorAccess(
* Updates all of the locator states with the position at this current frame.
* This is the same logic as ModelPart uses, that's why we reuse ModelPart#rotate.
*/
fun update(matrixStack: MatrixStack, state: MutableMap<String, MatrixWrapper>) {
fun update(matrixStack: MatrixStack, entity: Entity, scale: Float, state: MutableMap<String, MatrixWrapper>, isRoot: Boolean = false) {
matrixStack.push()
joint.transform(matrixStack)
if (isRoot) {
matrixStack.push()
matrixStack.scale(-1F, -1F, 1F)
state.getOrPut("root") { MatrixWrapper() }.updateMatrix(matrixStack.peek().positionMatrix)
matrixStack.pop()
// Put in an approximation of the target locator. If the model has one defined,
// this will be overridden.
matrixStack.push()
matrixStack.translate(0.0, -entity.boundingBox.yLength / 2.0 / scale, -entity.width * 0.6 / scale)
matrixStack.scale(-1F, -1F, 1F)
state.getOrPut("target") { MatrixWrapper() }.updateMatrix(matrixStack.peek().positionMatrix)
state.getOrPut("special_attack") { MatrixWrapper() }.updateMatrix(matrixStack.peek().positionMatrix)
matrixStack.pop()
// If we have the entity, put in a "middle" locator for center of mass
// this will be overridden.
matrixStack.push()
matrixStack.translate(0.0, -entity.boundingBox.yLength / 2.0 / scale, 0.0)
matrixStack.scale(-1F, -1F, 1F)
state.getOrPut("middle") { MatrixWrapper() }.updateMatrix(matrixStack.peek().positionMatrix)
matrixStack.pop()
}
for ((name, locator) in locators) {
matrixStack.push()
locator.transform(matrixStack)
matrixStack.scale(1F, -1F, 1F)
matrixStack.scale(-1F, -1F, 1F)
state.getOrPut(name) { MatrixWrapper() }.updateMatrix(matrixStack.peek().positionMatrix)
matrixStack.pop()
}
children.forEach {
it.update(matrixStack, state)
it.update(matrixStack, entity, scale, state, isRoot = false)
}
matrixStack.pop()
@@ -261,6 +261,41 @@ abstract class PoseableEntityModel<T : Entity>(
)
)
}
.addFunction("bedrock_primary_quirk") { params ->
val animationGroup = params.getString(0)
val animationNames = params.get<MoValue>(1)?.let { if (it is ArrayStruct) it.map.values.map { it.asString() } else listOf(it.asString()) } ?: listOf()
val minSeconds = params.getDoubleOrNull(2) ?: 8F
val maxSeconds = params.getDoubleOrNull(3) ?: 30F
val loopTimes = params.getDoubleOrNull(4)?.toInt() ?: 1
val excludedLabels = mutableSetOf<String>()
var curve: WaveFunction = { t ->
if (t < 0.1) {
t * 10
} else if (t < 0.9) {
1F
} else {
1F
}
}
for (index in 5 until params.params.size) {
val param = params.get<MoValue>(index)
if (param is ObjectValue<*>) {
curve = param.obj as WaveFunction
continue
}
val label = params.getString(index) ?: continue
excludedLabels.add(label)
}
ObjectValue(
quirk(
secondsBetweenOccurrences = minSeconds.toFloat() to maxSeconds.toFloat(),
condition = { true },
loopTimes = 1..loopTimes,
animation = { PrimaryAnimation(bedrockStateful(animationGroup, animationNames.random()), excludedLabels = excludedLabels, curve = curve) }
)
)
}
@Transient
val runtime = MoLangRuntime().setup().setupClient().also { it.environment.getQueryStruct().addFunctions(functions.functions) }
@@ -788,7 +823,6 @@ abstract class PoseableEntityModel<T : Entity>(
matrixStack.push()
matrixStack.scale(-1F, -1F, 1F)
scale = entity.pokemon.form.baseScale * entity.pokemon.scaleModifier * (entity.delegate as PokemonClientDelegate).entityScaleModifier
matrixStack.scale(scale, scale, scale)
} else if (entity is EmptyPokeBallEntity) {
matrixStack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(entity.yaw))
@@ -803,27 +837,14 @@ abstract class PoseableEntityModel<T : Entity>(
matrixStack.scale(1F, -1F, 1F)
}
matrixStack.push()
matrixStack.scale(1F, -1F, 1F)
val states = state.locatorStates
states.getOrPut("root") { MatrixWrapper() }.updateMatrix(matrixStack.peek().positionMatrix)
matrixStack.pop()
if (isForLivingEntityRenderer) {
// Standard living entity offset, only God knows why Mojang did this.
matrixStack.translate(0.0, -1.5, 0.0)
}
// If we have the entity, put in an approximation of the target locator. If the model has one defined,
// this will be overridden.
matrixStack.push()
matrixStack.translate(0.0, -entity.boundingBox.yLength / 2.0 / scale + 1.5F, -entity.width * 0.6 / scale)
matrixStack.scale(1F, -1F, 1F)
states.getOrPut("target") { MatrixWrapper() }.updateMatrix(matrixStack.peek().positionMatrix)
states.getOrPut("special_attack") { MatrixWrapper() }.updateMatrix(matrixStack.peek().positionMatrix)
matrixStack.pop()
locatorAccess.update(matrixStack, states)
locatorAccess.update(matrixStack, entity, scale, states, isRoot = true)
}
fun ModelPart.translation(
@@ -135,6 +135,7 @@ abstract class PoseableEntityState<T : Entity> : Schedulable {
val storm = ParticleStorm(
effect = effect,
entity = entity,
matrixWrapper = matrixWrapper,
world = world,
runtime = particleRuntime,
@@ -39,6 +39,7 @@ import com.google.gson.annotations.SerializedName
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
import java.util.function.Supplier
import net.minecraft.client.model.ModelPart
import net.minecraft.util.math.Vec3d
/**
@@ -49,7 +50,8 @@ import net.minecraft.util.math.Vec3d
* @author Hiroku
* @since August 7th, 2022
*/
class JsonPokemonPoseableModel(override val rootPart: Bone) : PokemonPoseableModel(), HeadedFrame {
class JsonPokemonPoseableModel(rootPart: Bone) : PokemonPoseableModel(), HeadedFrame {
override val rootPart = (rootPart as ModelPart).children.entries.first().let { rootPart.registerChildWithAllChildren(it.key) }
companion object {
val gson = GsonBuilder()
.setPrettyPrinting()
@@ -130,6 +132,7 @@ class JsonPokemonPoseableModel(override val rootPart: Bone) : PokemonPoseableMod
override fun createInstance(type: Type): JsonPokemonPoseableModel {
return JsonPokemonPoseableModel(modelPart!!).also {
model = it
modelPart as ModelPart
it.loadAllNamedChildren(modelPart!!)
}
}
@@ -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.client.render.models.blockbench.pokemon.gen2
import com.cobblemon.mod.common.client.render.models.blockbench.animation.BipedWalkAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.frame.BipedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.frame.HeadedFrame
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPose
import com.cobblemon.mod.common.client.render.models.blockbench.pokemon.PokemonPoseableModel
import com.cobblemon.mod.common.entity.PoseType
import net.minecraft.client.model.ModelPart
import net.minecraft.util.math.Vec3d
class SmeargleModel(root: ModelPart) : PokemonPoseableModel(), HeadedFrame, BipedFrame {
override val rootPart = root.registerChildWithAllChildren("smeargle")
override val head = getPart("head")
/* No arm swinging because it's holding its tail */
override val leftLeg = getPart("leg_left")
override val rightLeg = getPart("leg_right")
override var portraitScale = 1.29F
override var portraitTranslation = Vec3d(-0.03, 0.96, 0.0)
override var profileScale = 0.67F
override var profileTranslation = Vec3d(0.05, 0.8, 0.0)
lateinit var standing: PokemonPose
lateinit var walk: PokemonPose
//override val cryAnimation = CryProvider { _, _ -> bedrockStateful("smeargle", "cry") }
override fun registerPoses() {
val blink = quirk { bedrockStateful("smeargle", "blink") }
standing = registerPose(
poseName = "standing",
poseTypes = PoseType.STATIONARY_POSES + PoseType.UI_POSES + PoseType.SLEEP,
quirks = arrayOf(blink),
idleAnimations = arrayOf(
singleBoneLook(),
bedrock("smeargle", "ground_idle")
)
)
walk = registerPose(
poseName = "walk",
poseTypes = PoseType.MOVING_POSES,
quirks = arrayOf(blink),
idleAnimations = arrayOf(
singleBoneLook(),
bedrock("smeargle", "ground_idle"),
BipedWalkAnimation(this, periodMultiplier = 0.6F, amplitudeMultiplier = 0.9F)
)
)
}
}
@@ -47,7 +47,6 @@ class ShedinjaModel (root: ModelPart) : PokemonPoseableModel() {
poseName = "standing",
poseTypes = PoseType.UI_POSES + PoseType.STAND,
transformTicks = 10,
condition = { !it.isBattling },
quirks = arrayOf(blink),
idleAnimations = arrayOf(
bedrock("shedinja", "ground_idle")
@@ -20,7 +20,7 @@ import net.minecraft.util.math.Vec3d
class WailordModel(root: ModelPart) : PokemonPoseableModel() {
override val rootPart = root.registerChildWithAllChildren("wailord")
override var portraitScale = 0.45F
override var portraitTranslation = Vec3d(-0.38, 0.8, 0.0)
override var portraitTranslation = Vec3d(-0.38, 0.8, 6.69)
override var profileScale = 0.25F
override var profileTranslation = Vec3d(0.0, 1.2, -10.0)
@@ -21,7 +21,7 @@ class TyrantrumModel (root: ModelPart) : PokemonPoseableModel(), HeadedFrame {
override val head = getPart("head")
override var portraitTranslation = Vec3d(-0.86, 2.0, 0.0)
override var portraitTranslation = Vec3d(-0.86, 2.0, 5.0)
override var portraitScale = 0.59F
override var profileTranslation = Vec3d(0.02, 1.28, -10.0)
@@ -714,6 +714,7 @@ object PokemonModelRepository : VaryingModelRepository<PokemonEntity, PokemonPos
inbuilt("rowlet_hisui_bias", ::RowletHisuiBiasModel)
inbuilt("dartrix_hisui_bias", ::DartrixHisuiBiasModel)
inbuilt("decidueye_hisuian", ::DecidueyeHisuianModel)
inbuilt("smeargle", ::SmeargleModel)
}
override fun loadJsonPoser(json: String): (Bone) -> PokemonPoseableModel {
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.item.ability.AbilityChanger
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker.after
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.api.scheduling.taskBuilder
import com.cobblemon.mod.common.api.text.green
@@ -25,10 +26,13 @@ import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.entity.generic.GenericBedrockEntity
import com.cobblemon.mod.common.net.messages.client.animation.PlayPoseableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeStartedPacket
import com.cobblemon.mod.common.trade.ActiveTrade
import com.cobblemon.mod.common.trade.DummyTradeParticipant
import com.cobblemon.mod.common.trade.PlayerTradeParticipant
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.util.toPokemon
import com.google.gson.GsonBuilder
@@ -61,6 +65,22 @@ object TestCommand {
}
try {
val player = context.source.entity as ServerPlayerEntity
val evolutionEntity = GenericBedrockEntity(world = player.world)
evolutionEntity?.apply {
category = cobblemonResource("evolution")
colliderHeight = 1.5F
colliderWidth = 1.5F
scale = 1F
syncAge = true // Otherwise particle animation will be starting from zero even if you come along partway through
setPosition(player.x, player.y, player.z + 4)
}
player.world.spawnEntity(evolutionEntity)
after(seconds = 0.5F) {
player.sendPacket(PlayPoseableAnimationPacket(evolutionEntity.id, setOf("evolution:animation.evolution.evolution"), emptySet()))
}
// readBerryDataFromCSV()
// this.testClosestBattle(context)
@@ -17,6 +17,8 @@ import com.cobblemon.mod.common.api.data.DataProvider
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.dialogue.Dialogues
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.fishing.FishingBaits
import com.cobblemon.mod.common.api.fishing.PokeRods
import com.cobblemon.mod.common.api.fossil.Fossils
import com.cobblemon.mod.common.api.fossil.NaturalMaterials
import com.cobblemon.mod.common.api.moves.Moves
@@ -74,7 +76,9 @@ object CobblemonDataProvider : DataProvider {
this.register(Fossils)
CobblemonSpawnPools.load()
this.register(PokeRods)
this.register(Berries)
this.register(FishingBaits)
PlatformEvents.SERVER_PLAYER_LOGOUT.subscribe {
synchronizedPlayerIds.remove(it.player.uuid)
@@ -0,0 +1,721 @@
/*
* 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.entity.fishing
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonEntities
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.fishing.FishingBait
import com.cobblemon.mod.common.api.fishing.FishingBaits
import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.EntitySpawnResult
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawnCause
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.types.tera.TeraTypes
import com.cobblemon.mod.common.battles.BattleBuilder
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.interactive.PokerodItem
import com.cobblemon.mod.common.loot.CobblemonLootTables
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.toBlockPos
import kotlin.math.sqrt
import net.minecraft.advancement.criterion.Criteria
import net.minecraft.block.Blocks
import net.minecraft.entity.Entity
import net.minecraft.entity.EntityType
import net.minecraft.entity.ExperienceOrbEntity
import net.minecraft.entity.ItemEntity
import net.minecraft.entity.MovementType
import net.minecraft.entity.data.DataTracker
import net.minecraft.entity.data.TrackedData
import net.minecraft.entity.data.TrackedDataHandlerRegistry
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.projectile.FishingBobberEntity
import net.minecraft.entity.projectile.ProjectileUtil
import net.minecraft.item.ItemStack
import net.minecraft.loot.context.LootContextParameterSet
import net.minecraft.loot.context.LootContextParameters
import net.minecraft.loot.context.LootContextTypes
import net.minecraft.particle.DefaultParticleType
import net.minecraft.particle.ParticleTypes
import net.minecraft.registry.Registries
import net.minecraft.registry.tag.FluidTags
import net.minecraft.registry.tag.ItemTags
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.server.world.ServerWorld
import net.minecraft.sound.SoundCategory
import net.minecraft.stat.Stats
import net.minecraft.util.Hand
import net.minecraft.util.Identifier
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.hit.EntityHitResult
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import net.minecraft.util.math.Vec3d
import net.minecraft.util.math.random.Random
import net.minecraft.world.World
import kotlin.math.cos
import kotlin.math.sin
class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity>, world: World) : FishingBobberEntity(type, world) {
private val velocityRandom = Random.create()
private var caughtFish = false
private var outOfOpenWaterTicks = 0
// todo is this needed?
//val HOOK_ENTITY_ID = DataTracker.registerData(PokeRodFishingBobberEntity::class.java, TrackedDataHandlerRegistry.INTEGER)
//private val CAUGHT_FISH = DataTracker.registerData(PokeRodFishingBobberEntity::class.java, TrackedDataHandlerRegistry.BOOLEAN)
// todo if so replace these later
//var HOOK_ENTITY_ID = 0
//var CAUGHT_FISH = false
private var removalTimer = 0
private var hookCountdown = 0
private var waitCountdown = 0
private var fishTravelCountdown = 0
private var fishAngle = 0f
var inOpenWater = true
private var hookedEntity: Entity? = null
var state = State.FLYING
private var luckOfTheSeaLevel = 0
private var lureLevel = 0
private var typeCaught= "ITEM"
private var chosenBucket = Cobblemon.bestSpawner.config.buckets[0] // default to first rarity bucket
private val pokemonSpawnChance = 85 // chance a Pokemon will be fished up % out of 100
var pokeRodId: Identifier? = null
var lineColor: String = "000000" // default line color is black
var usedRod: Identifier? = null
var bobberBait: ItemStack = ItemStack.EMPTY
constructor(thrower: PlayerEntity, pokeRodId: Identifier, bait: ItemStack, world: World, luckOfTheSea: Int, lure: Int) : this(CobblemonEntities.POKE_BOBBER, world) {
owner = thrower
luckOfTheSeaLevel = luckOfTheSea
lureLevel = lure
this.pokeRodId = pokeRodId
this.bobberBait = bait
dataTracker.set(POKEROD_ID, pokeRodId.toString())
dataTracker.set(POKEBOBBER_BAIT, bobberBait)
dataTracker.set(HOOK_ENTITY_ID, 0)
dataTracker.set(CAUGHT_FISH, false)
this.usedRod = pokeRodId
val throwerPitch = thrower.pitch
val throwerYaw = thrower.yaw
val cosYaw = MathHelper.cos(-throwerYaw * 0.017453292f - 3.1415927f)
val sinYaw = MathHelper.sin(-throwerYaw * 0.017453292f - 3.1415927f)
val cosPitch = -MathHelper.cos(-throwerPitch * 0.017453292f)
val sinPitch = MathHelper.sin(-throwerPitch * 0.017453292f)
val posX = thrower.x - sinYaw.toDouble() * 0.3
val posY = thrower.eyeY
val posZ = thrower.z - cosYaw.toDouble() * 0.3
this.refreshPositionAndAngles(posX, posY, posZ, throwerYaw, throwerPitch)
var vec3d = Vec3d((-sinYaw).toDouble(), MathHelper.clamp(-(sinPitch / cosPitch), -5.0f, 5.0f).toDouble(), (-cosYaw).toDouble())
val m = vec3d.length()
vec3d = vec3d.multiply(0.6 / m + random.nextTriangular(0.5, 0.0103365), 0.6 / m + random.nextTriangular(0.5, 0.0103365), 0.6 / m + random.nextTriangular(0.5, 0.0103365))
velocity = vec3d
yaw = (MathHelper.atan2(vec3d.x, vec3d.z) * 57.2957763671875).toFloat()
pitch = (MathHelper.atan2(vec3d.y, vec3d.horizontalLength()) * 57.2957763671875).toFloat()
prevYaw = yaw
prevPitch = pitch
}
override fun initDataTracker() {
this.dataTracker.startTracking(HOOK_ENTITY_ID, 0)
this.dataTracker.startTracking(CAUGHT_FISH, false)
this.dataTracker.startTracking(POKEROD_ID, "")
this.dataTracker.startTracking(POKEBOBBER_BAIT, ItemStack.EMPTY)
}
override fun onTrackedDataSet(data: TrackedData<*>) {
if (HOOK_ENTITY_ID == data) {
val i = getDataTracker().get(HOOK_ENTITY_ID) as Int
this.hookedEntity = if (i > 0) world.getEntityById(i - 1) else null
}
if (CAUGHT_FISH == data) {
this.caughtFish = (getDataTracker().get(CAUGHT_FISH) as Boolean)
if (this.caughtFish) {
this.setVelocity(velocity.x, (-0.4f * MathHelper.nextFloat(this.velocityRandom, 0.3f, 0.5f)).toDouble(), velocity.z)
}
}
super.onTrackedDataSet(data)
}
fun calculateMinMaxCountdown(weight: Float): Pair<Int, Int> {
// Constants for the target min and max values at weight extremes
val minAtMaxWeight = 20
val maxAtMaxWeight = 40
val minAtMinWeight = 15
val maxAtMinWeight = 20
// Calculate factors for min and max based on the weight
val minFactor = ((minAtMaxWeight - minAtMinWeight) / 100f) * weight + minAtMinWeight
val maxFactor = ((maxAtMaxWeight - maxAtMinWeight) / 100f) * weight + maxAtMinWeight
// Ensure min and max are within the needed bounds
val min = minFactor.toInt().coerceIn(minAtMinWeight, minAtMaxWeight)
val max = maxFactor.toInt().coerceIn(maxAtMinWeight, maxAtMaxWeight)
return Pair(min, max)
}
fun chooseAdjustedSpawnBucket(buckets: List<SpawnBucket>, luckOfTheSeaLevel: Int): SpawnBucket {
val baseIncreases = listOf(5.0F, 1.0F, 0.2F) // Base increases for the first three buckets beyond the first
val adjustedWeights = buckets.mapIndexed { index, bucket ->
if (index == 0) {
// Placeholder, will be recalculated
0.0F
} else {
val increase = if (index < baseIncreases.size) baseIncreases[index] else baseIncreases.last() + (index - baseIncreases.size + 1) * 0.15F
bucket.weight + increase * luckOfTheSeaLevel
}
}.toMutableList()
// Recalculate the first bucket's weight to ensure the total is 100%
val totalAdjustedWeight = adjustedWeights.sum() - adjustedWeights[0] // Corrected to ensure the list contains Floats
adjustedWeights[0] = 100.0F - totalAdjustedWeight + buckets[0].weight
// Random selection based on adjusted weights
val weightSum = adjustedWeights.sum()
val chosenSum = kotlin.random.Random.nextDouble(weightSum.toDouble()).toFloat() // Ensure usage of Random from kotlin.random package
var sum = 0.0F
adjustedWeights.forEachIndexed { index, weight ->
sum += weight
if (sum >= chosenSum) {
return buckets[index]
}
}
return buckets.first() // Fallback
}
/*// make more generic version for other enchants. Pass in id of enchant
fun getEnchantLevel(enchantment: Enchantment): Int {
var pokerodEnchantments: NbtList? = null
if (owner?.isPlayer == true) {
pokerodEnchantments = (owner as PlayerEntity).mainHandStack?.enchantments
}
val luckOfTheSeaEnchantment = pokerodEnchantments?.filter {
if (it is NbtCompound)
if (it.getString("id") == Registries.ENCHANTMENT.getEntry(enchantment).key.toString())
return@filter true
return@filter false
}?.first()
return (luckOfTheSeaEnchantment as? NbtCompound)?.getShort("lvl")?.toInt() ?: 0
}*/
fun isOpenOrWaterAround(pos: BlockPos): Boolean {
var positionType = PositionType.INVALID
for (i in -1..2) {
val positionType2 = this.getPositionType(pos.add(-2, i, -2), pos.add(2, i, 2))
when (positionType2) {
PositionType.INVALID -> return false
PositionType.ABOVE_WATER -> if (positionType == PositionType.INVALID) {
return false
}
PositionType.INSIDE_WATER -> if (positionType == PositionType.ABOVE_WATER) {
return false
}
else -> return false
}
positionType = positionType2
}
return true
}
/*private fun getPositionType(start: BlockPos, end: BlockPos): PokeRodFishingBobberEntity.PositionType? {
return BlockPos.stream(start, end).map { pos: BlockPos? -> this.getPositionType(pos!!) }.reduce { positionType: PokeRodFishingBobberEntity.PositionType, positionType2: PokeRodFishingBobberEntity.PositionType -> if (positionType == positionType2) positionType else PokeRodFishingBobberEntity.PositionType.INVALID } as ((PositionType?, PositionType?) -> PositionType?)?.orElse(PokeRodFishingBobberEntity.PositionType.INVALID) as PokeRodFishingBobberEntity.PositionType
}*/
private fun getPositionType(start: BlockPos, end: BlockPos): PositionType? {
return BlockPos.stream(start, end)
.map { pos -> this.getPositionType(pos) }
.reduce { positionType, positionType2 ->
if (positionType == positionType2) positionType else PositionType.INVALID
}.orElse(PositionType.INVALID)
}
private fun getPositionType(pos: BlockPos): PositionType {
val blockState = world.getBlockState(pos)
return if (!blockState.isAir && !blockState.isOf(Blocks.LILY_PAD)) {
val fluidState = blockState.fluidState
if (fluidState.isIn(FluidTags.WATER) && fluidState.isStill && blockState.getCollisionShape(world, pos).isEmpty) PositionType.INSIDE_WATER else PositionType.INVALID
} else {
PositionType.ABOVE_WATER
}
}
enum class PositionType {
ABOVE_WATER,
INSIDE_WATER,
INVALID
}
private fun setPlayerFishHook(fishingBobber: FishingBobberEntity?) {
val playerEntity = this.playerOwner
if (playerEntity != null) {
playerEntity.fishHook = fishingBobber
}
}
enum class State {
FLYING,
HOOKED_IN_ENTITY,
BOBBING
}
// todo custom behavior for fishing logic
private fun tickFishingLogic(pos: BlockPos) {
val serverWorld = world as ServerWorld
var i = 1
val blockPos = pos.up()
if (random.nextFloat() < 0.25f && world.hasRain(blockPos)) {
++i
}
if (random.nextFloat() < 0.5f && !world.isSkyVisible(blockPos)) {
--i
}
if (this.hookCountdown > 0) {
--this.hookCountdown
if (this.hookCountdown <= 0) {
this.waitCountdown = 0
this.fishTravelCountdown = 0
//this.CAUGHT_FISH = false
getDataTracker().set(CAUGHT_FISH, false)
}
} else if (this.fishTravelCountdown > 0) { // create a fish trail that leads to the bobber visually
this.fishTravelCountdown -= i
if (this.fishTravelCountdown > 0) {
this.fishAngle += random.nextTriangular(0.0, 9.188).toFloat()
val f = this.fishAngle * (Math.PI.toFloat() / 180)
val g = MathHelper.sin(f)
val h = MathHelper.cos(f)
val offsetX = this.x + (g * this.fishTravelCountdown.toFloat() * 0.1f).toDouble()
val offsetY = (MathHelper.floor(this.y).toFloat() + 1.0f).toDouble()
val j = this.z + (h * this.fishTravelCountdown.toFloat() * 0.1f).toDouble()
val blockState = serverWorld.getBlockState(BlockPos.ofFloored(offsetX, offsetY - 1.0, j))
//val blockState = serverWorld.getBlockState(BlockPos.ofFloored(offsetX, (MathHelper.floor(this.y).toFloat() + 1.0f).toDouble().also { offsetY = it } - 1.0, this.z + (h * this.fishTravelCountdown.toFloat() * 0.1f).toDouble().also { j = it }))
if (blockState.isOf(Blocks.WATER)) {
if (random.nextFloat() < 0.15f) {
//serverWorld.spawnParticles(ParticleTypes.BUBBLE, this.x, this.y, this.z, 3, g.toDouble(), 0.1, h.toDouble(), 0.0)
serverWorld.spawnParticles(ParticleTypes.BUBBLE, offsetX, offsetY - 0.1, j, 1, g.toDouble(), 0.1, h.toDouble(), 0.0)
}
val k = g * 0.04f
val l = h * 0.04f
serverWorld.spawnParticles(ParticleTypes.FISHING, offsetX, offsetY, j, 0, l.toDouble(), 0.01, -k.toDouble(), 1.0)
serverWorld.spawnParticles(ParticleTypes.FISHING, offsetX, offsetY, j, 0, -l.toDouble(), 0.01, k.toDouble(), 1.0)
}
} else {
//playSound(SoundEvents.ENTITY_FISHING_BOBBER_SPLASH, 0.25f, 1.0f + (random.nextFloat() - random.nextFloat()) * 0.4f)
// play bobber hook notification sound
world.playSound(null, this.blockPos, CobblemonSounds.FISHING_NOTIFICATION, SoundCategory.BLOCKS, 1.0F, 1.0F)
val m = this.y + 0.5
serverWorld.spawnParticles(ParticleTypes.BUBBLE, this.x, m, this.z, (1.0f + this.width * 20.0f).toInt(), this.width.toDouble(), 0.0, this.width.toDouble(), 0.2)
serverWorld.spawnParticles(ParticleTypes.FISHING, this.x, m, this.z, (1.0f + this.width * 20.0f).toInt(), this.width.toDouble(), 0.0, this.width.toDouble(), 0.2)
// check for chance to catch pokemon based on the bait
if (MathHelper.nextInt(random, 0, 100) < getPokemonSpawnChance(bobberBait)) {
this.typeCaught = "POKEMON"
val buckets = Cobblemon.bestSpawner.config.buckets
// choose a spawn bucket according to weights no matter how many there are
chosenBucket = chooseAdjustedSpawnBucket(buckets, luckOfTheSeaLevel)
val reactionMinMax = calculateMinMaxCountdown(chosenBucket.weight)
// set the hook reaction time to be based off the rarity of the bucket chosen
this.hookCountdown = MathHelper.nextInt(random, reactionMinMax.first, reactionMinMax.second)
println("Player hooked a Pokemon from the bucket: " + chosenBucket.name)
}
else {
// todo caught item
this.typeCaught = "ITEM"
this.hookCountdown = MathHelper.nextInt(random, 20, 40)
}
//this.hookCountdown = MathHelper.nextInt(random, 20, 40)
getDataTracker().set(CAUGHT_FISH, true)
//this.CAUGHT_FISH = true
}
} else if (this.waitCountdown > 0) {
this.waitCountdown -= i
var f = 0.15f
if (this.waitCountdown < 20) {
f += (20 - this.waitCountdown).toFloat() * 0.05f
} else if (this.waitCountdown < 40) {
f += (40 - this.waitCountdown).toFloat() * 0.02f
} else if (this.waitCountdown < 60) {
f += (60 - this.waitCountdown).toFloat() * 0.01f
}
if (random.nextFloat() < f) {
val g = MathHelper.nextFloat(this.random, 0.0f, 360.0f) * 0.017453292f
val h = MathHelper.nextFloat(this.random, 25.0f, 60.0f)
val d = this.x + (MathHelper.sin(g) * h).toDouble() * 0.1 // X
val e = (MathHelper.floor(this.y).toFloat() + 1.0f).toDouble() // Y
val j = this.z + (MathHelper.cos(g) * h).toDouble() * 0.1 // randomized Z value
val blockState = serverWorld.getBlockState(BlockPos.ofFloored(d, e - 1.0, j))
if (blockState.isOf(Blocks.WATER)) {
serverWorld.spawnParticles<DefaultParticleType>(ParticleTypes.SPLASH, d, e, j, 2 + random.nextInt(2), 0.10000000149011612, 0.0, 0.10000000149011612, 0.0)
}
}
if (this.waitCountdown <= 0) {
this.fishAngle = MathHelper.nextFloat(random, 0.0f, 360.0f)
this.fishTravelCountdown = MathHelper.nextInt(random, 20, 80)
}
} else {
// set the time it takes to wait for a hooked item or pokemon
this.waitCountdown = MathHelper.nextInt(random, 100, 600)
this.waitCountdown -= this.lureLevel * 20 * 5
// check for the bait on the hook and see if the waitCountdown is reduced
this.waitCountdown = alterBiteTimeAttempt(this.waitCountdown, this.bobberBait)
}
}
private fun removeIfInvalid(player: PlayerEntity): Boolean {
val itemStack = player.mainHandStack
val itemStack2 = player.offHandStack
val bl = Registries.ITEM[this.usedRod] == itemStack.item //(itemStack.item is PokerodItem) // todo make this work again so the line breaks when you swap items
val bl2 = Registries.ITEM[this.usedRod] == itemStack2.item //(itemStack2.item is PokerodItem) // todo make this work again so the line breaks when you swap items
if (player.isRemoved || !player.isAlive || !bl && !bl2 || this.squaredDistanceTo(player) > 1024.0) {
discard()
return true
}
return false
}
private fun checkForCollision() {
val hitResult = ProjectileUtil.getCollision(this) { entity: Entity -> this.canHit(entity) }
onCollision(hitResult)
}
override fun canHit(entity: Entity): Boolean {
return super.canHit(entity) || entity.isAlive && entity is ItemEntity
}
override fun onEntityHit(entityHitResult: EntityHitResult) {
//super.onEntityHit(entityHitResult) // this calls the function in FishingBobberEntity which has null values which causes crash I think
// ProjectileEntity.onEntityHit(entityHitResult) // this is protected and cannot be reached
if (!world.isClient) {
this.updateHookedEntityId(entityHitResult.entity)
}
}
override fun onBlockHit(blockHitResult: BlockHitResult) {
super.onBlockHit(blockHitResult)
this.velocity = velocity.normalize().multiply(blockHitResult.squaredDistanceTo(this))
}
private fun updateHookedEntityId(entity: Entity?) {
this.hookedEntity = entity
getDataTracker().set(HOOK_ENTITY_ID, if (entity == null) 0 else entity.id + 1)
//this.HOOK_ENTITY_ID = if (entity == null) 0 else entity.id + 1
}
override fun tick() {
velocityRandom.setSeed(getUuid().leastSignificantBits xor world.time)
//super.tick()
val playerEntity = this.playerOwner
if (playerEntity == null) {
discard()
} else if (world.isClient || !removeIfInvalid(playerEntity)) {
if (this.isOnGround) {
++removalTimer
if (removalTimer >= 1200) {
discard()
return
}
} else {
removalTimer = 0
}
var f = 0.0f
val blockPos = blockPos
val fluidState = world.getFluidState(blockPos)
if (fluidState.isIn(FluidTags.WATER)) {
f = fluidState.getHeight(world, blockPos)
}
val bl = f > 0.0f
if (state == State.FLYING) {
if (hookedEntity != null) {
velocity = Vec3d.ZERO
state = State.HOOKED_IN_ENTITY
return
}
if (bl) {
velocity = velocity.multiply(0.3, 0.2, 0.3)
state = State.BOBBING
return
}
checkForCollision()
} else {
if (state == State.HOOKED_IN_ENTITY) {
if (hookedEntity != null) {
if (!hookedEntity!!.isRemoved && hookedEntity!!.world.registryKey === world.registryKey) {
this.setPosition(hookedEntity!!.x, hookedEntity!!.getBodyY(0.8), hookedEntity!!.z)
} else {
updateHookedEntityId(null as Entity?)
state = State.FLYING
}
}
return
}
if (state == State.BOBBING) {
val vec3d = velocity
var d = this.y + vec3d.y - blockPos.y.toDouble() - f.toDouble()
if (Math.abs(d) < 0.01) {
d += Math.signum(d) * 0.1
}
this.setVelocity(vec3d.x * 0.9, vec3d.y - d * random.nextFloat().toDouble() * 0.2, vec3d.z * 0.9)
inOpenWater = if (hookCountdown <= 0 && fishTravelCountdown <= 0) {
true
} else {
inOpenWater && outOfOpenWaterTicks < 10 && isOpenOrWaterAround(blockPos)
}
if (bl) {
outOfOpenWaterTicks = Math.max(0, outOfOpenWaterTicks - 1)
if (caughtFish) {
velocity = velocity.add(0.0, -0.1 * velocityRandom.nextFloat().toDouble() * velocityRandom.nextFloat().toDouble(), 0.0)
}
if (!world.isClient) {
tickFishingLogic(blockPos)
}
} else {
outOfOpenWaterTicks = Math.min(10, outOfOpenWaterTicks + 1)
}
}
}
if (!fluidState.isIn(FluidTags.WATER)) {
velocity = velocity.add(0.0, -0.03, 0.0)
}
move(MovementType.SELF, velocity)
this.updateRotation()
if (state == State.FLYING && (this.isOnGround || horizontalCollision)) {
velocity = Vec3d.ZERO
}
velocity = velocity.multiply(0.92)
refreshPosition()
}
}
override fun use(usedItem: ItemStack): Int {
val playerEntity = this.playerOwner
return if (!world.isClient && playerEntity != null && !removeIfInvalid(playerEntity)) {
var i = 0
if (this.hookedEntity != null) {
pullHookedEntity(this.hookedEntity)
Criteria.FISHING_ROD_HOOKED.trigger(playerEntity as ServerPlayerEntity?, usedItem, this, emptyList())
world.sendEntityStatus(this, 31.toByte())
i = if (this.hookedEntity is ItemEntity) 3 else 5
} else if (this.hookCountdown > 0) {
// check if thing caught was an item
if (this.typeCaught == "ITEM") {
val lootContextParameterSet = LootContextParameterSet.Builder(world as ServerWorld).add(LootContextParameters.ORIGIN, pos).add(LootContextParameters.TOOL, usedItem).add(LootContextParameters.THIS_ENTITY, this).luck(this.luckOfTheSeaLevel.toFloat() + playerEntity.luck).build(LootContextTypes.FISHING)
val lootTable = world.server!!.lootManager.getLootTable(CobblemonLootTables.FISHING_GAMEPLAY)
val list: List<ItemStack> = lootTable.generateLoot(lootContextParameterSet)
Criteria.FISHING_ROD_HOOKED.trigger(playerEntity as ServerPlayerEntity?, usedItem, this, list)
val var7: Iterator<*> = list.iterator()
while (var7.hasNext()) {
val itemStack = var7.next() as ItemStack
val itemEntity = ItemEntity(world, this.x, this.y, this.z, itemStack)
val d = playerEntity.getX() - this.x
val e = playerEntity.getY() - this.y
val f = playerEntity.getZ() - this.z
itemEntity.setVelocity(d * 0.1, e * 0.1 + sqrt(sqrt(d * d + e * e + f * f)) * 0.08, f * 0.1)
world.spawnEntity(itemEntity)
playerEntity.getWorld().spawnEntity(ExperienceOrbEntity(playerEntity.getWorld(), playerEntity.getX(), playerEntity.getY() + 0.5, playerEntity.getZ() + 0.5, random.nextInt(6) + 1))
if (itemStack.isIn(ItemTags.FISHES)) {
playerEntity.increaseStat(Stats.FISH_CAUGHT, 1)
}
}
i = 1
}
else { // logic for spawning Pokemon using rarity
println("Player reeled in a Pokemon from the bucket: " + chosenBucket.name)
val bobberOwner = playerOwner as ServerPlayerEntity
// spawn the pokemon from the chosen bucket at the bobber's location
spawnPokemonFromFishing(bobberOwner, chosenBucket, bobberBait)
val serverWorld = world as ServerWorld
val g = MathHelper.nextFloat(random, 0.0f, 360.0f) * (Math.PI.toFloat() / 180)
val h = MathHelper.nextFloat(random, 25.0f, 60.0f)
val partX = this.x + (MathHelper.sin(g) * h).toDouble() * 0.1
serverWorld.spawnParticles(ParticleTypes.SPLASH, partX, this.y, this.z, 6 + random.nextInt(4), 0.0, 0.2, 0.0, 0.0)
// spawn test particle when reeling in pokemon
val particle = Identifier("cobblemon:impact_water")
//SpawnSnowstormParticlePacket(particle, this.pos)
SpawnSnowstormEntityParticlePacket(particle,this.id)
playerEntity.world.spawnEntity(ExperienceOrbEntity(playerEntity.world, playerEntity.x, playerEntity.y + 0.5, playerEntity.z + 0.5, random.nextInt(6) + 1))
}
}
if (this.isOnGround) {
i = 2
}
discard()
i
} else {
0
}
}
// calculate the trajectory for the reeled in pokemon
fun lobPokemonTowardsTarget(player: PlayerEntity, entity: Entity) {
val rad = Math.toRadians(player.yaw.toDouble())
val targetDirection = Vec3d(-sin(rad), 0.0, cos(rad))
val targetPos = player.pos.add(targetDirection.multiply(5.0))
val delta = targetPos.subtract(entity.pos)
val horizontalDistance = sqrt(delta.x * delta.x + delta.z * delta.z)
// Introduce a damping factor that reduces the velocity as the distance increases
val dampingFactor = 1 - (horizontalDistance / 80).coerceIn(0.0, 0.8) // increase end of coerceIn to dampen more
val verticalVelocity = 0.30 // Base vertical velocity for a gentle arc
val horizontalVelocityFactor = 0.13 // Base horizontal velocity factor
// Apply the damping factor to both horizontal and vertical velocities
val adjustedHorizontalVelocity = horizontalDistance * horizontalVelocityFactor * dampingFactor
val adjustedVerticalVelocity = (verticalVelocity + (horizontalDistance * 0.05)) * dampingFactor
// Calculate the final velocities
val velocityX = delta.x / horizontalDistance * adjustedHorizontalVelocity
val velocityZ = delta.z / horizontalDistance * adjustedHorizontalVelocity
val tossVelocity = Vec3d(velocityX, adjustedVerticalVelocity, velocityZ)
entity.velocity = tossVelocity
}
fun spawnPokemonFromFishing(player: PlayerEntity, chosenBucket: SpawnBucket, bobberBait: ItemStack) {
var hookedEntityID: Int? = null
val spawner = BestSpawner.fishingSpawner
//val spawnCause = SpawnCause(spawner = spawner, bucket = spawner.chooseBucket(), entity = spawner.getCauseEntity())
val spawnCause = FishingSpawnCause(
spawner,
chosenBucket,
player,
player.mainHandStack, // Crab, you should probably parse in the rod item connected to the bobber so we can check enchants in spawn conditions
bobberBait
)
val result = spawner.run(spawnCause, world as ServerWorld, pos.toBlockPos())
if (result == null) {
// This has a chance to fail, if the position has no suitability for a fishing context
// it could also just be a miss which
// means two attempts to spawn in the same location can have differing results (which is expected for
// randomness).
player.sendMessage(lang("fishing.nocatch").red())
return
}
var spawnedPokemon: PokemonEntity? = null
val resultingSpawn = result.get()
if (resultingSpawn !is EntitySpawnResult) {
return
}
for (entity in resultingSpawn.entities) {
//pokemon.uuid = it.uuid
hookedEntityID = entity.id
spawnedPokemon = (entity as PokemonEntity)
// todo Check if there was a berry used on the bobber and if so then see how it may affect the pokemon
if (bobberBait != ItemStack.EMPTY) {
// look up how mints affect the pokemon
// remove the bait from the bobber
val playerPokerod = if (this.playerOwner?.getStackInHand(Hand.MAIN_HAND)?.item is PokerodItem) this.playerOwner!!.getStackInHand(Hand.MAIN_HAND).item else this.playerOwner!!.getStackInHand(Hand.OFF_HAND).item
(playerPokerod as PokerodItem).bait = ItemStack.EMPTY
}
if (spawnedPokemon.pokemon.species.weight < 900) { // if weight value of Pokemon is less than 200 lbs (in hectograms) which we store weight as) then reel it in to the player
// play sound for small splash when this weight class is fished up
world.playSound(null, this.blockPos, CobblemonSounds.FISHING_SPLASH_SMALL, SoundCategory.BLOCKS, 1.0F, 1.0F)
// Example of applying the new velocity
lobPokemonTowardsTarget(player, entity)
}
else { // it is a big lad and you cannot reel it in
world.playSound(null, this.blockPos, CobblemonSounds.FISHING_SPLASH_BIG, SoundCategory.BLOCKS, 1.0F, 1.0F)
}
}
// What if it isn't an entity though
hookedEntity = world.getEntityById(hookedEntityID!!)
//val spawnedPokemon = spawnAction.entity
if (spawnedPokemon != null) {
BattleBuilder.pve((player as ServerPlayerEntity), spawnedPokemon).ifErrored { it.sendTo(player) { it.red() } }
}
}
fun checkBaitSuccessRate(successChance: Double): Boolean {
return Math.random() <= successChance
}
// check if the bite time is reduced based on the bait bonus
fun alterBiteTimeAttempt(waitCountdown: Int, stack: ItemStack): Int {
val bait = FishingBaits.getFromItemStack(stack) ?: return waitCountdown
val effect = bait.effects[FishingBait.Effects.BITE_TIME] ?: return waitCountdown
if (!checkBaitSuccessRate(effect.chance)) return waitCountdown
return if (waitCountdown * (effect.value) <= 0)
1 // return min value
else
(waitCountdown * (effect.value)).toInt()
}
// check the chance of a pokemon to spawn and if it is affected by bait
fun getPokemonSpawnChance(stack: ItemStack): Int {
val bait = FishingBaits.getFromItemStack(stack) ?: return this.pokemonSpawnChance
val effect = bait.effects[FishingBait.Effects.POKEMON_CHANCE] ?: return this.pokemonSpawnChance
return if (checkBaitSuccessRate(effect.chance)) {
((effect.chance) * 100).toInt()
} else this.pokemonSpawnChance
}
companion object {
val POKEROD_ID = DataTracker.registerData(PokeRodFishingBobberEntity::class.java, TrackedDataHandlerRegistry.STRING)
val POKEBOBBER_BAIT = DataTracker.registerData(PokeRodFishingBobberEntity::class.java, TrackedDataHandlerRegistry.ITEM_STACK)
val HOOK_ENTITY_ID = DataTracker.registerData(PokeRodFishingBobberEntity::class.java, TrackedDataHandlerRegistry.INTEGER)
private val CAUGHT_FISH = DataTracker.registerData(PokeRodFishingBobberEntity::class.java, TrackedDataHandlerRegistry.BOOLEAN)
}
}
@@ -20,6 +20,7 @@ import com.cobblemon.mod.common.api.net.serializers.StringSetDataSerializer
import com.cobblemon.mod.common.api.net.serializers.Vec3DataSerializer
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokeball.catching.CaptureContext
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.scheduling.Schedulable
@@ -57,6 +58,7 @@ import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket
import net.minecraft.particle.ParticleTypes
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.sound.SoundEvents
import net.minecraft.util.Identifier
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.hit.EntityHitResult
import net.minecraft.util.hit.HitResult
@@ -189,7 +191,7 @@ class EmptyPokeBallEntity : ThrownItemEntity, Poseable, WaterDragModifier, Sched
val owner = owner
if (!pokemonEntity.pokemon.isWild()) {
owner?.sendMessage(lang("capture.not_wild", pokemonEntity.pokemon.species.translatedName).red())
owner?.sendMessage(lang("capture.not_wild", pokemonEntity.exposedSpecies.translatedName).red())
return drop()
}
@@ -204,7 +206,7 @@ class EmptyPokeBallEntity : ThrownItemEntity, Poseable, WaterDragModifier, Sched
val hitBattlePokemon = hitActor?.activePokemon?.find { it.battlePokemon?.effectedPokemon?.entity == pokemonEntity }
if (throwerActor == null) {
owner.sendMessage(lang("capture.in_battle", pokemonEntity.pokemon.species.translatedName).red())
owner.sendMessage(lang("capture.in_battle", pokemonEntity.exposedSpecies.translatedName).red())
return drop()
}
@@ -230,13 +232,13 @@ class EmptyPokeBallEntity : ThrownItemEntity, Poseable, WaterDragModifier, Sched
"capture.attempted_capture",
throwerActor.getName(),
pokeBall.item().name,
pokemonEntity.pokemon.species.translatedName
pokemonEntity.exposedSpecies.translatedName
).yellow()
)
battle.sendUpdate(BattleCaptureStartPacket(pokeBall.name, aspects, hitBattlePokemon.getPNX()))
throwerActor.forceChoose(ForcePassActionResponse())
} else if (pokemonEntity.isBusy) {
owner?.sendMessage(lang("capture.busy", pokemonEntity.pokemon.species.translatedName).red())
owner?.sendMessage(lang("capture.busy", pokemonEntity.exposedSpecies.translatedName).red())
return drop()
} else if (owner is ServerPlayerEntity && BattleRegistry.getBattleByParticipatingPlayer(owner) != null) {
owner.sendMessage(lang("you_in_battle").red())
@@ -23,6 +23,7 @@ import com.cobblemon.mod.common.api.net.serializers.StringSetDataSerializer
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.feature.ChoiceSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.feature.FlagSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.StringSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.reactive.ObservableSubscription
@@ -615,129 +616,152 @@ open class PokemonEntity(
override fun interactMob(player: PlayerEntity, hand: Hand) : ActionResult {
val itemStack = player.getStackInHand(hand)
val colorFeatureType = SpeciesFeatures.getFeaturesFor(pokemon.species).find { it is ChoiceSpeciesFeatureProvider && DataKeys.CAN_BE_COLORED in it.keys }
val colorFeature = pokemon.getFeature<StringSpeciesFeature>(DataKeys.CAN_BE_COLORED)
if (itemStack.isOf(Items.SHEARS) && this.isShearable) {
this.sheared(SoundCategory.PLAYERS)
this.emitGameEvent(GameEvent.SHEAR, player)
itemStack.damage(1, player) { it.sendToolBreakStatus(hand) }
return ActionResult.SUCCESS
}
else if (itemStack.isOf(Items.BUCKET)) {
if (pokemon.getFeature<FlagSpeciesFeature>(DataKeys.CAN_BE_MILKED) != null) {
player.playSound(SoundEvents.ENTITY_GOAT_MILK, 1.0f, 1.0f)
val milkBucket = ItemUsage.exchangeStack(itemStack, player, Items.MILK_BUCKET.defaultStack)
player.setStackInHand(hand, milkBucket)
return ActionResult.success(world.isClient)
}
} else if (itemStack.isOf(Items.BOWL)) {
if (pokemon.aspects.any() {it.contains("mooshtank")}) {
player.playSound(SoundEvents.ENTITY_MOOSHROOM_MILK, 1.0f, 1.0f)
// if the Mooshtank ate a Flower beforehand
if (pokemon.lastFlowerFed != ItemStack.EMPTY && pokemon.aspects.any() {it.contains("mooshtank-brown")}) {
var effect: StatusEffect? = null
var duration = 0
if (ownerUuid == player.uuid || ownerUuid == null) {
if (itemStack.isOf(Items.SHEARS) && this.isShearable) {
this.sheared(SoundCategory.PLAYERS)
this.emitGameEvent(GameEvent.SHEAR, player)
itemStack.damage(1, player) { it.sendToolBreakStatus(hand) }
return ActionResult.SUCCESS
} else if (itemStack.isOf(Items.BUCKET)) {
if (pokemon.getFeature<FlagSpeciesFeature>(DataKeys.CAN_BE_MILKED) != null) {
player.playSound(SoundEvents.ENTITY_GOAT_MILK, 1.0f, 1.0f)
val milkBucket = ItemUsage.exchangeStack(itemStack, player, Items.MILK_BUCKET.defaultStack)
player.setStackInHand(hand, milkBucket)
return ActionResult.success(world.isClient)
}
} else if (itemStack.isOf(Items.BOWL)) {
if (pokemon.aspects.any { it.contains("mooshtank") }) {
player.playSound(SoundEvents.ENTITY_MOOSHROOM_MILK, 1.0f, 1.0f)
// if the Mooshtank ate a Flower beforehand
if (pokemon.lastFlowerFed != ItemStack.EMPTY && pokemon.aspects.any() { it.contains("mooshtank-brown") }) {
var effect: StatusEffect? = null
var duration = 0
if (pokemon.lastFlowerFed.isOf(Items.ALLIUM)) {
effect = StatusEffect.byRawId(12) // Fire Resistance
duration = 80 // 4 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.AZURE_BLUET)) {
effect = StatusEffect.byRawId(15) // Blindness
duration = 160 // 8 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.BLUE_ORCHID) || pokemon.lastFlowerFed.isOf(Items.DANDELION)) {
effect = StatusEffect.byRawId(23) // Saturation
duration = 7 // .35 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.CORNFLOWER)) {
effect = StatusEffect.byRawId(8) // Jump Boost
duration = 120 // 6 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.LILY_OF_THE_VALLEY)) {
effect = StatusEffect.byRawId(19) // Poison
duration = 240 // 12 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.OXEYE_DAISY)) {
effect = StatusEffect.byRawId(10) // Regeneration
duration = 160 // 8 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.POPPY) || pokemon.lastFlowerFed.isOf(Items.TORCHFLOWER)) {
effect = StatusEffect.byRawId(16) // Night Vision
duration = 100 // 5 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.PINK_TULIP) || pokemon.lastFlowerFed.isOf(Items.RED_TULIP) || pokemon.lastFlowerFed.isOf(Items.WHITE_TULIP) || pokemon.lastFlowerFed.isOf(Items.ORANGE_TULIP)) {
effect = StatusEffect.byRawId(18) // Weakness
duration = 180 // 9 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.WITHER_ROSE)) {
effect = StatusEffect.byRawId(20) // Wither
duration = 160 // 8 seconds
} else if (pokemon.lastFlowerFed.isOf(CobblemonItems.PEP_UP_FLOWER)) {
effect = StatusEffect.byRawId(25) // Levitation
duration = 160 // 8 seconds
if (pokemon.lastFlowerFed.isOf(Items.ALLIUM)) {
effect = StatusEffect.byRawId(12) // Fire Resistance
duration = 80 // 4 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.AZURE_BLUET)) {
effect = StatusEffect.byRawId(15) // Blindness
duration = 160 // 8 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.BLUE_ORCHID) || pokemon.lastFlowerFed.isOf(Items.DANDELION)) {
effect = StatusEffect.byRawId(23) // Saturation
duration = 7 // .35 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.CORNFLOWER)) {
effect = StatusEffect.byRawId(8) // Jump Boost
duration = 120 // 6 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.LILY_OF_THE_VALLEY)) {
effect = StatusEffect.byRawId(19) // Poison
duration = 240 // 12 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.OXEYE_DAISY)) {
effect = StatusEffect.byRawId(10) // Regeneration
duration = 160 // 8 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.POPPY) || pokemon.lastFlowerFed.isOf(Items.TORCHFLOWER)) {
effect = StatusEffect.byRawId(16) // Night Vision
duration = 100 // 5 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.PINK_TULIP) || pokemon.lastFlowerFed.isOf(Items.RED_TULIP) || pokemon.lastFlowerFed.isOf(
Items.WHITE_TULIP
) || pokemon.lastFlowerFed.isOf(Items.ORANGE_TULIP)
) {
effect = StatusEffect.byRawId(18) // Weakness
duration = 180 // 9 seconds
} else if (pokemon.lastFlowerFed.isOf(Items.WITHER_ROSE)) {
effect = StatusEffect.byRawId(20) // Wither
duration = 160 // 8 seconds
} else if (pokemon.lastFlowerFed.isOf(CobblemonItems.PEP_UP_FLOWER)) {
effect = StatusEffect.byRawId(25) // Levitation
duration = 160 // 8 seconds
}
// modify the suspicious stew with the effect
val susStewStack = Items.SUSPICIOUS_STEW.defaultStack
SuspiciousStewItem.addEffectToStew(susStewStack, effect, duration)
val susStewEffect = ItemUsage.exchangeStack(itemStack, player, susStewStack)
//give player modified Suspicious Stew
player.setStackInHand(hand, susStewEffect)
// reset the flower fed state
pokemon.lastFlowerFed = ItemStack.EMPTY
return ActionResult.success(world.isClient)
} else {
val mushroomStew = ItemUsage.exchangeStack(itemStack, player, Items.MUSHROOM_STEW.defaultStack)
player.setStackInHand(hand, mushroomStew)
return ActionResult.success(world.isClient)
}
// modify the suspicious stew with the effect
val susStewStack = Items.SUSPICIOUS_STEW.defaultStack
SuspiciousStewItem.addEffectToStew(susStewStack, effect, duration)
val susStewEffect = ItemUsage.exchangeStack(itemStack, player, susStewStack)
//give player modified Suspicious Stew
player.setStackInHand(hand, susStewEffect)
// reset the flower fed state
pokemon.lastFlowerFed = ItemStack.EMPTY
return ActionResult.success(world.isClient)
}
else {
val mushroomStew = ItemUsage.exchangeStack(itemStack, player, Items.MUSHROOM_STEW.defaultStack)
player.setStackInHand(hand, mushroomStew)
return ActionResult.success(world.isClient)
}
}
}
// Flowers used on brown MooshTanks
else if (itemStack.isOf(Items.ALLIUM) ||
itemStack.isOf(Items.AZURE_BLUET) ||
itemStack.isOf(Items.BLUE_ORCHID) ||
itemStack.isOf(Items.DANDELION) ||
itemStack.isOf(Items.CORNFLOWER) ||
itemStack.isOf(Items.LILY_OF_THE_VALLEY) ||
itemStack.isOf(Items.OXEYE_DAISY) ||
itemStack.isOf(Items.POPPY) ||
itemStack.isOf(Items.TORCHFLOWER) ||
itemStack.isOf(Items.PINK_TULIP) ||
itemStack.isOf(Items.RED_TULIP) ||
itemStack.isOf(Items.WHITE_TULIP) ||
itemStack.isOf(Items.ORANGE_TULIP) ||
itemStack.isOf(Items.WITHER_ROSE) ||
itemStack.isOf(CobblemonItems.PEP_UP_FLOWER)) {
if (pokemon.aspects.any() {it.contains("mooshtank")}) {
player.playSound(SoundEvents.ENTITY_MOOSHROOM_EAT, 1.0f, 1.0f)
pokemon.lastFlowerFed = itemStack
// Flowers used on brown MooshTanks
else if (itemStack.isOf(Items.ALLIUM) ||
itemStack.isOf(Items.AZURE_BLUET) ||
itemStack.isOf(Items.BLUE_ORCHID) ||
itemStack.isOf(Items.DANDELION) ||
itemStack.isOf(Items.CORNFLOWER) ||
itemStack.isOf(Items.LILY_OF_THE_VALLEY) ||
itemStack.isOf(Items.OXEYE_DAISY) ||
itemStack.isOf(Items.POPPY) ||
itemStack.isOf(Items.TORCHFLOWER) ||
itemStack.isOf(Items.PINK_TULIP) ||
itemStack.isOf(Items.RED_TULIP) ||
itemStack.isOf(Items.WHITE_TULIP) ||
itemStack.isOf(Items.ORANGE_TULIP) ||
itemStack.isOf(Items.WITHER_ROSE) ||
itemStack.isOf(CobblemonItems.PEP_UP_FLOWER)
) {
if (pokemon.aspects.any { it.contains("mooshtank") }) {
player.playSound(SoundEvents.ENTITY_MOOSHROOM_EAT, 1.0f, 1.0f)
pokemon.lastFlowerFed = itemStack
return ActionResult.success(world.isClient)
}
} else if (!player.isSneaking && player.uuid == ownerUuid && (itemStack.isOf(CobblemonItems.RELIC_COIN)
|| itemStack.isOf(CobblemonItems.RELIC_COIN_POUCH)
|| itemStack.isOf(CobblemonItems.RELIC_COIN_SACK)
|| itemStack.isOf(Items.NETHERITE_SCRAP)
|| itemStack.isOf(Items.NETHERITE_INGOT)
|| itemStack.isOf(Items.NETHERITE_BLOCK))
) {
if (GimmighoulStashHandler.interactMob(player, hand, pokemon)) {
return ActionResult.SUCCESS
}
} else if (itemStack.item is DyeItem && colorFeatureType != null) {
val currentColor = colorFeature?.value ?: ""
val item = itemStack.item as DyeItem
if (!item.color.name.equals(currentColor, ignoreCase = true)) {
if (player is ServerPlayerEntity) {
if (colorFeature != null) {
colorFeature.value = item.color.name.lowercase()
this.pokemon.markFeatureDirty(colorFeature)
} else {
val newColorFeature =
StringSpeciesFeature(DataKeys.CAN_BE_COLORED, item.color.name.lowercase())
this.pokemon.features.add(newColorFeature)
this.pokemon.anyChangeObservable.emit(pokemon)
}
this.pokemon.updateAspects()
if (!player.isCreative) {
itemStack.decrement(1)
}
}
return ActionResult.success(world.isClient)
}
} else if (itemStack.item.equals(Items.WATER_BUCKET) && colorFeatureType != null) {
if (player is ServerPlayerEntity) {
if (colorFeature != null) {
if (!player.isCreative) {
itemStack.decrement(1)
player.giveOrDropItemStack(Items.BUCKET.defaultStack)
}
colorFeature.value = ""
this.pokemon.markFeatureDirty(colorFeature)
this.pokemon.updateAspects()
}
}
return ActionResult.success(world.isClient)
}
} else if(!player.isSneaking && (itemStack.isOf(CobblemonItems.RELIC_COIN)
|| itemStack.isOf(CobblemonItems.RELIC_COIN_POUCH)
|| itemStack.isOf(CobblemonItems.RELIC_COIN_SACK)
|| itemStack.isOf(Items.NETHERITE_SCRAP)
|| itemStack.isOf(Items.NETHERITE_INGOT)
|| itemStack.isOf(Items.NETHERITE_BLOCK))) {
if(GimmighoulStashHandler.interactMob(player, hand, pokemon)) {
return ActionResult.SUCCESS
}
}
else if (itemStack.item is DyeItem && colorFeature != null) {
val item = itemStack.item as DyeItem
if (item.color.name.lowercase() != colorFeature.value.lowercase()) {
itemStack.decrement(1)
colorFeature.value = item.color.name.lowercase()
this.pokemon.markFeatureDirty(colorFeature)
this.pokemon.updateAspects()
}
}
else if (itemStack.item.equals(Items.WATER_BUCKET) && colorFeature != null) {
itemStack.decrement(1)
player.giveOrDropItemStack(Items.BUCKET.defaultStack)
colorFeature.value = ""
this.pokemon.markFeatureDirty(colorFeature)
this.pokemon.updateAspects()
}
if (hand == Hand.MAIN_HAND && player is ServerPlayerEntity && pokemon.getOwnerPlayer() == player) {
if (player.isSneaking) {
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.item.group
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.fishing.PokeRods
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.item.ItemConvertible
import net.minecraft.item.ItemGroup
@@ -210,6 +211,8 @@ object CobblemonItemGroups {
entries.add(CobblemonItems.DARK_GEM)
entries.add(CobblemonItems.STEEL_GEM)
entries.add(CobblemonItems.FAIRY_GEM)
entries.add(CobblemonItems.POKEROD_SMITHING_TEMPLATE)
}
private fun blockEntries(displayContext: DisplayContext, entries: Entries) {
@@ -534,6 +537,7 @@ object CobblemonItemGroups {
private fun pokeballentries(displayContext: DisplayContext, entries: Entries) {
CobblemonItems.pokeBalls.forEach(entries::add)
CobblemonItems.pokeRods.forEach(entries::add)
}
private fun foodInjections(injector: Injector) {
@@ -558,6 +562,7 @@ object CobblemonItemGroups {
injector.putAfter(CobblemonItems.SUSPICIOUS_SHERD, CobblemonItems.NOSTALGIC_SHERD)
injector.putAfter(CobblemonItems.AUTOMATON_ARMOR_TRIM_SMITHING_TEMPLATE, Items.SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE)
injector.putAfter(CobblemonItems.POKEROD_SMITHING_TEMPLATE, Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE)
}
/**
@@ -0,0 +1,109 @@
/*
* 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.item.interactive
import com.cobblemon.mod.common.advancement.CobblemonCriteria
import com.cobblemon.mod.common.api.fishing.FishingBaits
import com.cobblemon.mod.common.api.fishing.PokeRods
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.text.gray
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.item.BerryItem
import net.minecraft.client.item.TooltipContext
import net.minecraft.enchantment.EnchantmentHelper
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.item.FishingRodItem
import net.minecraft.item.ItemStack
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.sound.SoundCategory
import net.minecraft.sound.SoundEvents
import net.minecraft.stat.Stats
import net.minecraft.text.Text
import net.minecraft.util.ActionResult
import net.minecraft.util.Hand
import net.minecraft.util.Identifier
import net.minecraft.util.TypedActionResult
import net.minecraft.world.World
import net.minecraft.world.event.GameEvent
class PokerodItem(val pokeRodId: Identifier, settings: Settings?) : FishingRodItem(settings) {
var bait: ItemStack = ItemStack.EMPTY
override fun use(world: World, user: PlayerEntity, hand: Hand): TypedActionResult<ItemStack> {
// if item in mainhand is berry item then don't do anything
if (user.getStackInHand(Hand.MAIN_HAND).item is BerryItem)
return TypedActionResult(ActionResult.FAIL, user.getStackInHand(hand))
val itemStack = user.getStackInHand(hand)
val offHandItem = user.getStackInHand(Hand.OFF_HAND)
if (!world.isClient && user.fishHook == null && FishingBaits.isFishingBait(offHandItem) && !ItemStack.areItemsEqual(offHandItem, bait) && !user.isSneaking) {
// swap baits if one is already on the hook
if (bait != ItemStack.EMPTY) {
user.dropStack(bait) // drop old bait
bait = itemStack // apply new bait
}
// apply it to the rod as bait
bait = offHandItem.item.defaultStack
// decrement 1 stack of that item from the other hand
offHandItem.decrement(1)
}
if (!world.isClient && user.fishHook == null && user.isSneaking) {
// remove bait if one is already on the hook
if (bait != ItemStack.EMPTY) {
user.dropStack(bait) // drop old bait on the ground
bait = ItemStack.EMPTY
}
}
val i: Int
if (user.fishHook != null) { // if the bobber is out yet
if (!world.isClient) {
i = user.fishHook!!.use(itemStack)
itemStack.damage(i, user) { p: PlayerEntity -> p.sendToolBreakStatus(hand) }
}
world.playSound(null as PlayerEntity?, user.x, user.y, user.z, SoundEvents.ENTITY_FISHING_BOBBER_RETRIEVE, SoundCategory.NEUTRAL, 1.0f, 0.4f / (world.getRandom().nextFloat() * 0.4f + 0.8f))
user.emitGameEvent(GameEvent.ITEM_INTERACT_FINISH)
} else { // if the bobber is not out yet
world.playSound(null as PlayerEntity?, user.x, user.y, user.z, SoundEvents.ENTITY_FISHING_BOBBER_THROW, SoundCategory.NEUTRAL, 0.5f, 0.4f / (world.getRandom().nextFloat() * 0.4f + 0.8f))
if (!world.isClient) {
i = EnchantmentHelper.getLure(itemStack)
val j = EnchantmentHelper.getLuckOfTheSea(itemStack)
val bobberEntity = PokeRodFishingBobberEntity(user, pokeRodId, bait/*Registries.ITEM.getId(bait?.item)*/, world, j, i)
world.spawnEntity(bobberEntity)
CobblemonCriteria.CAST_POKE_ROD.trigger(user as ServerPlayerEntity, !bait.isEmpty)
}
user.incrementStat(Stats.USED.getOrCreateStat(this))
user.emitGameEvent(GameEvent.ITEM_INTERACT_START)
}
return TypedActionResult.success(itemStack, world.isClient())
}
override fun getEnchantability(): Int {
return 1
}
override fun appendTooltip(
stack: ItemStack,
world: World?,
tooltip: MutableList<Text>,
context: TooltipContext
) {
val rod = PokeRods.getPokeRod((stack.item as PokerodItem).pokeRodId) ?: return
val ball = PokeBalls.getPokeBall(rod.pokeBallId) ?: return
tooltip.add(ball.item.name.copy().gray())
super.appendTooltip(stack, world, tooltip, context)
}
override fun getTranslationKey(): String {
return "item.cobblemon.poke_rod"
}
}
@@ -0,0 +1,17 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.loot
import com.cobblemon.mod.common.util.cobblemonResource
object CobblemonLootTables {
val FISHING_GAMEPLAY = cobblemonResource("fishing/pokerod")
}
@@ -54,7 +54,9 @@ object LootInjector {
LootTables.SIMPLE_DUNGEON_CHEST,
LootTables.SPAWN_BONUS_CHEST,
LootTables.STRONGHOLD_CORRIDOR_CHEST,
LootTables.WOODLAND_MANSION_CHEST
LootTables.WOODLAND_MANSION_CHEST,
LootTables.FISHING_TREASURE_GAMEPLAY,
LootTables.FISHING_JUNK_GAMEPLAY
).apply { addAll(villageHouseLootTables) }
/**
@@ -0,0 +1,35 @@
/*
* 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.net.messages.client.data
import com.cobblemon.mod.common.api.fishing.PokeRods
import com.cobblemon.mod.common.api.fishing.PokeRod
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.PacketByteBuf
class PokeRodRegistrySyncPacket(rods: Collection<PokeRod>) : DataRegistrySyncPacket<PokeRod, PokeRodRegistrySyncPacket>(rods) {
companion object {
val ID = cobblemonResource("pokerod_sync")
fun decode(buffer: PacketByteBuf) = PokeRodRegistrySyncPacket(emptyList()).apply { decodeBuffer(buffer) }
}
override val id = ID
override fun encodeEntry(buffer: PacketByteBuf, entry: PokeRod) {
entry.encode(buffer)
}
override fun decodeEntry(buffer: PacketByteBuf) = PokeRod.decode(buffer)
override fun synchronizeDecoded(entries: Collection<PokeRod>) {
PokeRods.reload(entries.associateBy { it.name!! })
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.client.fishing
import com.cobblemon.mod.common.api.fishing.FishingBait
import com.cobblemon.mod.common.api.fishing.FishingBaits
import com.cobblemon.mod.common.net.messages.client.data.DataRegistrySyncPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.PacketByteBuf
class FishingBaitRegistrySyncPacket(fishingBaits: List<FishingBait>) : DataRegistrySyncPacket<FishingBait, FishingBaitRegistrySyncPacket>(fishingBaits) {
companion object {
val ID = cobblemonResource("fishing_baits")
fun decode(buffer: PacketByteBuf) = FishingBaitRegistrySyncPacket(emptyList()).apply { decodeBuffer(buffer) }
}
override val id = ID
override fun encodeEntry(buffer: PacketByteBuf, entry: FishingBait) {
buffer.writeIdentifier(entry.item)
}
override fun decodeEntry(buffer: PacketByteBuf): FishingBait {
// server handles the effects so client doesnt need to know them
return FishingBait(buffer.readIdentifier(), emptyMap())
}
override fun synchronizeDecoded(entries: Collection<FishingBait>) {
FishingBaits.reload(entries.associateBy { it.item })
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.client.pokemon.update
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.PacketByteBuf
import net.minecraft.util.Identifier
class FormUpdatePacket(pokemon: () -> Pokemon, form: FormData) : SingleUpdatePacket<FormData, FormUpdatePacket>(pokemon, form) {
override fun encodeValue(buffer: PacketByteBuf) {
this.value.encode(buffer)
}
override fun set(pokemon: Pokemon, value: FormData) {
pokemon.form = value
}
override val id: Identifier = ID
companion object {
val ID: Identifier = cobblemonResource("packets/form-update")
fun decode(buffer: PacketByteBuf): FormUpdatePacket {
val pokemon = decodePokemon(buffer)
val form = FormData()
form.species = pokemon().species
form.decode(buffer)
return FormUpdatePacket(pokemon, form)
}
}
}
@@ -43,6 +43,7 @@ object SnowstormParticleReader {
val emitterShapeEntityBoundingBoxJson = componentsJson.get("minecraft:emitter_shape_entity_aabb")?.asJsonObject
val emitterLifetimeEventsJson = componentsJson.get("minecraft:emitter_lifetime_events")?.asJsonObject
val dynamicMotionJson = componentsJson.get("minecraft:particle_motion_dynamic")?.asJsonObject
val parametricMotionJson = componentsJson.get("minecraft:particle_motion_parametric")?.asJsonObject
val particleAppearanceJson = componentsJson.get("minecraft:particle_appearance_billboard").asJsonObject
val sizeJson = particleAppearanceJson.get("size")?.asJsonArray
val particleLifetimeJson = componentsJson.get("minecraft:particle_lifetime_expression")?.asJsonObject
@@ -234,13 +235,22 @@ object SnowstormParticleReader {
val motion = if (dynamicMotionJson != null) {
val accelerationExpressions = dynamicMotionJson.get("linear_acceleration")?.asJsonArray?.map { it.asString.asExpression() }
?: listOf(0.0.asExpression(), 0.0.asExpression(), 0.0.asExpression())
val drag= dynamicMotionJson.get("linear_drag_coefficient")?.asString?.asExpression() ?: 0.0.asExpression()
val drag = dynamicMotionJson.get("linear_drag_coefficient")?.asString?.asExpression() ?: 0.0.asExpression()
DynamicParticleMotion(
direction = direction!!,
speed = speed,
acceleration = Triple(accelerationExpressions[0], accelerationExpressions[1], accelerationExpressions[2]),
drag = drag
)
} else if (parametricMotionJson != null) {
val offsetExpressions = parametricMotionJson.get("relative_position")?.asJsonArray?.map { it.asString.asExpression() }
?: listOf(0.0.asExpression(), 0.0.asExpression(), 0.0.asExpression())
val directionExpressions = parametricMotionJson.get("direction")?.asJsonArray?.map { it.asString.asExpression() }
?: listOf(0.0.asExpression(), 0.0.asExpression(), 0.0.asExpression())
ParametricParticleMotion(
offset = Triple(offsetExpressions[0], offsetExpressions[1], offsetExpressions[2]),
direction = Triple(directionExpressions[0], directionExpressions[1], directionExpressions[2])
)
} else {
StaticParticleMotion()
}
@@ -299,12 +309,19 @@ object SnowstormParticleReader {
textureSizeY = uvModeJson.get("texture_height")?.asInt ?: 128
)
}
val rotation = DynamicParticleRotation(
startRotation = startRotation,
speed = rotationSpeed,
acceleration = dynamicMotionJson?.get("rotation_acceleration")?.asString?.asExpression() ?: 0.0.asExpression(),
drag = dynamicMotionJson?.get("rotation_drag_coefficient")?.asString?.asExpression() ?: 0.0.asExpression()
)
val motionJson = dynamicMotionJson ?: parametricMotionJson
val parametricParticleRotation = motionJson?.get("rotation")?.asString?.asExpression()
val rotation = if (parametricParticleRotation != null) {
ParametricParticleRotation(expression = parametricParticleRotation)
} else {
DynamicParticleRotation(
startRotation = startRotation,
speed = rotationSpeed,
acceleration = dynamicMotionJson?.get("rotation_acceleration")?.asString?.asExpression() ?: 0.0.asExpression(),
drag = dynamicMotionJson?.get("rotation_drag_coefficient")?.asString?.asExpression() ?: 0.0.asExpression()
)
}
val tinting = if (colourJson is JsonObject) {
GradientParticleTinting(
interpolant = colourJson.get("interpolant").asString.asExpression(),
@@ -18,12 +18,12 @@ import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.status.statuses.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.SleepStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.SleepStatus
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
@@ -14,12 +14,12 @@ import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalcula
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.status.statuses.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.SleepStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.SleepStatus
import kotlin.math.roundToInt
import kotlin.random.Random
import net.minecraft.entity.LivingEntity
@@ -12,12 +12,12 @@ import com.cobblemon.mod.common.api.pokeball.catching.CaptureContext
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalculator
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.status.statuses.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.SleepStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.SleepStatus
import kotlin.math.max
import kotlin.math.roundToInt
import kotlin.random.Random
@@ -13,12 +13,12 @@ import com.cobblemon.mod.common.api.pokeball.catching.CaptureContext
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalculator
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.status.statuses.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.SleepStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.SleepStatus
import kotlin.math.roundToInt
import kotlin.math.sqrt
import kotlin.random.Random
@@ -15,12 +15,12 @@ import com.cobblemon.mod.common.api.pokeball.catching.calculators.CriticalCaptur
import com.cobblemon.mod.common.api.pokeball.catching.calculators.PokedexProgressCaptureMultiplierProvider
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.status.statuses.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.SleepStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.SleepStatus
import kotlin.math.roundToInt
import kotlin.math.sqrt
import kotlin.random.Random
@@ -15,12 +15,12 @@ import com.cobblemon.mod.common.api.pokeball.catching.calculators.CriticalCaptur
import com.cobblemon.mod.common.api.pokeball.catching.calculators.PokedexProgressCaptureMultiplierProvider
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.status.statuses.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.SleepStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.SleepStatus
import kotlin.math.pow
import kotlin.math.roundToInt
import kotlin.random.Random
@@ -15,12 +15,12 @@ import com.cobblemon.mod.common.api.pokeball.catching.calculators.CriticalCaptur
import com.cobblemon.mod.common.api.pokeball.catching.calculators.PokedexProgressCaptureMultiplierProvider
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.status.statuses.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.SleepStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.SleepStatus
import kotlin.math.pow
import kotlin.math.roundToInt
import kotlin.random.Random
@@ -13,12 +13,12 @@ import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalcula
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CriticalCaptureProvider
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.status.statuses.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.SleepStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.SleepStatus
import kotlin.math.max
import kotlin.math.pow
import kotlin.math.roundToInt
@@ -14,12 +14,12 @@ import com.cobblemon.mod.common.api.pokeball.catching.calculators.CriticalCaptur
import com.cobblemon.mod.common.api.pokeball.catching.calculators.PokedexProgressCaptureMultiplierProvider
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.status.statuses.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.SleepStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.BurnStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.FrozenStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.ParalysisStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonBadlyStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.SleepStatus
import kotlin.math.max
import kotlin.math.pow
import kotlin.math.roundToInt
@@ -34,6 +34,7 @@ import com.cobblemon.mod.common.api.pokemon.experience.ExperienceSource
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeature
import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.SynchronizedSpeciesFeature
import com.cobblemon.mod.common.api.pokemon.feature.SynchronizedSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.friendship.FriendshipMutationCalculator
import com.cobblemon.mod.common.api.pokemon.labels.CobblemonPokemonLabels
import com.cobblemon.mod.common.api.pokemon.moves.LearnsetQuery
@@ -472,7 +473,11 @@ open class Pokemon : ShowdownIdentifiable {
SeasonFeatureHandler.updateSeason(this, level, position.toBlockPos())
val entity = PokemonEntity(level, this)
illusion?.start(entity)
entity.setPositionSafely(position)
val sentOut = entity.setPositionSafely(position)
//If sendout failed, fall back
if (!sentOut) {
entity.setPos(position.x, position.y, position.z)
}
mutation(entity)
level.spawnEntity(entity)
state = SentOutState(entity)
@@ -1586,7 +1591,7 @@ open class Pokemon : ShowdownIdentifiable {
}
}
private val _form = SimpleObservable<FormData>()
private val _form = registerObservable(SimpleObservable<FormData>()) { FormUpdatePacket({ this }, it) }
private val _species = registerObservable(SimpleObservable<Species>()) { SpeciesUpdatePacket({ this }, it) }
private val _nickname = registerObservable(SimpleObservable<MutableText?>()) { NicknameUpdatePacket({ this }, it) }
private val _experience = registerObservable(SimpleObservable<Int>()) { ExperienceUpdatePacket({ this }, it) }
@@ -1614,7 +1619,8 @@ open class Pokemon : ShowdownIdentifiable {
private val _originalTrainerName = registerObservable(SimpleObservable<String?>()) { OriginalTrainerUpdatePacket({ this }, it) }
private val _features = registerObservable(SimpleObservable<SpeciesFeature>()) {
if (it is SynchronizedSpeciesFeature) {
val featureProvider = SpeciesFeatures.getFeature(it.name)
if (it is SynchronizedSpeciesFeature && featureProvider is SynchronizedSpeciesFeatureProvider && featureProvider.visible) {
SpeciesFeatureUpdatePacket({ this }, species.resourceIdentifier, it)
} else {
null
@@ -45,6 +45,7 @@ object CobblemonRequirementAdapter : RequirementAdapter {
this.registerType(WeatherRequirement.ADAPTER_VARIANT, WeatherRequirement::class)
this.registerType(StatCompareRequirement.ADAPTER_VARIANT, StatCompareRequirement::class)
this.registerType(StatEqualRequirement.ADAPTER_VARIANT, StatEqualRequirement::class)
this.registerType(AttackDefenceRatioRequirement.ADAPTER_VARIANT, AttackDefenceRatioRequirement::class)
this.registerType(BattleCriticalHitsRequirement.ADAPTER_VARIANT, BattleCriticalHitsRequirement::class)
this.registerType(DamageTakenRequirement.ADAPTER_VARIANT, DamageTakenRequirement::class)
this.registerType(UseMoveRequirement.ADAPTER_VARIANT, UseMoveRequirement::class)
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.evolution.requirements
import com.cobblemon.mod.common.api.pokemon.evolution.requirement.EvolutionRequirement
import com.cobblemon.mod.common.pokemon.Pokemon
class AttackDefenceRatioRequirement : EvolutionRequirement {
companion object {
const val ADAPTER_VARIANT = "attack_defence_ratio"
}
enum class AttackDefenceRatio {
ATTACK_HIGHER,
DEFENCE_HIGHER,
EQUAL
}
val ratio = AttackDefenceRatio.ATTACK_HIGHER
override fun check(pokemon: Pokemon): Boolean {
return when (ratio) {
AttackDefenceRatio.ATTACK_HIGHER -> pokemon.attack > pokemon.defence
AttackDefenceRatio.DEFENCE_HIGHER -> pokemon.defence > pokemon.attack
AttackDefenceRatio.EQUAL -> pokemon.attack == pokemon.defence
}
}
}
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.feature
import com.cobblemon.mod.common.api.pokemon.feature.FlagSpeciesFeature
import com.cobblemon.mod.common.pokemon.Pokemon
/**
* A fishing. You know the one.
*
* @author Hiroku
* @since November 25th, 2022
*/
const val FISHED = "fished"
object FishedFeatureHandler {
fun updateFished(pokemon: Pokemon, fished: Boolean?) {
val feature = pokemon.getFeature<FlagSpeciesFeature>(FISHED) ?: return
val currentFished = feature.enabled
if (currentFished != fished && fished != null) {
feature.enabled = fished
pokemon.updateAspects()
pokemon.markFeatureDirty(feature)
}
}
}
@@ -16,4 +16,9 @@ import net.minecraft.util.Identifier
*
* @author Deltric
*/
class VolatileStatus(name: Identifier) : Status(name, "", "", "")
open class VolatileStatus(
name: Identifier,
showdownName: String,
applyMessage: String,
removeMessage: String,
) : Status(name, showdownName, applyMessage, removeMessage)
@@ -0,0 +1,19 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.status.statuses.nonpersistent
import com.cobblemon.mod.common.pokemon.status.VolatileStatus
import com.cobblemon.mod.common.util.cobblemonResource
class AttractStatus : VolatileStatus(
cobblemonResource("attract"),
"attract",
"cobblemon.battle.attract_start",
"cobblemon.battle.attract_snapped"
)
@@ -0,0 +1,19 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.status.statuses.nonpersistent
import com.cobblemon.mod.common.pokemon.status.VolatileStatus
import com.cobblemon.mod.common.util.cobblemonResource
class ConfuseStatus : VolatileStatus(
cobblemonResource("confused"),
"confusion",
"cobblemon.battle.confusion_start",
"cobblemon.battle.confusion_snapped"
)
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.status.statuses
package com.cobblemon.mod.common.pokemon.status.statuses.persistent
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
import com.cobblemon.mod.common.util.cobblemonResource
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.status.statuses
package com.cobblemon.mod.common.pokemon.status.statuses.persistent
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
import com.cobblemon.mod.common.util.cobblemonResource
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.status.statuses
package com.cobblemon.mod.common.pokemon.status.statuses.persistent
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
import com.cobblemon.mod.common.util.cobblemonResource
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.status.statuses
package com.cobblemon.mod.common.pokemon.status.statuses.persistent
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.status.PersistentStatus

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