Compare commits

...
Author SHA1 Message Date
Guilherme Pereira 694f74be13 add candle cake 2025-02-17 17:11:49 -03:00
Guilherme Pereira 79d097b17c add support for diferent cake variants 2025-02-16 19:14:50 -03:00
Guilherme Pereira 7ef30653bb refactor cake rendering 2025-02-16 17:14:35 -03:00
Guilherme Pereira 1ecb13f7fc Merge branch 'main' into we_are_cooking_now 2025-02-16 13:14:27 -03:00
Test d19878edf6 refactor pot 2025-02-14 16:00:56 -05:00
Test 4f98d1e054 Merge branch 'we_are_cooking_now' of https://gitlab.com/cable-mc/cobblemon into we_are_cooking_now
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonItems.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/cooking/CookingComponentColorHelper.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/block/CampfireBlockEntityRenderer.kt
2025-02-13 18:54:15 -05:00
Test 7b8e07aef3 refactor pot 2025-02-13 18:44:11 -05:00
Guilherme Pereira 1442bf5c34 fix merge 2025-02-06 03:47:13 -03:00
Guilherme Pereira 2ac37f6c4d Merge branch 'main' into we_are_cooking_now 2025-02-06 03:46:51 -03:00
Guilherme Pereira 9795dce227 remove campfire pot trap 2025-02-06 03:34:02 -03:00
Guilherme Pereira d9cf802a9e fix saccharine trees natural world generation 2025-01-31 11:27:59 -03:00
Guilherme Pereira 3fbf2595cc add wool particles to cake breaking 2025-01-26 13:58:57 -03:00
Guilherme Pereira 6665285253 refactor cakes 2025-01-25 19:06:11 -03:00
Guilherme Pereira 61ae29b745 add candied apple, fried rice and potato mochi 2025-01-21 18:29:15 -03:00
Guilherme Pereira a17bf5eeeb update licenses 2025-01-21 17:42:03 -03:00
Guilherme Pereira c71d91a46d add mochis 2025-01-21 17:40:44 -03:00
Guilherme Pereira c86103fd31 add player edible cake 2025-01-20 18:49:18 -03:00
Plastered_Crab 5190bbe723 Prep for adding new cooking recipe items 2025-01-18 22:08:16 -08:00
Plastered_Crab 3663ce2555 Merge remote-tracking branch 'origin/we_are_cooking_now' into we_are_cooking_now 2025-01-18 15:00:46 -08:00
Plastered_Crab b5fc52d9b1 Merge branch 'main' into we_are_cooking_now
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/api/tags/CobblemonBlockTags.kt
#	common/src/main/resources/data/cobblemon/recipe/cooking_pot/whipped_dream_from_cooking_pot.json
#	common/src/main/resources/data/minecraft/tags/block/logs.json
#	common/src/main/resources/data/minecraft/tags/item/logs.json
#	common/src/main/resources/data/minecraft/tags/item/logs_that_burn.json
2025-01-18 14:59:52 -08:00
Guilherme Pereira ca79b70815 add shapes to lure cakes 2025-01-17 13:05:37 -03:00
Guilherme Pereira 898c956f17 add rendering for the inner part of the cake 2025-01-16 17:01:37 -03:00
Guilherme Pereira 2ce48c9dff add custom lure cake color based on seasonings 2025-01-16 10:36:33 -03:00
Guilherme Pereira f64ec12a6a update licenses 2025-01-15 19:55:56 -03:00
Guilherme Pereira 474d3f3b55 add campfire pot bubbles color 2025-01-15 19:54:26 -03:00
Guilherme Pereira c87e6a391d refactor cooking pot renderer 2025-01-15 18:34:43 -03:00
Guilherme Pereira 192fd4dba7 make campfire pot bubbles disapear on lid close 2025-01-15 17:22:10 -03:00
plastered_crab c26786c644 Put water back to how it was 2025-01-14 21:19:22 -08:00
Plastered_Crab ebb0277d80 Merge remote-tracking branch 'origin/we_are_cooking_now' into we_are_cooking_now
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/client/render/block/CampfireBlockEntityRenderer.kt
2025-01-14 17:20:34 -08:00
Plastered_Crab 2a73efa479 Create custom render type to allow Cooking Pot water pane to show particles through it (wip) 2025-01-14 17:19:53 -08:00
Guilherme Pereira 6e68d22d4a update campfire pot models (again) 2025-01-14 20:39:49 -03:00
Guilherme Pereira dee08e6572 fix cooking pot rotation 2025-01-14 18:38:34 -03:00
Guilherme Pereira 2383686e42 update campfire pot models 2025-01-14 17:54:16 -03:00
Guilherme Pereira b8de9c415e Merge branch 'we_are_cooking_now' of gitlab.com:cable-mc/cobblemon into we_are_cooking_now 2025-01-14 16:59:09 -03:00
Guilherme Pereira f0269a3638 fix cooking pot items not being properly synched with the client 2025-01-14 16:58:54 -03:00
Plastered_Crab dbbbd309dc Added TransparentColorMixer method 2025-01-14 11:06:07 -08:00
plastered_crab b7a905ad79 Remove old bubbles 2025-01-14 06:59:23 -08:00
plastered_crab 60c8049459 More water bubble changes for cooking pot 2025-01-14 06:13:50 -08:00
plastered_crab 8c395b6fee Reduce and raise the particles for bubbling water in cooking pot 2025-01-13 22:21:14 -08:00
plastered_crab e9935a41e3 Start to add particles for bubbling water in cooking pot 2025-01-13 21:45:14 -08:00
Guilherme Pereira f0ecd4d50d add seasoning color to cooking pot water 2025-01-13 23:30:07 -03:00
Guilherme Pereira c0661022ab i hate bubbles 2025-01-13 21:50:30 -03:00
Guilherme Pereira e7305a3dfb improve seasonings spin on the cooking pot 2025-01-13 20:24:41 -03:00
Guilherme Pereira 86f52f176a make cooking pot seasonings spin 2025-01-13 20:01:25 -03:00
Guilherme Pereira beb9796e75 add water liquid to open cooking pot 2025-01-13 19:20:20 -03:00
Guilherme Pereira 6bb55adebc add campfire pot open/close variants 2025-01-13 13:41:37 -03:00
Guilherme Pereira 011bc85d18 Merge branch 'main' into we_are_cooking_now 2025-01-12 19:44:19 -03:00
Guilherme Pereira fad75c498b make cooking pot drop items on remove 2025-01-10 14:22:00 -03:00
Guilherme Pereira 915c69b443 improve cooking pot sounds 2025-01-10 11:06:49 -03:00
Guilherme Pereira 05b574543c add more constants to cooking pot 2025-01-10 10:30:01 -03:00
Guilherme Pereira 5198b2a709 render preview item on cooking pot 2025-01-10 09:35:14 -03:00
Guilherme Pereira 509973a027 add button to open/close the cooking pot lid 2025-01-10 07:58:58 -03:00
Guilherme Pereira 0253475f50 make cooking pot work similar to a furnace 2025-01-09 14:00:25 -03:00
Guilherme Pereira 6961c92c8d fix container data of cooking pot 2025-01-08 17:41:43 -03:00
Plastered_Crab 906d7b361d Lure Cakes now show their Bait Effects on their tooltip 2025-01-07 15:45:32 -08:00
Plastered_Crab 54121199b6 Merge remote-tracking branch 'origin/we_are_cooking_now' into we_are_cooking_now 2025-01-07 15:29:39 -08:00
Plastered_Crab 78b3e0a1aa Lure Cakes now are grey (colors coming later) and get eaten in bits as pokemon spawn 2025-01-07 15:29:17 -08:00
Guilherme Pereira aa45795944 add cooking pot sounds 2025-01-07 19:55:57 -03:00
Guilherme Pereira 353209c72c Merge branch 'we_are_cooking_now' of gitlab.com:cable-mc/cobblemon into we_are_cooking_now 2025-01-07 18:28:39 -03:00
Guilherme Pereira d07eaefdd4 lil cooking pot refactoring 2025-01-07 18:28:21 -03:00
Sterrezo 2ead9f6f02 Added campfire pot sounds 2025-01-07 21:58:05 +01:00
Plastered_Crab 472a349709 Merge remote-tracking branch 'origin/we_are_cooking_now' into we_are_cooking_now 2025-01-07 12:40:06 -08:00
Plastered_Crab 9043520364 Lure Cakes now store CookingComponent data, render, and can use BaitEffects 2025-01-07 12:39:41 -08:00
Guilherme Pereira 88ea77e2f3 ad quality tooltip to aprijuices 2025-01-07 15:56:49 -03:00
Guilherme Pereira a609e2d561 add separate aprijuice tooltip generator 2025-01-07 15:28:52 -03:00
Guilherme Pereira 6f1c791df4 add shift click functionality to cooking pot 2025-01-07 08:47:53 -03:00
plastered_crab dc41e7b50f Lure cakes work now with new (maybe temporary) spawn context "bait" 2025-01-06 23:00:33 -08:00
Plastered_Crab 1b22da8bd9 Merge remote-tracking branch 'origin/we_are_cooking_now' into we_are_cooking_now 2025-01-06 17:27:28 -08:00
Plastered_Crab 773eb9e1c3 So close to having Lure Cakes spawning pokemon 2025-01-06 17:27:07 -08:00
Guilherme Pereira 87deea9748 make cooking pot items persistent 2025-01-06 21:13:47 -03:00
Guilherme Pereira 556636f41e Merge branch 'we_are_cooking_now' of gitlab.com:cable-mc/cobblemon into we_are_cooking_now 2025-01-06 20:48:27 -03:00
Guilherme Pereira daab3dd3dd fix bait vanishing 2025-01-06 20:48:17 -03:00
Plastered_Crab 5535390298 Lure Cakes are real!! 2025-01-06 15:13:19 -08:00
Guilherme Pereira f54eaf3101 make baits update sprite and tooltip while crafting on the cooking pot 2025-01-06 19:18:57 -03:00
Guilherme Pereira 1d86ace385 no more food waste 2025-01-06 18:32:49 -03:00
Guilherme Pereira 4a02a0a67d add leaf color based on quality to aprijuices 2025-01-06 17:33:14 -03:00
Guilherme Pereira 7f193c11e1 add flavor tooltip to aprijuices 2025-01-06 16:26:44 -03:00
plastered_crab 9f90a08276 Added preliminary foundation for Lure/Bait spawns 2025-01-06 09:58:30 -08:00
Guilherme Pereira f49a114f0f make aprijuice mix colors base on flavors 2025-01-06 14:00:30 -03:00
Guilherme Pereira 281fc300a6 fix merge 2025-01-06 14:00:20 -03:00
plastered_crab ea7078b749 Merge branch 'refs/heads/main' into we_are_cooking_now
# 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/client/CobblemonBakingOverrides.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/item/interactive/PokerodItem.kt
#	common/src/main/resources/assets/cobblemon/lang/en_us.json
#	common/src/main/resources/data/cobblemon/tags/block/flowers.json
#	common/src/main/resources/data/minecraft/tags/block/flowers.json
#	fabric/src/main/kotlin/com/cobblemon/mod/fabric/CobblemonFabric.kt
2025-01-06 08:03:32 -08:00
plastered_crab 03aabb55ca Merge remote-tracking branch 'origin/we_are_cooking_now' into we_are_cooking_now 2025-01-06 07:33:30 -08:00
plastered_crab 078da5b213 Adjust tooltip info for "seasoning" ingredients 2025-01-06 07:33:19 -08:00
Guilherme Pereira 22af77c7e6 add aprijuice item color provider 2025-01-06 12:21:32 -03:00
Guilherme Pereira 7ee7a6624f add dynamic rendering for poke baits based on seasonings 2025-01-06 10:19:43 -03:00
plastered_crab 85c9fcd28e Added test tooltip info for "seasoning" ingredients to show flavor data (thank you Tim) 2025-01-05 23:16:51 -08:00
plastered_crab 619da2e19a All berry seasoning files are done now! So many flavors! 2025-01-05 21:49:06 -08:00
plastered_crab c457cdfe5e Edit the seasoning files to include accurate berry flavors 2025-01-05 18:19:24 -08:00
plastered_crab a87c319d10 Fishing Bait combining works now! Fish be warned! 2025-01-04 23:53:18 -08:00
plastered_crab db5bdb0ef1 You can now combine fishing bait! (kind of) 2025-01-04 18:38:20 -08:00
plastered_crab adc79a34d1 Gordon Ramsey is with me like the movie Ghost 2025-01-04 02:52:00 -08:00
plastered_crab 8776dffed4 What's that? are we close to cooking with Seasoning Slots?? I think so! 2025-01-04 01:49:57 -08:00
plastered_crab 77f5434b78 Started work on potential CookingComponent 2025-01-04 00:49:41 -08:00
plastered_crab 0191373ff5 Slight tweak to RICE placement in shallow water 🌾 2025-01-03 12:09:41 -08:00
plastered_crab 0daf995c70 The RICE it grooooooows in water babyyy 🌾 2025-01-03 09:13:29 -08:00
plastered_crab dd9437c663 Fix the bounding box shape of the RICE and allow it to be waterlogged🌾 2025-01-03 01:29:17 -08:00
plastered_crab 752675ffb0 Can you smell what the RICE is cooking?!? 🌾🌾🍚🍙🌾🌾 2025-01-03 00:24:50 -08:00
Plastered_Crab 5bcc0fb343 Alcremie Sweets are cookable + honey bottles return glass bottles when cooked with 2025-01-02 16:34:59 -08:00
Plastered_Crab 2cd726c003 Aprijuice is coming (for real this time) 2025-01-02 16:12:00 -08:00
Plastered_Crab f911ad57b9 Aprijuice is coming 🚨🚨🚨 2025-01-02 16:10:21 -08:00
Plastered_Crab bc2a547b86 Yeah boi we cooking with some recipes now 🧑🏻‍🍳 2025-01-02 13:53:48 -08:00
Plastered_Crab d5ba9bc3f5 Shapeless recipes go brrrr 🧑🏻‍🍳 2025-01-02 11:37:32 -08:00
plastered_crab 3dc8493b76 Start to getting Cooking Pot Shapeless recipes working 2025-01-01 20:13:56 -08:00
Plastered_Crab 0b991c4ae3 Let's gooooooo! Cooking Pot seasoning slots and renderer working so good now 🧑🏻‍🍳 2025-01-01 13:14:17 -08:00
Plastered_Crab 1352508070 Yeah baby CookingPot stays rendered now and Components work properly now 😎 2025-01-01 12:49:58 -08:00
plastered_crab 87d467ec67 My God I'm dumb. It works now lmao 2025-01-01 00:37:17 -08:00
plastered_crab c020deb728 Please god end this suffering. Why won't this work 💀 2024-12-31 23:15:39 -08:00
plastered_crab d5e15f7bf2 Attempting to store data on Campfire Block Entity via Components 2024-12-30 17:04:09 -08:00
plastered_crab 59622a1bfb Additional fixes for crafting grid syncing 2024-12-30 10:50:12 -08:00
plastered_crab 1c237503a0 Cooking Pot syncs with crafting slots finally 2024-12-29 22:22:43 -08:00
plastered_crab 138a83c844 More fixes and syncing 2024-12-29 16:57:39 -08:00
plastered_crab e8c86f4b4e Cooking Pot clears crafting slots when result item is taken 2024-12-29 16:45:16 -08:00
plastered_crab 344d2ae3e6 plz pot 2024-12-29 16:08:58 -08:00
plastered_crab 19a571147b I hate this pot 2024-12-29 14:59:34 -08:00
plastered_crab 2f369fb6ee More work trying to get the Cooking Pot to behave 2024-12-29 14:24:31 -08:00
plastered_crab c1d16f5185 Redo how crafting slots are initialized for Cooking Pot (result = 0 crafting 1-9) and add more debug 2024-12-28 22:36:11 -08:00
plastered_crab 2382895a42 Add minecraft:potion id for crafting medicinal brew 2024-12-27 18:09:39 -08:00
plastered_crab 68adae082b Additional recipes for the Cooking Pot 2024-12-27 17:12:49 -08:00
Erusel eac1b573c9 trying to debugs these ***** slots (i think ?) 2024-12-27 22:42:38 +01:00
Erusel a29a17a28b Added Cooking Pot ( globally ) 2024-12-13 00:09:33 +01:00
Erusel 4805659a68 Started furnace like crafting 2024-12-03 09:42:54 +01:00
Erusel fbdcfb20ed Cooking Pot now keep items inside 2024-11-30 12:11:28 +01:00
Erusel 4a2b186ef7 Merge remote-tracking branch 'origin/we_are_cooking_now' into we_are_cooking_now 2024-11-30 12:10:48 +01:00
Plastered_Crab fa41b30a4c Add 2 test overlays for Poke Bait 2024-11-29 18:42:38 -08:00
Plastered_Crab 505282986e Add better texture for Poke Bait 2024-11-29 17:13:15 -08:00
Plastered_Crab 9e6e74db7c Add test recipe for Poke Bait 2024-11-29 14:03:01 -08:00
Plastered_Crab cf68e88103 Add test Poke Bait sprite for testing new combining bait system 2024-11-29 12:44:59 -08:00
Plastered_Crab 84ae73d0d1 Add test Pokepuff sprites for testing 2024-11-29 12:35:38 -08:00
Erusel 9bb0f13793 Added cooking pot to the block group 2024-11-29 16:51:16 +01:00
Erusel 912485067b Working cooking pot recipe book 2024-11-29 12:10:59 +01:00
Plastered_Crab 128f7c15a6 Add debug chaos to try to help see why registering recipes doesnt work 2024-11-28 00:44:05 -08:00
Erusel 5d67d0fdfc - i want to die 2024-11-28 03:12:45 +01:00
Erusel 3f90d19801 - Added Cooking Pot block
- Added Sweety things
- Started cooking pot handled things
2024-11-26 22:28:05 +01:00
Plastered_Crab 897e1651c5 Remove unneeded file 2024-11-05 14:49:17 -08:00
Plastered_Crab 9c5bb2fcdb Start to implementing Sweet Incense block 2024-11-05 12:14:47 -08:00
Plastered_Crab 8ee8d84c8b Add missing bugwort textures and blockstates 2024-11-04 15:43:56 -08:00
Plastered_Crab b25fbf42ab Bugwort plants now grow and behave properly 2024-11-04 15:40:23 -08:00
Plastered_Crab f9f3392901 Leaves drip honey down below to other leaves more often 2024-11-04 11:33:14 -08:00
Plastered_Crab 35249f5804 Stripped Saccharine Logs now give Sweet Sap again 2024-11-03 23:34:12 -08:00
Plastered_Crab b32fdeda9c Merge remote-tracking branch 'origin/we_are_cooking_now' into we_are_cooking_now 2024-11-03 23:07:07 -08:00
tmetcalfe89 1d033701ed fix leaf block
keys in the ignition, bonemeal on, redundant props removed
2024-11-03 22:53:31 -08:00
Plastered_Crab 6422cffde3 Merge branch 'main' into we_are_cooking_now 2024-11-03 14:35:58 -08:00
Plastered_Crab e8588370e5 Remove not needed tag additions for leaves 2024-11-03 13:39:11 -08:00
Plastered_Crab a96692e5cf Bees now like the Saccharine Leaves again and added more old files back that were missing 2024-11-03 13:24:58 -08:00
Nick 81c272778f Merge branch 'we_are_cooking_now' of gitlab.com:cable-mc/pokemon-cobbled into we_are_cooking_now 2024-11-02 01:11:33 -07:00
Nick 31351116b5 Include a configuration for maxShiftBy 2024-11-02 01:11:18 -07:00
Plastered_Crab fd18e4d7f1 Remove unneeded tags in saccharine_leaves json block tag 2024-11-02 00:35:32 -07:00
Plastered_Crab 7eb5364ba7 Add BeeEntityMixin to mixins.cobblemon-common json file 2024-11-02 00:10:03 -07:00
Plastered_Crab 97b679bbe0 Add saccharine_leaves block tags 2024-11-01 23:32:14 -07:00
Plastered_Crab 85dc3ad1d7 Add Saccharine Trees to bee_growables list 2024-11-01 23:18:40 -07:00
Plastered_Crab a90e9544be More remappings and trying to get Bees to fertilize Saccharine Leaves again 2024-11-01 17:14:46 -07:00
Plastered_Crab 6261163728 Add old Abstract CookingPot ScreenHandler to new branch to be remapped 2024-10-31 16:42:52 -07:00
Plastered_Crab 9288ae8776 More work fixing up some remappings 2024-10-31 16:35:13 -07:00
Plastered_Crab f887f678f1 - Add back more missing files from old branch that need re-mapped 2024-10-28 12:20:26 -07:00
Plastered_Crab 59ba74606e - Add textures for real real this time 2024-10-25 14:44:44 -07:00
Plastered_Crab 130b1c99db - Add textures for missing Saccharine items and add missing recipes for Saccharine items. Set stage for re-adding bugwort, saccharine saplings, cooking pot, etc 2024-10-25 14:24:00 -07:00
Plastered_Crab adc4b424c5 Merge branch 'main' into we_are_cooking_now 2024-10-25 12:22:57 -07:00
Erusel 4ad140f7a3 saccharines bases blocks 2024-09-06 20:48:52 +02:00
637 changed files with 14349 additions and 318 deletions
@@ -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.mixin;
import com.cobblemon.mod.common.block.SaccharineLeafBlock;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Desc;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.Slice;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(targets = "net.minecraft.world.entity.animal.Bee$BeeGrowCropGoal")
public abstract class BeeEntityMixin {
@Unique
private BlockState cobblemon$result = null;
@Inject(method = "tick()V", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/block/BonemealableBlock;performBonemeal(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/util/RandomSource;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)V",
shift = At.Shift.BY,
by = 2
),
locals = LocalCapture.CAPTURE_FAILHARD
)
private void injectCustomGrowth(CallbackInfo ci, int i, BlockPos blockPos, BlockState blockState, Block block) {
if (block instanceof SaccharineLeafBlock) {
int age = blockState.getValue(SaccharineLeafBlock.Companion.getAGE());
if (age < 2) {
this.cobblemon$result = blockState.setValue(SaccharineLeafBlock.Companion.getAGE(), age + 1);
}
}
}
@ModifyVariable(method = "tick", at = @At(value = "LOAD", ordinal = 0), index = 5)
private BlockState applyCustomBlockState(BlockState before) {
if(this.cobblemon$result != null && before == null) {
BlockState result = this.cobblemon$result;
this.cobblemon$result = null;
return result;
}
return before;
}
}
@@ -0,0 +1,88 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.CobblemonRecipeCategories;
import com.cobblemon.mod.common.CobblemonRecipeTypes;
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotBookCategory;
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotRecipe;
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotShapelessRecipe;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import net.minecraft.client.ClientRecipeBook;
import net.minecraft.client.RecipeBookCategories;
import net.minecraft.client.gui.screens.recipebook.RecipeCollection;
import net.minecraft.core.RegistryAccess;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.RecipeType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
import java.util.Map;
@Mixin(ClientRecipeBook.class)
public abstract class ClientRecipeBookMixin {
@Shadow
protected static Map<RecipeBookCategories, List<List<RecipeHolder<?>>>> categorizeAndGroupRecipes(Iterable<RecipeHolder<?>> recipes) {
return null;
}
@Shadow private Map<RecipeBookCategories, List<RecipeCollection>> collectionsByTab;
@Shadow private List<RecipeCollection> allCollections;
@Inject(method = "getCategory", at = @At(value = "HEAD"), cancellable = true)
private static void addCustomCategory(RecipeHolder<?> recipe, CallbackInfoReturnable<RecipeBookCategories> cir) {
Recipe<?> recipe2 = recipe.value();
if (recipe2 instanceof CookingPotRecipe || recipe2 instanceof CookingPotShapelessRecipe) {
CookingPotBookCategory category = recipe2 instanceof CookingPotRecipe
? ((CookingPotRecipe) recipe2).getCategory()
: ((CookingPotShapelessRecipe) recipe2).category();
RecipeBookCategories var7;
System.out.println(category);
switch (category) {
case MISC -> var7 = CobblemonRecipeCategories.COOKING_POT_MISC.toVanillaCategory();
case FOODS -> var7 = CobblemonRecipeCategories.COOKING_POT_FOODS.toVanillaCategory();
case MEDICINES -> var7 = CobblemonRecipeCategories.COOKING_POT_MEDICINES.toVanillaCategory();
case BAITS -> var7 = CobblemonRecipeCategories.COOKING_POT_BAITS.toVanillaCategory();
default -> throw new MatchException(null, null);
}
cir.setReturnValue(var7);
}
}
@Inject(method = "setupCollections", at = @At("RETURN"))
private void addCustomAggregateCategories(Iterable<RecipeHolder<?>> recipes, RegistryAccess registryAccess, CallbackInfo ci) {
Map<RecipeBookCategories, List<List<RecipeHolder<?>>>> map = categorizeAndGroupRecipes(recipes);
Map<RecipeBookCategories, List<RecipeCollection>> map2 = Maps.newHashMap();
ImmutableList.Builder<RecipeCollection> builder = ImmutableList.builder();
map.forEach((recipeBookCategories, list) -> {
List<RecipeCollection> collections = list.stream()
.map(listx -> new RecipeCollection(registryAccess, listx))
.peek(builder::add)
.collect(ImmutableList.toImmutableList());
map2.put(recipeBookCategories, collections);
});
RecipeBookCategories.AGGREGATE_CATEGORIES.forEach((recipeBookCategories, list) -> map2.put(recipeBookCategories, (List)list.stream().flatMap((recipeBookCategoriesx) -> ((List)map2.getOrDefault(recipeBookCategoriesx, ImmutableList.of())).stream()).collect(ImmutableList.toImmutableList())));
CobblemonRecipeCategories.Companion.getCustomAggregateCategories().forEach((recipeBookCategories, list) -> map2.put(recipeBookCategories, (List)list.stream().flatMap((recipeBookCategoriesx) -> ((List)map2.getOrDefault(recipeBookCategoriesx, ImmutableList.of())).stream()).collect(ImmutableList.toImmutableList())));
this.collectionsByTab = ImmutableMap.copyOf(map2);
this.allCollections = builder.build();
}
}
@@ -0,0 +1,73 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.CobblemonItems;
import com.cobblemon.mod.common.CobblemonRecipeCategories;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.RecipeBookCategories;
import net.minecraft.world.inventory.RecipeBookType;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.gen.Invoker;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.ArrayList;
import java.util.List;
@Mixin(RecipeBookCategories.class)
public class RecipeBookCategoriesMixin {
@Final
@Mutable
@Shadow
private static RecipeBookCategories[] $VALUES;
@Invoker("<init>")
private static RecipeBookCategories cobblemon$createCategory(String name, int ordinal, ItemStack... icons) {
throw new AssertionError();
}
@Inject(
method = "<clinit>",
at = @At("TAIL")
)
private static void cobblemon$addRecipeBookType(CallbackInfo ci) {
ArrayList<RecipeBookCategories> types = new ArrayList<>(List.of($VALUES));
types.add(cobblemon$createCategory("COOKING_POT_SEARCH", $VALUES.length, new ItemStack(Items.COMPASS)));
types.add(cobblemon$createCategory("COOKING_POT_FOODS", $VALUES.length + 1, new ItemStack(CobblemonItems.ROASTED_LEEK)));
types.add(cobblemon$createCategory("COOKING_POT_MEDICINES", $VALUES.length + 2, new ItemStack(CobblemonItems.HP_UP)));
types.add(cobblemon$createCategory("COOKING_POT_BAITS", $VALUES.length + 3, new ItemStack(CobblemonItems.PERSIM_BERRY)));
types.add(cobblemon$createCategory("COOKING_POT_MISC", $VALUES.length + 4, new ItemStack(CobblemonItems.MIRROR_HERB)));
$VALUES = types.toArray(RecipeBookCategories[]::new);
}
@Inject(method = "getCategories", at = @At("HEAD"), cancellable = true)
private static void modifyCategories(RecipeBookType recipeBookType, CallbackInfoReturnable<List<RecipeBookCategories>> cir) {
if (recipeBookType == RecipeBookType.valueOf("COOKING_POT")) {
List var10000 = ImmutableList.of(
CobblemonRecipeCategories.COOKING_POT_SEARCH.toVanillaCategory(),
CobblemonRecipeCategories.COOKING_POT_FOODS.toVanillaCategory(),
CobblemonRecipeCategories.COOKING_POT_MEDICINES.toVanillaCategory(),
CobblemonRecipeCategories.COOKING_POT_BAITS.toVanillaCategory(),
CobblemonRecipeCategories.COOKING_POT_MISC.toVanillaCategory());
cir.setReturnValue(var10000);
return;
}
}
}
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.CobblemonRecipeCategories;
import net.minecraft.client.ClientRecipeBook;
import net.minecraft.client.Minecraft;
import net.minecraft.client.RecipeBookCategories;
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
import net.minecraft.client.gui.screens.recipebook.RecipeBookPage;
import net.minecraft.client.gui.screens.recipebook.RecipeBookTabButton;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(RecipeBookComponent.class)
public class RecipeBookComponentMixin {
@Shadow private ClientRecipeBook book;
@Shadow protected Minecraft minecraft;
@Shadow private int width;
@Shadow private int xOffset;
@Shadow private int height;
@Shadow @Final private RecipeBookPage recipeBookPage;
@Inject(method = "updateTabs", at = @At("HEAD"), cancellable = true)
private void customUpdateTabs(CallbackInfo ci) {
RecipeBookComponent instance = (RecipeBookComponent) (Object) this;
int i = (width - 147) / 2 - xOffset - 30;
int j = (height - 166) / 2 + 3;
int k = 27;
int l = 0;
for (RecipeBookTabButton recipeBookTabButton : instance.tabButtons) {
RecipeBookCategories recipeBookCategories = recipeBookTabButton.getCategory();
if (recipeBookCategories != RecipeBookCategories.CRAFTING_SEARCH && recipeBookCategories != RecipeBookCategories.FURNACE_SEARCH && recipeBookCategories != CobblemonRecipeCategories.COOKING_POT_SEARCH.toVanillaCategory()) {
if (recipeBookTabButton.updateVisibility(book)) {
recipeBookTabButton.setPosition(i, j + 27 * l++);
recipeBookTabButton.startAnimation(minecraft);
}
} else {
recipeBookTabButton.visible = true;
recipeBookTabButton.setPosition(i, j + k * l++);
}
}
ci.cancel();
}
}
@@ -0,0 +1,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.mixin;
import net.minecraft.stats.RecipeBookSettings;
import net.minecraft.world.inventory.RecipeBookType;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
@Mixin(RecipeBookSettings.class)
public class RecipeBookSettingsMixin {
@Shadow @Final public Map<RecipeBookType, RecipeBookSettings.TypeSettings> states;
@Inject(method = "<init>", at = @At("TAIL"))
private void cobblemon$init(CallbackInfo ci) {
states.put(RecipeBookType.valueOf("COOKING_POT"), new RecipeBookSettings.TypeSettings(true, true));
}
}
@@ -0,0 +1,40 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import net.minecraft.client.ClientRecipeBook;
import net.minecraft.client.gui.screens.recipebook.RecipeBookTabButton;
import net.minecraft.client.gui.screens.recipebook.RecipeCollection;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
@Mixin(RecipeBookTabButton.class)
public class RecipeBookTabButtonMixin {
@Inject(method = "updateVisibility", at = @At("HEAD"), cancellable = true)
private void customUpdateVisibility(ClientRecipeBook recipeBook, CallbackInfoReturnable<Boolean> cir) {
RecipeBookTabButton instance = (RecipeBookTabButton) (Object) this;
List<RecipeCollection> list = recipeBook.getCollection(instance.getCategory());
boolean visible = false;
if (list != null) {
for (RecipeCollection recipeCollection : list) {
if (recipeCollection.hasKnownRecipes() && recipeCollection.hasFitting()) {
visible = true;
break;
}
}
}
instance.visible = visible;
cir.setReturnValue(visible);
}
}
@@ -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.mixin;
import net.minecraft.world.inventory.RecipeBookType;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.gen.Invoker;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.ArrayList;
import java.util.List;
@Mixin(value = RecipeBookType.class)
public class RecipeBookTypeMixin {
@Final
@Mutable
@Shadow
private static RecipeBookType[] $VALUES; // moj
@Invoker("<init>")
private static RecipeBookType cobblemon$createBookType(String name, int ordinal) {
throw new AssertionError();
}
@Inject(
method = "<clinit>",
at = @At("TAIL")
)
private static void cobblemon$addRecipeBookType(CallbackInfo ci) {
ArrayList<RecipeBookType> types = new ArrayList<>(List.of($VALUES));
types.add(cobblemon$createBookType("COOKING_POT", $VALUES.length));
$VALUES = types.toArray(RecipeBookType[]::new);
}
}
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.client.gui.screens.recipebook.RecipeCollection;
import net.minecraft.stats.RecipeBook;
import net.minecraft.world.entity.player.StackedContents;
import net.minecraft.world.item.crafting.RecipeHolder;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
import java.util.Set;
@Mixin(RecipeCollection.class)
public class RecipeCollectionMixin {
@Shadow @Final private Set<RecipeHolder<?>> fitsDimensions;
@Shadow @Final private Set<RecipeHolder<?>> craftable;
@Shadow @Final private List<RecipeHolder<?>> recipes;
@Inject(method = "canCraft", at = @At("HEAD"), cancellable = true)
private void customCanCraft(StackedContents handler, int width, int height, RecipeBook book, CallbackInfo ci) {
for (RecipeHolder<?> recipeHolder : recipes) {
boolean bl = recipeHolder.value().canCraftInDimensions(width, height) && book.contains(recipeHolder);
if (bl) {
fitsDimensions.add(recipeHolder);
} else {
fitsDimensions.remove(recipeHolder);
}
if (bl && handler.canCraft(recipeHolder.value(), null)) {
craftable.add(recipeHolder);
} else {
craftable.remove(recipeHolder);
}
}
ci.cancel();
}
}
@@ -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.mixin;
import com.google.gson.JsonElement;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.item.crafting.RecipeManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
@Mixin(RecipeManager.class)
public class RecipeManagerMixin {
@Inject(method = "apply*", at = @At(value = "HEAD"))
private void logRecipeCreation(Map<ResourceLocation, JsonElement> object, ResourceManager resourceManager, ProfilerFiller profiler, CallbackInfo ci) {
for (Map.Entry<ResourceLocation, JsonElement> entry : object.entrySet()) {
ResourceLocation resourceLocation = entry.getKey();
System.out.println("Recipe found: " + resourceLocation);
}
}
}
@@ -0,0 +1,22 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.accessor;
import net.minecraft.client.gui.components.StateSwitchingButton;
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
import net.minecraft.network.chat.Component;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(RecipeBookComponent.class)
public interface RecipeBookComponentAccessor {
@Accessor("filterButton")
StateSwitchingButton getFilterButton();
}
@@ -99,6 +99,7 @@ import com.mongodb.client.MongoClient
import com.mongodb.client.MongoClients
import net.minecraft.client.Minecraft
import net.minecraft.commands.synchronization.SingletonArgumentInfo
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
@@ -172,6 +173,8 @@ object Cobblemon {
this.LOGGER.info(" - Branch: ${CobblemonBuildDetails.BRANCH}")
}
implementation.registerRecipeSerializers()
implementation.registerRecipeTypes()
implementation.registerPermissionValidator()
implementation.registerSoundEvents()
implementation.registerDataComponents()
@@ -183,6 +186,7 @@ object Cobblemon {
implementation.registerVillagers()
implementation.registerWorldGenFeatures()
implementation.registerParticles()
implementation.registerMenu()
implementation.registerEntityDataSerializers()
implementation.registerCriteria()
implementation.registerEntitySubPredicates()
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.block.entity.PokeCakeBlockEntity
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import com.cobblemon.mod.common.block.entity.HealingMachineBlockEntity
import com.cobblemon.mod.common.block.entity.PCBlockEntity
@@ -45,9 +46,9 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
@JvmField
val PASTURE: BlockEntityType<PokemonPastureBlockEntity> = this.create("pasture", BlockEntityType.Builder.of(::PokemonPastureBlockEntity, CobblemonBlocks.PASTURE).build(null))
@JvmField
val SIGN: BlockEntityType<CobblemonSignBlockEntity> = this.create("sign", BlockEntityType.Builder.of(::CobblemonSignBlockEntity, CobblemonBlocks.APRICORN_SIGN, CobblemonBlocks.APRICORN_WALL_SIGN).build(null))
val SIGN: BlockEntityType<CobblemonSignBlockEntity> = this.create("sign", BlockEntityType.Builder.of(::CobblemonSignBlockEntity, CobblemonBlocks.APRICORN_SIGN, CobblemonBlocks.APRICORN_WALL_SIGN, CobblemonBlocks.SACCHARINE_SIGN, CobblemonBlocks.SACCHARINE_WALL_SIGN).build(null))
@JvmField
val HANGING_SIGN: BlockEntityType<CobblemonHangingSignBlockEntity> = this.create("hanging_sign", BlockEntityType.Builder.of(::CobblemonHangingSignBlockEntity, CobblemonBlocks.APRICORN_HANGING_SIGN, CobblemonBlocks.APRICORN_WALL_HANGING_SIGN).build(null))
val HANGING_SIGN: BlockEntityType<CobblemonHangingSignBlockEntity> = this.create("hanging_sign", BlockEntityType.Builder.of(::CobblemonHangingSignBlockEntity, CobblemonBlocks.APRICORN_HANGING_SIGN, CobblemonBlocks.APRICORN_WALL_HANGING_SIGN, CobblemonBlocks.SACCHARINE_HANGING_SIGN, CobblemonBlocks.SACCHARINE_WALL_HANGING_SIGN).build(null))
@JvmField
val GILDED_CHEST: BlockEntityType<GildedChestBlockEntity> = this.create("chest", BlockEntityType.Builder.of(::GildedChestBlockEntity,
@@ -86,4 +87,36 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
val DISPLAY_CASE: BlockEntityType<DisplayCaseBlockEntity> = this.create("display_case",
BlockEntityType.Builder.of(::DisplayCaseBlockEntity, CobblemonBlocks.DISPLAY_CASE).build(null)
)
@JvmField
val INCENSE_SWEET: BlockEntityType<SweetIncenseBlockEntity> = this.create("incense_sweet",
BlockEntityType.Builder.of(::SweetIncenseBlockEntity, CobblemonBlocks.INCENSE_SWEET).build(null)
)
@JvmField
val CAMPFIRE: BlockEntityType<CampfireBlockEntity> = this.create("campfire_pot",
BlockEntityType.Builder.of(::CampfireBlockEntity, CobblemonBlocks.CAMPFIRE).build(null)
)
/*@JvmField
val LURE_CAKE: BlockEntityType<LureCakeBlockEntity> =
BlockEntityType.Builder.of({ pos, state -> LureCakeBlockEntity(pos, state) },
CobblemonBlocks.LURE_CAKE
).build(null)*/
@JvmField
val LURE_CAKE: BlockEntityType<LureCakeBlockEntity> = this.create(
"lure_cake",
BlockEntityType.Builder.of(::LureCakeBlockEntity, CobblemonBlocks.LURE_CAKE).build(null)
)
@JvmField
val POKE_CAKE: BlockEntityType<PokeCakeBlockEntity> = this.create(
"poke_cake",
BlockEntityType.Builder.of(::PokeCakeBlockEntity, CobblemonBlocks.POKE_CAKE).build(null)
)
@JvmField
val CANDLE_POKE_CAKE: BlockEntityType<CandlePokeCakeBlockEntity> = this.create(
"candle_poke_cake",
BlockEntityType.Builder.of(::CandlePokeCakeBlockEntity, CobblemonBlocks.CANDLE_POKE_CAKE).build(null)
)
}
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.apricorn.Apricorn
import com.cobblemon.mod.common.block.*
import com.cobblemon.mod.common.block.CampfireBlock
import com.cobblemon.mod.common.block.LecternBlock
import com.cobblemon.mod.common.block.MintBlock.MintType
import com.cobblemon.mod.common.block.chest.GildedChestBlock
@@ -20,6 +21,7 @@ import com.cobblemon.mod.common.block.sign.CobblemonWallSignBlock
import com.cobblemon.mod.common.mixin.invoker.*
import com.cobblemon.mod.common.platform.PlatformRegistry
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.core.BlockPos
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
@@ -27,9 +29,13 @@ import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.valueproviders.UniformInt
import net.minecraft.world.effect.MobEffects
import net.minecraft.world.entity.EntityType
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.state.BlockBehaviour
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.BlockSetType
import net.minecraft.world.level.block.state.properties.BlockStateProperties.LIT
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument
import net.minecraft.world.level.block.state.properties.WoodType
import net.minecraft.world.level.material.MapColor
@@ -43,6 +49,8 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val APRICORN_BLOCK_SET_TYPE = BlockSetType("apricorn")
val APRICORN_WOOD_TYPE = WoodType.register(WoodType("apricorn", APRICORN_BLOCK_SET_TYPE))
val SACCHARINE_BLOCK_SET_TYPE = BlockSetType("saccharine")
val SACCHARINE_WOOD_TYPE = WoodType.register(WoodType("saccharine", SACCHARINE_BLOCK_SET_TYPE))
// Evolution Ores
@JvmField
@@ -159,15 +167,77 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val YELLOW_APRICORN_SAPLING = this.create("yellow_apricorn_sapling", ApricornSaplingBlock(PLANT_PROPERTIES, Apricorn.YELLOW))
// Saccharines
@JvmField
val SACCHARINE_LOG = log("saccharine_log", arg2 = MapColor.COLOR_BLUE)
@JvmField
val STRIPPED_SACCHARINE_LOG = this.create("stripped_saccharine_log", SaccharineStrippedLogBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_LIGHT_BLUE).instrument(NoteBlockInstrument.BASS).strength(2.0F).sound(SoundType.WOOD).ignitedByLava())) //log("stripped_saccharine_log")
@JvmField
val SACCHARINE_WOOD = log("saccharine_wood")
@JvmField
val STRIPPED_SACCHARINE_WOOD = this.create("stripped_saccharine_wood", SaccharineStrippedLogBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_LIGHT_BLUE).instrument(NoteBlockInstrument.BASS).strength(2.0F).sound(SoundType.WOOD).ignitedByLava())) //log("stripped_saccharine_log")
@JvmField
val SACCHARINE_PLANKS = this.create("saccharine_planks", Block(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_LIGHT_BLUE).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
@JvmField
val SACCHARINE_LEAVES = this.create("saccharine_leaves", SaccharineLeafBlock(
BlockBehaviour.Properties.of().mapColor(
MapColor.PLANT
).strength(0.2f).randomTicks().sound(SoundType.GRASS).noOcclusion()
.isValidSpawn { _: BlockState?, _: BlockGetter?, _: BlockPos?, entity: EntityType<*>? ->
entity === EntityType.OCELOT || entity === EntityType.PARROT
}.isSuffocating { _: BlockState?, _: BlockGetter?, _: BlockPos? ->
false
}.isViewBlocking { _: BlockState?, _: BlockGetter?, _: BlockPos? ->
false
}.ignitedByLava().pushReaction(PushReaction.DESTROY)
.isRedstoneConductor { _: BlockState?, _: BlockGetter?, _: BlockPos? ->
false
}))
@JvmField
val SACCHARINE_FENCE = this.create("saccharine_fence", FenceBlock(BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
@JvmField
val SACCHARINE_FENCE_GATE = this.create("saccharine_fence_gate", FenceGateBlock(SACCHARINE_WOOD_TYPE, BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
val SACCHARINE_BUTTON = this.create("saccharine_button", BlocksInvoker.createWoodenButtonBlock(BlockSetType.OAK))
@JvmField
val SACCHARINE_PRESSURE_PLATE = this.create("saccharine_pressure_plate", PressurePlateBlockInvoker.`cobblemon$create`(SACCHARINE_BLOCK_SET_TYPE, BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).noCollission().strength(0.5f).sound(SoundType.WOOD)))
@JvmField
val SACCHARINE_SIGN = this.create("saccharine_sign", CobblemonSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_SIGN), SACCHARINE_WOOD_TYPE))
@JvmField
val SACCHARINE_WALL_SIGN = this.create("saccharine_wall_sign", CobblemonWallSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_WALL_SIGN), SACCHARINE_WOOD_TYPE))
@JvmField
val SACCHARINE_HANGING_SIGN = this.create("saccharine_hanging_sign", CobblemonHangingSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_WALL_HANGING_SIGN), SACCHARINE_WOOD_TYPE))
@JvmField
val SACCHARINE_WALL_HANGING_SIGN = this.create("saccharine_wall_hanging_sign", CobblemonWallHangingSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_HANGING_SIGN), SACCHARINE_WOOD_TYPE))
@JvmField
val SACCHARINE_SLAB = this.create("saccharine_slab", SlabBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
@JvmField
val SACCHARINE_STAIRS = this.create("saccharine_stairs", StairsBlockInvoker.`cobblemon$create`(SACCHARINE_PLANKS.defaultBlockState(), BlockBehaviour.Properties.ofFullCopy(SACCHARINE_PLANKS)))
@JvmField
val SACCHARINE_DOOR = this.create("saccharine_door", DoorBlockInvoker.`cobblemon$create`(SACCHARINE_BLOCK_SET_TYPE, BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(3.0F).sound(SoundType.WOOD).noOcclusion()))
@JvmField
val SACCHARINE_TRAPDOOR = this.create("saccharine_trapdoor", TrapdoorBlockInvoker.`cobblemon$create`(SACCHARINE_BLOCK_SET_TYPE, BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(3.0F).sound(SoundType.WOOD).noOcclusion()))
@JvmField
val SACCHARINE_SAPLING = this.create("saccharine_sapling", SaccharineSaplingBlock(PLANT_PROPERTIES))
@JvmField
val MEDICINAL_LEEK = this.create("medicinal_leek", MedicinalLeekBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.CRIMSON_NYLIUM).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MEDICINAL_LEEK_SOUNDS)))
@JvmField
val BUGWORT = this.create("bugwort", BugwortBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.COLOR_PURPLE).noCollission().randomTicks().instabreak().sound(CobblemonSounds.MEDICINAL_LEEK_SOUNDS)))
@JvmField
val ENERGY_ROOT = this.create("energy_root", EnergyRootBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.DIRT).noCollission().randomTicks().instabreak().sound(CobblemonSounds.ENERGY_ROOT_SOUNDS)))
@JvmField
val BIG_ROOT = this.create("big_root", BigRootBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.PLANT).noCollission().randomTicks().instabreak().sound(CobblemonSounds.BIG_ROOT_SOUNDS)))
@JvmField
val REVIVAL_HERB = this.create("revival_herb", RevivalHerbBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).mapColor(MapColor.PLANT).ignitedByLava().noCollission().instabreak().sound(CobblemonSounds.REVIVAL_HERB_SOUNDS)))
@JvmField
val LURE_CAKE = this.create("lure_cake", LureCakeBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY)))
@JvmField
val POKE_CAKE = this.create("poke_cake", PokeCakeBlock(BlockBehaviour.Properties.ofFullCopy(LURE_CAKE)))
@JvmField
val CANDLE_POKE_CAKE = this.create("candle_poke_cake", CandlePokeCakeBlock(BlockBehaviour.Properties.ofFullCopy(LURE_CAKE).lightLevel { blockState -> if (blockState.getValue(LIT) == true) 3 else 0 }))
@JvmField
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
@JvmField
@@ -319,6 +389,24 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val YELLOW_APRICORN = apricornBlock("yellow_apricorn", Apricorn.YELLOW)
@JvmField
val BLACK_CAMPFIRE_POT = create("campfire_pot_black", CampfirePotBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BLACK).sound(CobblemonSounds.CAMPFIRE_POT_SOUNDS).strength(0.5F).pushReaction(PushReaction.BLOCK).noOcclusion()))
@JvmField
val BLUE_CAMPFIRE_POT = create("campfire_pot_blue", CampfirePotBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BLUE).sound(CobblemonSounds.CAMPFIRE_POT_SOUNDS).strength(0.5F).pushReaction(PushReaction.BLOCK).noOcclusion()))
@JvmField
val GREEN_CAMPFIRE_POT = create("campfire_pot_green", CampfirePotBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_GREEN).sound(CobblemonSounds.CAMPFIRE_POT_SOUNDS).strength(0.5F).pushReaction(PushReaction.BLOCK).noOcclusion()))
@JvmField
val PINK_CAMPFIRE_POT = create("campfire_pot_pink", CampfirePotBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_PINK).sound(CobblemonSounds.CAMPFIRE_POT_SOUNDS).strength(0.5F).pushReaction(PushReaction.BLOCK).noOcclusion()))
@JvmField
val RED_CAMPFIRE_POT = create("campfire_pot_red", CampfirePotBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_RED).sound(CobblemonSounds.CAMPFIRE_POT_SOUNDS).strength(0.5F).pushReaction(PushReaction.BLOCK).noOcclusion()))
@JvmField
val WHITE_CAMPFIRE_POT = create("campfire_pot_white", CampfirePotBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_LIGHT_GRAY).sound(CobblemonSounds.CAMPFIRE_POT_SOUNDS).strength(0.5F).pushReaction(PushReaction.BLOCK).noOcclusion()))
@JvmField
val YELLOW_CAMPFIRE_POT = create("campfire_pot_yellow", CampfirePotBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_YELLOW).sound(CobblemonSounds.CAMPFIRE_POT_SOUNDS).strength(0.5F).pushReaction(PushReaction.BLOCK).noOcclusion()))
@JvmField
val CAMPFIRE = create("campfire", CampfireBlock(BlockBehaviour.Properties.of().sound(SoundType.WOOD).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.PODZOL).strength(2.0F).lightLevel { if ((it.getValue(CampfireBlock.SOUL) as Boolean)) 9 else 14 }))
@JvmField
val RELIC_COIN_POUCH = create("relic_coin_pouch", CoinPouchBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.RELIC_COIN_POUCH_SOUNDS).pushReaction(PushReaction.DESTROY).noOcclusion(), true))
@JvmField
@@ -360,6 +448,9 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val DISPLAY_CASE = create("display_case", DisplayCaseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3F)))
@JvmField
val INCENSE_SWEET = create("incense_sweet", SweetIncenseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3f)))
@JvmField
val PASTURE = create("pasture", PastureBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BROWN).sound(SoundType.WOOD).strength(2F).noOcclusion().pushReaction(PushReaction.BLOCK).lightLevel { if ((it.getValue(PastureBlock.ON) as Boolean) && (it.getValue(PastureBlock.PART) == PastureBlock.PasturePart.TOP)) 10 else 0 }))
@@ -381,6 +472,8 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
val PEP_UP_FLOWER = this.create("pep_up_flower", FlowerBlock(MobEffects.LEVITATION, 10F, BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().instabreak().sound(SoundType.GRASS).offsetType(BlockBehaviour.OffsetType.XZ).pushReaction(PushReaction.DESTROY)))
@JvmField
val POTTED_PEP_UP_FLOWER = this.create("potted_pep_up_flower", BlocksInvoker.createFlowerPotBlock(PEP_UP_FLOWER))
@JvmField
val HEARTY_GRAINS = this.create("hearty_grains", HeartyGrainsBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.PLANT).noCollission().randomTicks().instabreak().sound(CobblemonSounds.VIVICHOKE_SOUNDS)))
/**
* Returns a map of all the blocks that can be stripped with an axe in the format of input - output.
@@ -389,7 +482,9 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
*/
fun strippedBlocks(): Map<Block, Block> = mapOf(
APRICORN_WOOD to STRIPPED_APRICORN_WOOD,
APRICORN_LOG to STRIPPED_APRICORN_LOG
APRICORN_LOG to STRIPPED_APRICORN_LOG,
SACCHARINE_WOOD to STRIPPED_SACCHARINE_WOOD,
SACCHARINE_LOG to STRIPPED_SACCHARINE_LOG
)
@@ -100,6 +100,10 @@ interface CobblemonImplementation {
*/
fun registerVillagers()
fun registerRecipeSerializers()
fun registerRecipeTypes()
/**
* TODO
*
@@ -108,6 +112,8 @@ interface CobblemonImplementation {
fun registerParticles()
fun registerMenu()
fun registerEntitySubPredicates()
@@ -8,8 +8,10 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.block.PotComponent
import com.cobblemon.mod.common.item.components.HeldItemCapableComponent
import com.cobblemon.mod.common.item.components.PokemonItemComponent
import com.cobblemon.mod.common.item.components.CookingComponent
import com.cobblemon.mod.common.item.RodBaitComponent
import com.cobblemon.mod.common.platform.PlatformRegistry
import net.minecraft.core.Registry
@@ -36,11 +38,22 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
.networkSynchronized(RodBaitComponent.PACKET_CODEC)
.build())
val POT_DATA: DataComponentType<PotComponent> = create("cooking_pot_item", DataComponentType.builder<PotComponent>()
.persistent(PotComponent.CODEC)
.networkSynchronized(PotComponent.PACKET_CODEC)
.build())
val COOKING_COMPONENT: DataComponentType<CookingComponent> = create("cooking_component", DataComponentType.builder<CookingComponent>()
.persistent(CookingComponent.CODEC)
.networkSynchronized(CookingComponent.PACKET_CODEC) // Use CODEC if no specific PACKET_CODEC is defined
.build())
fun register() {
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:pokemon_item"), POKEMON_ITEM)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:bait"), BAIT)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:cooking_pot_item"), POT_DATA)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:cooking_component"), COOKING_COMPONENT)
}
override val registry = BuiltInRegistries.DATA_COMPONENT_TYPE
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.api.text.gray
import com.cobblemon.mod.common.block.BerryBlock
import com.cobblemon.mod.common.block.MintBlock
import com.cobblemon.mod.common.block.MintBlock.MintType
import com.cobblemon.mod.common.client.cooking.AprijuiceTypes
import com.cobblemon.mod.common.client.pokedex.PokedexType
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
import com.cobblemon.mod.common.item.*
@@ -171,6 +172,25 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val POKEDEX_WHITE = pokedexItem(PokedexType.WHITE)
val campfire_pots = mutableListOf<CampfirePotItem>()
@JvmField
val CAMPFIRE_POT_BLACK = campfirePotItem(CobblemonBlocks.BLACK_CAMPFIRE_POT, "black")
@JvmField
val CAMPFIRE_POT_BLUE = campfirePotItem(CobblemonBlocks.BLUE_CAMPFIRE_POT, "blue")
@JvmField
val CAMPFIRE_POT_GREEN = campfirePotItem(CobblemonBlocks.GREEN_CAMPFIRE_POT, "green")
@JvmField
val CAMPFIRE_POT_PINK = campfirePotItem(CobblemonBlocks.PINK_CAMPFIRE_POT, "pink")
@JvmField
val CAMPFIRE_POT_RED = campfirePotItem(CobblemonBlocks.RED_CAMPFIRE_POT, "red")
@JvmField
val CAMPFIRE_POT_WHITE = campfirePotItem(CobblemonBlocks.WHITE_CAMPFIRE_POT, "white")
@JvmField
val CAMPFIRE_POT_YELLOW = campfirePotItem(CobblemonBlocks.YELLOW_CAMPFIRE_POT, "yellow")
@JvmField
val Hearty_Grains = compostableItem("hearty_grains", HeartyGrainsItem(CobblemonBlocks.HEARTY_GRAINS))
@JvmField
val VIVICHOKE = compostableItem("vivichoke")
@@ -261,6 +281,307 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val GIMMIGHOUL_CHEST = this.create("gimmighoul_chest", BlockItem(CobblemonBlocks.GIMMIGHOUL_CHEST, Item.Properties()))
// Saccharines
@JvmField
val SACCHARINE_LOG = blockItem("saccharine_log", CobblemonBlocks.SACCHARINE_LOG)
@JvmField
val STRIPPED_SACCHARINE_LOG = blockItem("stripped_saccharine_log", CobblemonBlocks.STRIPPED_SACCHARINE_LOG)
@JvmField
val SACCHARINE_WOOD = blockItem("saccharine_wood", CobblemonBlocks.SACCHARINE_WOOD)
@JvmField
val STRIPPED_SACCHARINE_WOOD = blockItem("stripped_saccharine_wood", CobblemonBlocks.STRIPPED_SACCHARINE_WOOD)
@JvmField
val SACCHARINE_PLANKS = blockItem("saccharine_planks", CobblemonBlocks.SACCHARINE_PLANKS)
@JvmField
val SACCHARINE_LEAVES = compostableBlockItem("saccharine_leaves", CobblemonBlocks.SACCHARINE_LEAVES)
@JvmField
val SACCHARINE_BOAT = create("saccharine_boat", CobblemonBoatItem(CobblemonBoatType.SACCHARINE, false, Item.Properties().stacksTo(1)))
@JvmField
val SACCHARINE_CHEST_BOAT = create("saccharine_chest_boat", CobblemonBoatItem(CobblemonBoatType.SACCHARINE, true, Item.Properties().stacksTo(1)))
@JvmField
val SACCHARINE_DOOR = blockItem("saccharine_door", CobblemonBlocks.SACCHARINE_DOOR)
@JvmField
val SACCHARINE_TRAPDOOR = blockItem("saccharine_trapdoor", CobblemonBlocks.SACCHARINE_TRAPDOOR)
@JvmField
val SACCHARINE_FENCE = blockItem("saccharine_fence", CobblemonBlocks.SACCHARINE_FENCE)
@JvmField
val SACCHARINE_FENCE_GATE = blockItem("saccharine_fence_gate", CobblemonBlocks.SACCHARINE_FENCE_GATE)
@JvmField
val SACCHARINE_BUTTON = blockItem("saccharine_button", CobblemonBlocks.SACCHARINE_BUTTON)
@JvmField
val SACCHARINE_PRESSURE_PLATE = blockItem("saccharine_pressure_plate", CobblemonBlocks.SACCHARINE_PRESSURE_PLATE)
@JvmField
val SACCHARINE_SLAB = blockItem("saccharine_slab", CobblemonBlocks.SACCHARINE_SLAB)
@JvmField
val SACCHARINE_STAIRS = blockItem("saccharine_stairs", CobblemonBlocks.SACCHARINE_STAIRS)
@JvmField
val SACCHARINE_SIGN = this.create("saccharine_sign", SignItem(Item.Properties().stacksTo(16), CobblemonBlocks.SACCHARINE_SIGN, CobblemonBlocks.SACCHARINE_WALL_SIGN))
@JvmField
val SACCHARINE_HANGING_SIGN = this.create("saccharine_hanging_sign", HangingSignItem(CobblemonBlocks.SACCHARINE_HANGING_SIGN, CobblemonBlocks.SACCHARINE_WALL_HANGING_SIGN, Item.Properties().stacksTo(16)))
@JvmField
val SACCHARINE_SAPLING = saccharineSaplingItem("saccharine_sapling", SaccharineSaplingItem(CobblemonBlocks.SACCHARINE_SAPLING))
@JvmField
val SWEET_SAP = noSettingsItem("sweet_sap")
@JvmField
val BUGWORT = bugwortItem("bugwort", BugwortItem(CobblemonBlocks.BUGWORT))
@JvmField
val POKE_BAIT = noSettingsItem("poke_bait")
@JvmField
val LURE_CAKE = blockItem("lure_cake", CobblemonBlocks.LURE_CAKE)
@JvmField
val POKE_CAKE = blockItem("poke_cake", CobblemonBlocks.POKE_CAKE)
val aprijuices = mutableListOf<AprijuiceItem>()
@JvmField
val APRIJUICE_BLACK = aprijuiceItem(AprijuiceTypes.BLACK)
@JvmField
val APRIJUICE_RED = aprijuiceItem(AprijuiceTypes.RED)
@JvmField
val APRIJUICE_BLUE = aprijuiceItem(AprijuiceTypes.BLUE)
@JvmField
val APRIJUICE_GREEN = aprijuiceItem(AprijuiceTypes.GREEN)
@JvmField
val APRIJUICE_YELLOW = aprijuiceItem(AprijuiceTypes.YELLOW)
@JvmField
val APRIJUICE_WHITE = aprijuiceItem(AprijuiceTypes.WHITE)
@JvmField
val APRIJUICE_PINK = aprijuiceItem(AprijuiceTypes.PINK)
@JvmField
val POKE_PUFF = noSettingsItem("poke_puff") // todo make a PokePuffItem class for friendship boosting purposes
// FOODS
@JvmField
val SWEET_HEART = noSettingsItem("sweet_heart") // todo make a SweetHeartItem class for breeding purposes
@JvmField
val PEWTER_CRUNCHIES = create("pewter_crunchies", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField
val RAGE_CANDY_BAR = create("rage_candy_bar", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField
val LAVA_COOKIE = create("lava_cookie", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField
val OLD_GATEAU = create("old_gateau", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField
val CASTELIACONE = create("casteliacone", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField
val SHALOR_SABLE = create("shalor_sable", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField
val LUMIOSE_GALETTE = create("lumiose_galette", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField
val BIG_MALASADA = create("big_malasada", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
/*@JvmField // todo see what we are doing for these (are there different kinds?)
val CURRY = create("curry", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.usingConvertsTo(Items.BOWL)
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})*/
@JvmField
val JUBILIFE_MUFFIN = create("jubilife_muffin", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
/*@JvmField // todo see what we are doing for these (are there different kinds?)
val SANDWICH = create("sandwich", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.usingConvertsTo(Items.BOWL)
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})*/
@JvmField
val CHOICE_DUMPLING = noSettingsItem("choice_dumpling") // todo make a ChoiceDumpingItem class for breeding purposes
@JvmField
val SWAP_SNACK = noSettingsItem("swap_snack") // todo make a SwapSnackItem class for breeding purposes
@JvmField
val TWICE_SPICED_BEETROOT = noSettingsItem("twice_spiced_beetroot") // todo make a TwiceSpiceBeetrootItem class for breeding purposes
@JvmField
val POTATO_MOCHI = create("potato_mochi", object : Item(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField
val FRIED_RICE = create("fried_rice", object : Item(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField
val CANDIED_APPLE = create("candied_apple", object : Item(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField
val CANDIED_BERRY = create("candied_berry", object : Item(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField
val JELLY_DOUGHNUT = create("jelly_doughnut", object : Item(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
// todo etc?
//@JvmField
//val SCATTER_BANG = this.create("scatter_bang", ScatterBangItem(Item.Settings()))
//@JvmField
//val STICKY_GLOB = this.create("sticky_glob", StickyGlobItem(Item.Settings()))
@JvmField
val RESTORATION_TANK = blockItem("restoration_tank", CobblemonBlocks.RESTORATION_TANK)
@JvmField
@@ -276,7 +597,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val DISPLAY_CASE = blockItem("display_case", CobblemonBlocks.DISPLAY_CASE)
@JvmField
val LECTERN = blockItem("lectern", CobblemonBlocks.LECTERN)
val INCENSE_SWEET = blockItem("incense_sweet", CobblemonBlocks.INCENSE_SWEET)
// Evolution items
@JvmField val LINK_CABLE = create("link_cable", LinkCableItem())
@@ -421,6 +743,20 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val ZINC = create("zinc", VitaminItem(Stats.SPECIAL_DEFENCE))
@JvmField
val HEALTH_MOCHI = create("health_mochi", MochiItem(Stats.HP))
@JvmField
val MUSCLE_MOCHI = create("muscle_mochi", MochiItem(Stats.ATTACK))
@JvmField
val RESIST_MOCHI = create("resist_mochi", MochiItem(Stats.DEFENCE))
@JvmField
val GENIUS_MOCHI = create("genius_mochi", MochiItem(Stats.SPECIAL_ATTACK))
@JvmField
val CLEVER_MOCHI = create("clever_mochi", MochiItem(Stats.SPECIAL_DEFENCE))
@JvmField
val SWIFT_MOCHI = create("swift_mochi", MochiItem(Stats.SPEED))
@JvmField
val FRESH_START_MOCHI = create("fresh_start_mochi", FreshStartMochiItem())
@JvmField
val GENIUS_FEATHER = create("genius_feather", FeatherItem(Stats.SPECIAL_ATTACK))
@JvmField
val SWIFT_FEATHER = create("swift_feather", FeatherItem(Stats.SPEED))
@@ -1263,6 +1599,18 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
return item
}
private fun campfirePotItem(block: Block, type: String): CampfirePotItem {
val item = create("campfire_pot_${type}", CampfirePotItem(block))
campfire_pots.add(item)
return item
}
private fun aprijuiceItem(type: AprijuiceTypes): AprijuiceItem {
val item = create("aprijuice_${type.name.lowercase()}", AprijuiceItem(type))
aprijuices.add(item)
return item
}
private fun heldItem(name: String, remappedName: String? = null): CobblemonItem = create(
name,
CobblemonItem(Item.Properties()).also {
@@ -1321,6 +1669,21 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
return item
}
private fun saccharineSaplingItem(name: String, saccharineSaplingItem: SaccharineSaplingItem): SaccharineSaplingItem {
val finalName = "saccharine_sapling"
val item = this.create(finalName, saccharineSaplingItem)
compostable(item, .65f)
return item
}
private fun bugwortItem(name: String, bugwortItem: BugwortItem): BugwortItem {
val finalName = "bugwort"
val item = this.create(finalName, bugwortItem)
compostable(item, .65f)
return item
}
private fun mintSeed(name: String, mintBlock: MintBlock): Item {
val finalName = "${name}_mint_seeds"
val item = this.blockItem(finalName, mintBlock)
@@ -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
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotMenu
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotScreen
import com.cobblemon.mod.common.platform.PlatformRegistry
import net.minecraft.client.gui.screens.MenuScreens
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceKey
import net.minecraft.world.inventory.MenuType
object CobblemonMenuType : PlatformRegistry<Registry<MenuType<*>>, ResourceKey<Registry<MenuType<*>>>, MenuType<*>>() {
val COOKING_POT = MenuType.register("cooking_pot", ::CookingPotMenu)
init {
MenuScreens.register(COOKING_POT, ::CookingPotScreen)
}
override val registry: Registry<MenuType<*>>
get() = BuiltInRegistries.MENU
override val resourceKey: ResourceKey<Registry<MenuType<*>>>
get() = Registries.MENU
}
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.client.net.battle.*
import com.cobblemon.mod.common.client.net.callback.move.OpenMoveCallbackHandler
import com.cobblemon.mod.common.client.net.callback.party.OpenPartyCallbackHandler
import com.cobblemon.mod.common.client.net.callback.partymove.OpenPartyMoveCallbackHandler
import com.cobblemon.mod.common.client.net.cooking.ToggleCookingPotLidHandler
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
import com.cobblemon.mod.common.client.net.data.UnlockReloadPacketHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
@@ -56,6 +57,8 @@ 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.cooking.SeasoningRegistrySyncPacket
import com.cobblemon.mod.common.net.messages.client.cooking.ToggleCookingPotLidPacket
import com.cobblemon.mod.common.net.messages.client.data.*
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
@@ -122,18 +125,11 @@ import com.cobblemon.mod.common.net.messages.server.storage.SwapPCPartyPokemonPa
import com.cobblemon.mod.common.net.messages.server.storage.party.MovePartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.ReleasePartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.SwapPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePCPokemonToPartyPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.MovePartyPokemonToPCPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.PCBoxWallpapersPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.ReleasePCPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.RequestChangePCBoxWallpaperPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.RequestRenamePCBoxPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.SortPCBoxPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.SwapPCPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.UnlinkPlayerFromPCPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.*
import com.cobblemon.mod.common.net.messages.server.trade.*
import com.cobblemon.mod.common.net.serverhandling.*
import com.cobblemon.mod.common.net.serverhandling.ChallengeHandler
import com.cobblemon.mod.common.net.serverhandling.ChallengeResponseHandler
import com.cobblemon.mod.common.net.serverhandling.RequestInteractionsHandler
import com.cobblemon.mod.common.net.serverhandling.battle.*
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectCancelledHandler
@@ -311,6 +307,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(PokedexDexSyncPacket.ID, PokedexDexSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(DexEntrySyncPacket.ID, DexEntrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(FishingBaitRegistrySyncPacket.ID, FishingBaitRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SeasoningRegistrySyncPacket.ID, SeasoningRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(FlowRegistrySyncPacket.ID, FlowRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
// Effects
@@ -364,6 +361,7 @@ object CobblemonNetwork {
// Pokédex scanning
list.add(PacketRegisterInfo(ServerConfirmedRegisterPacket.ID, ServerConfirmedRegisterPacket::decode, ServerConfirmedRegisterHandler))
return list
}
@@ -458,6 +456,9 @@ object CobblemonNetwork {
// NPC packets
list.add(PacketRegisterInfo(SaveNPCPacket.ID, SaveNPCPacket::decode, SaveNPCHandler))
// Cooking
list.add(PacketRegisterInfo(ToggleCookingPotLidPacket.ID, ToggleCookingPotLidPacket::decode, ToggleCookingPotLidHandler))
return list
}
@@ -0,0 +1,40 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common
import net.minecraft.client.RecipeBookCategories
enum class CobblemonRecipeCategories {
COOKING_POT_SEARCH("COOKING_POT_SEARCH"),
COOKING_POT_FOODS("COOKING_POT_FOODS"),
COOKING_POT_MEDICINES("COOKING_POT_MEDICINES"),
COOKING_POT_BAITS("COOKING_POT_BAITS"),
COOKING_POT_MISC("COOKING_POT_MISC");
companion object {
val customAggregateCategories: Map<RecipeBookCategories, List<RecipeBookCategories>> = mapOf(
COOKING_POT_SEARCH.toVanillaCategory() to listOf(
COOKING_POT_FOODS.toVanillaCategory(), COOKING_POT_MISC.toVanillaCategory(), COOKING_POT_MEDICINES.toVanillaCategory(), COOKING_POT_BAITS.toVanillaCategory()
)
)
}
var id: String
constructor(id: String) {
this.id = id
}
fun toVanillaCategory(): RecipeBookCategories {
return RecipeBookCategories.valueOf(this.id)
}
}
@@ -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
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotRecipe
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotShapelessRecipe
import com.cobblemon.mod.common.platform.PlatformRegistry
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceKey
import net.minecraft.world.item.crafting.Recipe
import net.minecraft.world.item.crafting.RecipeSerializer
object CobblemonRecipeSerializers : PlatformRegistry<Registry<RecipeSerializer<*>>, ResourceKey<Registry<RecipeSerializer<*>>>, RecipeSerializer<*>>() {
val COOKING_POT_COOKING: RecipeSerializer<*> = register("cobblemon:cooking_pot", CookingPotRecipe.Serializer()).also {
println("Registered cooking pot recipe serializer: $it")
}
val COOKING_POT_SHAPELESS: RecipeSerializer<*> = register(
"cobblemon:cooking_pot_shapeless",
CookingPotShapelessRecipe.Serializer()
).also {
println("Registered cooking pot shapeless recipe serializer: $it")
}
override val registry: Registry<RecipeSerializer<*>>
get() = BuiltInRegistries.RECIPE_SERIALIZER
override val resourceKey: ResourceKey<Registry<RecipeSerializer<*>>>
get() = Registries.RECIPE_SERIALIZER
fun <S : RecipeSerializer<T>, T : Recipe<*>> register(key: String, recipeSerializer: S): S {
return Registry.register(BuiltInRegistries.RECIPE_SERIALIZER, key, recipeSerializer) as S
}
}
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotRecipe
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotShapelessRecipe
import com.cobblemon.mod.common.platform.PlatformRegistry
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.crafting.Recipe
import net.minecraft.world.item.crafting.RecipeType
object CobblemonRecipeTypes : PlatformRegistry<Registry<RecipeType<*>>, ResourceKey<Registry<RecipeType<*>>>, RecipeType<*>>() {
val COOKING_POT_COOKING: RecipeType<CookingPotRecipe> = register<CookingPotRecipe>("cobblemon:cooking_pot")
val COOKING_POT_SHAPELESS: RecipeType<CookingPotShapelessRecipe> = register<CookingPotShapelessRecipe>("cobblemon:cooking_pot_shapeless")
override val registry: Registry<RecipeType<*>>
get() = BuiltInRegistries.RECIPE_TYPE
override val resourceKey: ResourceKey<Registry<RecipeType<*>>>
get() = Registries.RECIPE_TYPE
fun <T : Recipe<*>> register(identifier: String): RecipeType<T> {
return Registry.register(
BuiltInRegistries.RECIPE_TYPE,
identifier,
object : RecipeType<T> {
override fun toString(): String {
return identifier
}
}
).also { println("Registered recipe type with identifier: $identifier") }
}
}
@@ -110,6 +110,8 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
val MEDICINE_SPRAY_USE = this.create("item.medicine.spray.use")
@JvmField
val MEDICINE_FEATHER_USE = this.create("item.medicine.feather.use")
@JvmField
val MOCHI_USE = this.create("item.medicine.feather.use")
@JvmField
val MULCH_PLACE = this.create("block.mulch.place")
@@ -423,5 +425,37 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
DISPLAY_CASE_STEP
)
@JvmField
val CAMPFIRE_POT_PLACE_CAMPFIRE = this.create("block.campfire_pot.place_campfire")
@JvmField
val CAMPFIRE_POT_RETRIEVE = this.create("block.campfire_pot.retrieve")
@JvmField
val CAMPFIRE_POT_OPEN = this.create("block.campfire_pot.open")
@JvmField
val CAMPFIRE_POT_CLOSE = this.create("block.campfire_pot.close")
@JvmField
val CAMPFIRE_POT_USE = this.create("block.campfire_pot.use")
@JvmField
val CAMPFIRE_POT_COOK = this.create("block.campfire_pot.cook")
@JvmField
val CAMPFIRE_POT_CRAFT = this.create("block.campfire_pot.craft")
@JvmField
val CAMPFIRE_POT_PLACE = this.create("block.campfire_pot.place")
@JvmField
val CAMPFIRE_POT_BREAK = this.create("block.campfire_pot.break")
@JvmField
val CAMPFIRE_POT_HIT = this.create("block.campfire_pot.hit")
@JvmField
val CAMPFIRE_POT_STEP = this.create("block.campfire_pot.step")
@JvmField
val CAMPFIRE_POT_SOUNDS = SoundType(1f, 1.1f,
CAMPFIRE_POT_BREAK,
CAMPFIRE_POT_STEP,
CAMPFIRE_POT_PLACE,
CAMPFIRE_POT_HIT,
CAMPFIRE_POT_STEP
)
private fun create(name: String): SoundEvent = this.create(name, SoundEvent.createVariableRangeEvent(cobblemonResource(name)))
}
@@ -0,0 +1,98 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.cooking
import com.cobblemon.mod.common.item.components.CookingComponent
import net.minecraft.util.FastColor
import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.ItemStack
import kotlin.collections.flatMap
private val colorMap = mapOf(
"spicy" to 0xFEB37D,
"dry" to 0x8AE9FC,
"sweet" to 0xFFBEED,
"bitter" to 0x9EED8F,
"sour" to 0xFCF38A
)
private val bubbleColorMap = mapOf(
"spicy" to 0xFFD9AD,
"dry" to 0xBCF8FE,
"sweet" to 0xFEE3F9,
"bitter" to 0xC8F7BC,
"sour" to 0xFDFAB8
)
fun getColorMixFromSeasonings(seasonings: List<ItemStack>, forBubbles: Boolean = false): Int? {
val flavors = seasonings
.flatMap { Seasonings.getFromItemStack(it)?.flavors?.entries ?: emptyList() }
.groupingBy { it.key }
.fold(0) { acc, entry -> acc + entry.value }
val maxFlavorValue = flavors.values.maxOrNull()
val dominantFlavors = flavors.filter { it.value == maxFlavorValue }.map { it.key }
return getColorMixFromCookingComponent(dominantFlavors, forBubbles)
}
fun getTransparentColorMixFromSeasonings(seasonings: List<ItemStack>): Int? {
val baseColor = getColorMixFromSeasonings(seasonings) // This gets the original color
if (baseColor == null) return null
// Apply 50% transparency by ensuring the alpha channel is 0x80
return (baseColor and 0x00FFFFFF) or (0x80 shl 24)
}
fun getColorMixFromCookingComponent(cookingComponent: CookingComponent): Int? {
val dominantFlavors = cookingComponent.getDominantFlavors()
return getColorMixFromCookingComponent(dominantFlavors)
}
fun getColorMixFromCookingComponent(dominantFlavors: List<String>, forBubbles: Boolean = false): Int? {
val colors =
dominantFlavors.mapNotNull { if (forBubbles) bubbleColorMap[it] else colorMap[it] }
.map { FastColor.ARGB32.opaque(it) }
if (colors.isEmpty()) return null
val (alphaSum, redSum, greenSum, blueSum) = colors.fold(IntArray(4)) { acc, color ->
acc[0] += FastColor.ARGB32.alpha(color)
acc[1] += FastColor.ARGB32.red(color)
acc[2] += FastColor.ARGB32.green(color)
acc[3] += FastColor.ARGB32.blue(color)
acc
}
return FastColor.ARGB32.color(
alphaSum / colors.size,
redSum / colors.size,
greenSum / colors.size,
blueSum / colors.size
)
}
fun getColor(color: String): Int? {
return when (color.lowercase()) {
"red" -> DyeColor.RED
"orange" -> DyeColor.ORANGE
"yellow" -> DyeColor.YELLOW
"lime" -> DyeColor.LIME
"green" -> DyeColor.GREEN
"cyan" -> DyeColor.CYAN
"light blue" -> DyeColor.LIGHT_BLUE
"blue" -> DyeColor.BLUE
"purple" -> DyeColor.PURPLE
"magenta" -> DyeColor.MAGENTA
"pink" -> DyeColor.PINK
"white" -> DyeColor.WHITE
else -> null
}?.textureDiffuseColor
}
@@ -0,0 +1,39 @@
/*
* 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.cooking
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.resources.ResourceLocation
data class Seasoning(
val ingredient: ResourceLocation,
val flavors: Map<String, Int>, // Updated to store a map of flavor types and values
val color: String,
val quality: Int
) {
companion object {
val CODEC: Codec<Seasoning> = RecordCodecBuilder.create { builder ->
builder.group(
ResourceLocation.CODEC.fieldOf("ingredient").forGetter<Seasoning> { it.ingredient },
Codec.unboundedMap(Codec.STRING, Codec.INT).fieldOf("flavors").forGetter<Seasoning> { it.flavors }, // Use map codec
Codec.STRING.fieldOf("color").forGetter<Seasoning> { it.color },
Codec.INT.fieldOf("quality").forGetter<Seasoning> { it.quality }
).apply(builder, ::Seasoning)
}
val BLANK_SEASONING = Seasoning(
ingredient = cobblemonResource("blank"),
flavors = mapOf("spicy" to 0, "dry" to 0, "sweet" to 0, "bitter" to 0, "sour" to 0), // Default to all 0
color = "",
quality = 0
)
}
}
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.cooking
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.cooking.SeasoningRegistrySyncPacket
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.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.world.item.ItemStack
object Seasonings : JsonDataRegistry<Seasoning> {
override val id = cobblemonResource("seasonings")
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<Seasonings>()
override val typeToken: TypeToken<Seasoning> = TypeToken.get(Seasoning::class.java)
override val resourcePath = "seasonings"
override val gson: Gson = GsonBuilder()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.setPrettyPrinting()
.create()
private val itemMap = mutableMapOf<ResourceLocation, Seasoning>()
override fun sync(player: ServerPlayer) {
SeasoningRegistrySyncPacket(this.itemMap.values.toList()).sendToPlayer(player)
}
override fun reload(data: Map<ResourceLocation, Seasoning>) {
itemMap.clear()
data.forEach { id, seasoning ->
//println("Loaded Seasoning: $id -> $seasoning") // Debugging output
itemMap[id] = seasoning
}
}
fun getFromItemStack(stack: ItemStack): Seasoning? {
val itemId = BuiltInRegistries.ITEM.getKey(stack.item)
//println("Checking ItemStack: ${stack.item} -> Identifier: $itemId")
return if (itemId != null) getFromIdentifier(itemId) else null
}
fun getFromIdentifier(identifier: ResourceLocation): Seasoning? {
//println("Looking for Identifier: $identifier in ${itemMap.keys}")
return itemMap[identifier]
}
fun isSeasoning(stack: ItemStack) = BuiltInRegistries.ITEM.getKey(stack.item)?.let { itemMap.containsKey(it) } == true
}
@@ -247,6 +247,11 @@ object CobblemonEvents {
@JvmField
val BAIT_EFFECT_REGISTRATION = EventObservable<BaitEffectFunctionRegistryEvent>()
// Baits and Lures
@JvmField
val BAIT_SPAWN_POKEMON_MODIFY = EventObservable<BaitSpawnPokemonEvent.Modify>()
@JvmField
val COLLECT_EGG = CancelableObservable<CollectEggEvent>()
@JvmField
@@ -0,0 +1,59 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.fishing
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.item.ItemStack
/**
* Event that is fired when a Pokemon is spawned by a bobber.
*/
interface BaitSpawnPokemonEvent {
/**
* Event that is fired before a Pokemon is spawned by a bobber.
* @param bobber The PokeRodFishingBobberEntity that is spawning the Pokemon.
* @param chosenBucket The bucket that is chosen.
* @param rod The ItemStack of the rod that the bobber is attached to.
*/
data class Pre(
val bobber: PokeRodFishingBobberEntity, // todo replace this with the bait entity
val chosenBucket: SpawnBucket,
val rod: ItemStack
) : Cancelable(), BobberSpawnPokemonEvent
/**
* Event that is fired when a Pokemon is modified before it is spawned by a bobber.
* @param chosenBucket The bucket that is chosen.
* @param rod The ItemStack of the rod that the bobber is attached to.
* @param pokemon The Pokemon that is modified.
*/
data class Modify(
val chosenBucket: SpawnBucket,
val rod: ItemStack,
val pokemon: PokemonEntity
) : BobberSpawnPokemonEvent
/**
* Event that is fired after a Pokemon is spawned by a bobber.
* @param bobber The PokeRodFishingBobberEntity that is spawning the Pokemon.
* @param chosenBucket The bucket that is chosen.
* @param bait The ItemStack of the bait that is set on the rod.
* @param pokemon The Pokemon that is spawned.
*/
data class Post(
val bobber: PokeRodFishingBobberEntity,
val chosenBucket: SpawnBucket,
val bait: ItemStack,
val pokemon: PokemonEntity
) : BobberSpawnPokemonEvent
}
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.fishing
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.fishing.BaitEffectFunctionRegistryEvent
import com.cobblemon.mod.common.api.spawning.bait.BaitSpawnCause
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawnCause
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
@@ -127,6 +127,7 @@ class ParticleEvent(
sourceAlive = storm.sourceAlive,
sourceVisible = storm.sourceVisible,
onDespawn = {},
getParticleColor = storm.getParticleColor,
targetPos = storm.targetPos,
runtime = MoLangRuntime().setup(),
entity = storm.entity
@@ -30,7 +30,8 @@ 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.bait.BaitSpawner
import com.cobblemon.mod.common.api.spawning.context.BaitSpawningContext
import com.cobblemon.mod.common.api.spawning.preset.BasicSpawnDetailPreset
import com.cobblemon.mod.common.api.spawning.preset.BestSpawnerConfig
import com.cobblemon.mod.common.api.spawning.preset.PokemonSpawnDetailPreset
@@ -82,6 +83,7 @@ object BestSpawner {
val spawnerManagers = mutableListOf<SpawnerManager>(CobblemonWorldSpawnerManager)
var defaultPokemonDespawner: Despawner<PokemonEntity> = CobblemonAgingDespawner(getAgeTicks = { it.ticksLived })
lateinit var fishingSpawner: FishingSpawner
lateinit var baitSpawner: BaitSpawner
fun init() {
LOGGER.info("Starting the Best Spawner...")
@@ -93,6 +95,7 @@ object BestSpawner {
SpawningCondition.register(SurfaceSpawningCondition.NAME, SurfaceSpawningCondition::class.java)
SpawningCondition.register(SeafloorSpawningCondition.NAME, SeafloorSpawningCondition::class.java)
SpawningCondition.register(FishingSpawningCondition.NAME, FishingSpawningCondition::class.java)
SpawningCondition.register(BaitSpawningCondition.NAME, BaitSpawningCondition::class.java)
LOGGER.info("Loaded ${SpawningCondition.conditionTypes.size} spawning condition types.")
@@ -108,6 +111,8 @@ object BestSpawner {
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)
SpawningContext.register(name = "bait", clazz = BaitSpawningContext::class.java, defaultCondition = BaitSpawningCondition.NAME)
// todo do we want a bait context? Or just use Surface?
LOGGER.info("Loaded ${SpawningContext.contexts.size} spawning context types.")
@@ -133,5 +138,6 @@ object BestSpawner {
fun onServerStarted() {
spawnerManagers.forEach(SpawnerManager::onServerStarted)
fishingSpawner = FishingSpawner()
baitSpawner = BaitSpawner()
}
}
@@ -0,0 +1,231 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.bait
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.fishing.BaitSpawnPokemonEvent
import com.cobblemon.mod.common.api.fishing.FishingBait
import com.cobblemon.mod.common.api.pokemon.Natures
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.egg.EggGroup
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.spawning.SpawnBucket
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.PokemonSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.world.entity.Entity
import net.minecraft.world.item.ItemStack
import kotlin.collections.any
import kotlin.collections.contains
import kotlin.collections.filter
import kotlin.collections.filterIsInstance
import kotlin.collections.firstOrNull
import kotlin.collections.forEach
import kotlin.collections.plus
import kotlin.collections.random
import kotlin.collections.randomOrNull
import kotlin.let
import kotlin.random.Random.Default.nextInt
import kotlin.run
/**
* A spawning cause that is embellished with bait information. Could probably also
* have the bait entity or something.
*
* @author Hiroku
* @since February 3rd, 2024
*/
class BaitSpawnCause(
spawner: Spawner,
bucket: SpawnBucket,
entity: Entity?,
val baitStack: ItemStack,
val baitEffect: FishingBait?
) : SpawnCause(spawner, bucket, entity) {
companion object {
const val BAIT_ASPECT = "lured"
fun shinyReroll(pokemonEntity: PokemonEntity, effect: FishingBait.Effect) {
if (pokemonEntity.pokemon.shiny) return
val shinyOdds = Cobblemon.config.shinyRate.toInt()
if (shinyOdds <= 0) {
return
}
val randomNumber = nextInt(0, shinyOdds + 1)
if (randomNumber <= effect.value.toInt()) {
pokemonEntity.pokemon.shiny = true
}
}
fun alterNatureAttempt(pokemonEntity: PokemonEntity, effect: FishingBait.Effect) {
val baitStat = effect.subcategory?.let { it1 -> Stats.getStat(it1.path).identifier } ?: run {
LOGGER.warn("One of your nature baits is missing a subcategory and failed to effect a fished Pokemon")
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 == baitStat }
if (possibleNatures.isEmpty() || possibleNatures.any { it == pokemonEntity.pokemon.nature }) return
val takenNature = possibleNatures.random()
pokemonEntity.pokemon.nature = Natures.getNature(takenNature.name.path) ?: return
}
fun alterIVAttempt(pokemonEntity: PokemonEntity, effect: FishingBait.Effect) {
val iv = effect.subcategory ?: return
if ((pokemonEntity.pokemon.ivs[Stats.getStat(iv.path)] ?: 0) + effect.value > 31) // if HP IV is already less than 3 away from 31
pokemonEntity.pokemon.ivs.set(Stats.getStat(iv.path), 31)
else
pokemonEntity.pokemon.ivs.set(Stats.getStat(iv.path), (pokemonEntity.pokemon.ivs[Stats.getStat(iv.path)] ?: 0) + (effect.value).toInt())
}
fun alterGenderAttempt(pokemonEntity: PokemonEntity, effect: FishingBait.Effect) {
if (pokemonEntity.pokemon.species.maleRatio > 0 && pokemonEntity.pokemon.species.maleRatio < 1) // if the pokemon is allowed to be male or female
when (effect.subcategory) {
cobblemonResource("male") -> if (pokemonEntity.pokemon.gender != Gender.MALE) pokemonEntity.pokemon.gender = Gender.MALE
cobblemonResource("female") -> if (pokemonEntity.pokemon.gender != Gender.FEMALE) pokemonEntity.pokemon.gender = Gender.FEMALE
}
}
fun alterLevelAttempt(pokemonEntity: PokemonEntity, effect: FishingBait.Effect) {
var level = pokemonEntity.pokemon.level + effect.value.toInt()
if (level > Cobblemon.config.maxPokemonLevel)
level = Cobblemon.config.maxPokemonLevel
pokemonEntity.pokemon.level = level
}
fun alterHAAttempt(pokemonEntity: PokemonEntity) {
//val species = pokemonEntity.pokemon.species.let { PokemonSpecies.getByName(it.name) } ?: return
//val ability = species.abilities.mapping[Priority.LOW]?.first()?.template?.name ?: return
//pokemonEntity.pokemon.ability = Abilities.get(ability)?.create(false) ?: return
// Old code from Licious that might be helpful if the above proves to not work
// This will iterate from highest to lowest priority
pokemonEntity.pokemon.form.abilities.mapping.values.forEach { abilities ->
abilities.filterIsInstance<HiddenAbility>()
.randomOrNull ()
?.let { ability ->
// No need to force, this is legal
pokemonEntity.pokemon.ability = ability.template.create(false, ability.priority)
return
}
}
// There was never a hidden ability :( possible but not by default
return
}
fun alterFriendshipAttempt(pokemonEntity: PokemonEntity, effect: FishingBait.Effect) {
if (pokemonEntity.pokemon.friendship + effect.value > 255)
pokemonEntity.pokemon.setFriendship(255)
else
pokemonEntity.pokemon.setFriendship(pokemonEntity.pokemon.friendship + effect.value.toInt())
}
}
val bait: FishingBait? = baitEffect
override fun affectSpawn(entity: Entity) {
super.affectSpawn(entity)
if (entity is PokemonEntity) {
entity.pokemon.forcedAspects += BAIT_ASPECT
bait?.effects?.forEach { it ->
if (Math.random() > it.chance) return
FishingBait.Effects.getEffectFunction(it.type)?.invoke(entity, it)
}
// Some of the bait actions might have changed the aspects and we need it to be
// in the entityData IMMEDIATELY otherwise it will flash as what it would be
// with the old aspects.
// New aspects copy into the entity data only on the next tick.
entity.entityData.set(PokemonEntity.ASPECTS, entity.pokemon.aspects)
CobblemonEvents.BAIT_SPAWN_POKEMON_MODIFY.post(BaitSpawnPokemonEvent.Modify(bucket, baitStack, entity))
}
}
// EV related bait effects
override fun affectWeight(detail: SpawnDetail, ctx: SpawningContext, weight: Float): Float {
// if bait exists and any effects are related to EV yields
if (bait != null && bait.effects.any{ it.type == FishingBait.Effects.EV }){
if (detail is PokemonSpawnDetail) {
val detailSpecies = detail.pokemon.species?.let { PokemonSpecies.getByName(it) }
val baitEVStat = bait.effects.firstOrNull { it.type == FishingBait.Effects.EV }?.subcategory?.path?.let { Stats.getStat(it) }
if (detailSpecies != null && baitEVStat != null) {
val evYieldValue = detailSpecies.evYield[baitEVStat]?.toFloat() ?: 0f
return when {
evYieldValue > 0 -> super.affectWeight(detail, ctx, weight) // use original weight if EV yield is greater than 0
else -> super.affectWeight(detail, ctx, 0f) // use spawn weight of 0 if EV yield is 0
}
}
}
}
// if bait exists and any effects are related to Typing
if (bait != null && bait.effects.any{ it.type == FishingBait.Effects.TYPING }){
if (detail is PokemonSpawnDetail) {
val detailSpecies = detail.pokemon.species?.let { PokemonSpecies.getByName(it) }
val baitEffect = bait.effects.firstOrNull { it.type == FishingBait.Effects.TYPING }
val baitTypingEffect = baitEffect?.subcategory?.path?.let { ElementalTypes.get(it) }
if (detailSpecies != null && baitTypingEffect != null) {
val isMatchingType = detailSpecies.types.contains(baitTypingEffect)
return when {
isMatchingType -> super.affectWeight(detail, ctx, weight * baitEffect.value.toFloat()) // multiply weight by multiplier of bait effect if typing is found
else -> super.affectWeight(detail, ctx, weight) // use base spawn weight if typing is not same as bait
}
}
}
}
// if bait exists and any effects are related to Egg Groups
if (bait != null && bait.effects.any { it.type == FishingBait.Effects.EGG_GROUP }) {
if (detail is PokemonSpawnDetail) {
val detailSpecies = detail.pokemon.species?.let { PokemonSpecies.getByName(it) }
if (detailSpecies != null) {
// Collect all the egg group effects
val eggGroupEffects = bait.effects.filter { it.type == FishingBait.Effects.EGG_GROUP }
// Check if any of the egg group effects match the species' egg groups
val matchingEffect = eggGroupEffects.firstOrNull { effect ->
val effectEggGroupKey = effect.subcategory?.path ?: return@firstOrNull false
val eggGroup = EggGroup.fromIdentifier(effectEggGroupKey)
if (eggGroup == null) {
LOGGER.warn("Unknown egg group identifier: $effectEggGroupKey")
return@firstOrNull false
}
detailSpecies.eggGroups.contains(eggGroup)
}
if (matchingEffect != null) {
val multiplier = matchingEffect.value
return super.affectWeight(detail, ctx, (weight * multiplier).toFloat())
}
}
}
}
return super.affectWeight(detail, ctx, weight)
}
}
@@ -0,0 +1,39 @@
/*
* 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.bait
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.context.BaitSpawningContext
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.level.ServerLevel
import net.minecraft.core.BlockPos
/**
* A spawner that takes in a [com.cobblemon.mod.common.api.spawning.bait.FishingSpawnCause] and spawns things.
*
* @author Hiroku
* @since February 3rd, 2024
*/
open class BaitSpawner(
override val name: String = "bait",
pool: SpawnPool = CobblemonSpawnPools.WORLD_SPAWN_POOL
) : TriggerSpawner<BaitSpawnCause>(name, pool) {
override fun parseContext(cause: BaitSpawnCause, world: ServerLevel, pos: BlockPos): BaitSpawningContext? {
// Maybe confirm that it's water we're fishing in?
val ctx = BaitSpawningContext(
cause = cause,
world = world,
pos = pos,
influences = influences
)
return ctx
}
}
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.context.BaitSpawningContext
import com.cobblemon.mod.common.api.spawning.context.GroundedSpawningContext
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.Item
import net.minecraft.world.item.enchantment.EnchantmentHelper
import net.minecraft.world.item.enchantment.Enchantments
import net.minecraft.world.level.block.Block
/**
* A spawning condition that applies to [BaitSpawningContext]s.
*
* @author Hiroku
* @since February 3rd, 2024
*/
class BaitSpawningCondition: SpawningCondition<BaitSpawningContext>() {
override fun contextClass() = BaitSpawningContext::class.java
var neededNearbyBlocks: MutableList<RegistryLikeCondition<Block>>? = null
var baitType: ResourceLocation? = null
override fun fits(ctx: BaitSpawningContext): Boolean {
if (!super.fits(ctx)) {
return false
} else if (neededNearbyBlocks != null && neededNearbyBlocks!!.none { cond -> ctx.nearbyBlockTypes.any { cond.fits(it, ctx.blockRegistry) } }) {
return false
}
if (baitType != null) { // check for the type of bait being used
val baitItem = ctx.baitItem
if (baitItem != baitType) {
return false
}
}
/*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
}
companion object {
const val NAME = "bait"
}
}
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.context
import com.cobblemon.mod.common.api.spawning.bait.BaitSpawnCause
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.util.toVec3d
import net.minecraft.world.level.block.Block
import net.minecraft.server.level.ServerLevel
import net.minecraft.core.BlockPos
import net.minecraft.world.phys.AABB
/**
* A spawning context that was generated for a Bait action.
*
* @author Hiroku
* @since February 3rd, 2024
*/
class BaitSpawningContext(
cause: BaitSpawnCause,
world: ServerLevel,
pos: BlockPos,
influences: MutableList<SpawningInfluence>
) : TriggerSpawningContext(
cause = cause,
world = world,
position = pos,
light = world.getMaxLocalRawBrightness(pos),
skyLight = world.getMaxLocalRawBrightness(pos.above()),
canSeeSky = world.canSeeSkyFromBelowWater(pos),
influences = influences
) {
val nearbyBlocks = world.getBlockStates(AABB.ofSize(pos.toVec3d(), 10.0, 10.0, 10.0))
val nearbyBlockTypes: List<Block> by lazy { nearbyBlocks.map { it.block }.distinct().toList() }
val baitItem = cause.baitStack
}
@@ -10,6 +10,9 @@ package com.cobblemon.mod.common.api.spawning.fishing
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.fishing.BobberSpawnPokemonEvent
import com.cobblemon.mod.common.api.fishing.FishingBait
@@ -26,9 +29,11 @@ import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.interactive.PokerodItem
import com.cobblemon.mod.common.item.interactive.PokerodItem.Companion.getCookingComponentOnRod
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.world.entity.Entity
import net.minecraft.world.item.ItemStack
import kotlin.random.Random.Default.nextInt
@@ -133,6 +138,7 @@ class FishingSpawnCause(
}
val rodItem = rodStack.item as? PokerodItem
// Determine the combined bait effects if any
val bait = PokerodItem.getBaitOnRod(rodStack)
override fun affectSpawn(entity: Entity) {
@@ -22,6 +22,7 @@ import net.minecraft.tags.TagKey
object CobblemonBlockTags {
@JvmField val ALL_HANGING_SIGNS = createTag("all_hanging_signs")
@JvmField val ALL_SAPLINGS = createTag("all_saplings")
@JvmField val ALL_SIGNS = createTag("all_signs")
@JvmField val ANCIENT_CITY_BLOCKS = createTag("ancient_city_blocks")
@JvmField val APRICORN_LEAVES = createTag("apricorn_leaves")
@@ -56,6 +57,7 @@ object CobblemonBlockTags {
@JvmField val MACHINES = createTag("machines")
@JvmField val MANSION_BLOCKS = createTag("mansion_blocks")
@JvmField val MEDICINAL_LEEK_PLANTABLE = createTag("medicinal_leek_plantable")
@JvmField val HEARTY_GRAINS_PLANTABLE = createTag("hearty_grains_plantable")
@JvmField val MINTS = createTag("mints")
@JvmField val MOON_STONE_ORES = createTag("moon_stone_ores")
@JvmField val NATURAL = createTag("natural")
@@ -67,6 +69,9 @@ object CobblemonBlockTags {
@JvmField val REDSTONE_BLOCKS = createTag("redstone_blocks")
@JvmField val ROOTS = createTag("roots")
@JvmField val ROOTS_SPREADABLE = createTag("roots_spreadable")
@JvmField val SACCHARINE_LEAVES = createTag("saccharine_leaves")
@JvmField val SACCHARINE_LOGS = createTag("saccharine_logs")
@JvmField val SACCHARINE_SAPLING = createTag("saccharine_sapling")
@JvmField val RUINED_PORTAL_BLOCKS = createTag("ruined_portal_blocks")
@JvmField val SEES_SKY = createTag("sees_sky")
@JvmField val SHINY_STONE_ORES = createTag("shiny_stone_ores")
@@ -0,0 +1,132 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundSource
import net.minecraft.tags.BlockTags
import net.minecraft.util.RandomSource
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.ItemLike
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.BonemealableBlock
import net.minecraft.world.level.block.CropBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape
import kotlin.random.Random
@Suppress("OVERRIDE_DEPRECATION")
class BugwortBlock(settings: Properties) : CropBlock(settings), BonemealableBlock {
init {
registerDefaultState(stateDefinition.any()
.setValue(AGE, 0)
.setValue(IS_WILD, false))
}
override fun getAgeProperty(): IntegerProperty = AGE
override fun getMaxAge(): Int = MATURE_AGE
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(this.ageProperty)
builder.add(IS_WILD)
}
override fun getBaseSeedId(): ItemLike = CobblemonItems.BUGWORT
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape = AGE_TO_SHAPE[this.getAge(state)]
override fun randomTick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
// This is specified as growing fast like sugar cane
// They have 15 age stages until they grow upwards, this is an attempt at a chance based but likely event
if (this.isMaxAge(state) || random.nextInt(4) != 0) {
return
}
this.growCrops(world, pos, state)
}
// These 3 are still around for the sake of compatibility, vanilla won't trigger it but some mods might
// We implement applyGrowth & getGrowthAmount for them
override fun isValidBonemealTarget(world: LevelReader, pos: BlockPos, state: BlockState) = state.getValue(BugwortBlock.AGE) < BugwortBlock.MATURE_AGE
override fun isBonemealSuccess(world: Level, random: RandomSource, pos: BlockPos, state: BlockState) = true
override fun performBonemeal(world: ServerLevel, random: RandomSource, pos: BlockPos, state: BlockState) {
world.setBlock(pos, state.setValue(AGE, state.getValue(AGE) + 1), 2)
}
override fun canSurvive(state: BlockState, world: LevelReader, pos: BlockPos): Boolean {
val floor = world.getBlockState(pos.below())
return (world.getRawBrightness(pos, 0) >= 8 || world.canSeeSky(pos)) && ((this.isWild(state) && (floor.`is`(BlockTags.DIRT) || floor.`is`(Blocks.FARMLAND)) || this.mayPlaceOn(floor, world, pos)))
}
fun isWild(state: BlockState): Boolean = state.getValue(IS_WILD)
override fun mayPlaceOn(state: BlockState, world: BlockGetter, pos: BlockPos): Boolean {
val blockPos = pos!!.below()
val down = pos.below()
val floor = world.getBlockState(down)
return floor.`is`(BlockTags.DIRT) || floor.`is`(Blocks.FARMLAND)
}
override fun useWithoutItem(
state: BlockState,
world: Level,
pos: BlockPos,
player: Player,
hit: BlockHitResult
): InteractionResult {
if (state != null && world != null && pos != null) {
// if bugwort is at max age then revert age to 0 and drop items
if (player.getItemInHand(InteractionHand.MAIN_HAND).`is`(Items.BONE_MEAL) && !this.isMaxAge(state)) {
return InteractionResult.PASS
} else if (this.isMaxAge(state)) {
world.playSound(null, pos, CobblemonSounds.BERRY_HARVEST, SoundSource.BLOCKS, 0.4F, 1F)
world.setBlock(pos, state.setValue(this.ageProperty, (0).coerceAtMost(this.maxAge)), UPDATE_CLIENTS)
Block.popResource(world, pos, ItemStack(CobblemonItems.BUGWORT, Random.nextInt(2, 4)))
return InteractionResult.sidedSuccess(world.isClientSide)
}
}
return super.useWithoutItem(state, world, pos, player, hit)
}
override fun getBonemealAgeIncrease(world: Level): Int = 1
companion object {
const val MATURE_AGE = 3
val AGE: IntegerProperty = BlockStateProperties.AGE_3
val IS_WILD: BooleanProperty = BooleanProperty.create("is_wild")
val AGE_TO_SHAPE = arrayOf(
box(0.0, 0.0, 0.0, 16.0, 2.0, 16.0),
box(0.0, 0.0, 0.0, 16.0, 5.0, 16.0),
box(0.0, 0.0, 0.0, 16.0, 8.0, 16.0),
box(0.0, 0.0, 0.0, 16.0, 11.0, 16.0)
)
}
}
@@ -0,0 +1,103 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.block.entity.CakeBlockEntity
import com.cobblemon.mod.common.item.components.CookingComponent
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape
abstract class CakeBlock(settings: Properties): BaseEntityBlock(settings) {
companion object {
val SHAPE_BY_BITE = arrayOf(
box(1.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(3.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(5.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(7.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(9.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(11.0, 0.0, 1.0, 15.0, 9.0, 15.0),
box(13.0, 0.0, 1.0, 15.0, 9.0, 15.0)
)
}
override fun setPlacedBy(
level: Level,
pos: BlockPos,
state: BlockState,
placer: LivingEntity?,
stack: ItemStack
) {
super.setPlacedBy(level, pos, state, placer, stack)
val blockEntity = level.getBlockEntity(pos) as? CakeBlockEntity
blockEntity?.initializeFromItemStack(stack)
}
override fun getCloneItemStack(level: LevelReader, pos: BlockPos, state: BlockState): ItemStack {
val blockEntity = level.getBlockEntity(pos) as? CakeBlockEntity ?: return ItemStack.EMPTY
return blockEntity.toItemStack()
}
override fun getShape(
state: BlockState,
level: BlockGetter,
pos: BlockPos,
context: CollisionContext
): VoxelShape? {
return SHAPE_BY_BITE[getBites(level, pos)]
}
override fun spawnDestroyParticles(
level: Level,
player: Player,
pos: BlockPos,
state: BlockState
) {
val blockEntity = level.getBlockEntity(pos) as? CakeBlockEntity
val color = blockEntity?.cookingComponent?.seasoning3?.color
val woolBlock = when (color?.lowercase()) {
"red" -> Blocks.RED_WOOL
"orange" -> Blocks.ORANGE_WOOL
"yellow" -> Blocks.YELLOW_WOOL
"lime" -> Blocks.LIME_WOOL
"green" -> Blocks.GREEN_WOOL
"cyan" -> Blocks.CYAN_WOOL
"light blue" -> Blocks.LIGHT_BLUE_WOOL
"blue" -> Blocks.BLUE_WOOL
"purple" -> Blocks.PURPLE_WOOL
"magenta" -> Blocks.MAGENTA_WOOL
"pink" -> Blocks.PINK_WOOL
"white" -> Blocks.WHITE_WOOL
else -> Blocks.WHITE_WOOL
}
level.levelEvent(player, 2001, pos, getId(woolBlock.defaultBlockState()))
}
fun getCookingComponent(level: BlockGetter, pos: BlockPos): CookingComponent? {
return (level.getBlockEntity(pos) as? CakeBlockEntity)?.cookingComponent
}
fun setCookingComponent(level: BlockGetter, pos: BlockPos, cookingComponent: CookingComponent) {
(level.getBlockEntity(pos) as? CakeBlockEntity)?.cookingComponent = cookingComponent
}
fun getBites(level: BlockGetter, pos: BlockPos): Int = (level.getBlockEntity(pos) as? CakeBlockEntity)?.bites ?: 0
fun setBites(level: BlockGetter, pos: BlockPos, bites: Int) = (level.getBlockEntity(pos) as? CakeBlockEntity)?.bites = bites
}
@@ -0,0 +1,301 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.PREVIEW_ITEM_SLOT
import com.cobblemon.mod.common.block.entity.DisplayCaseBlockEntity
import com.cobblemon.mod.common.item.CampfirePotItem
import com.cobblemon.mod.common.item.PokeBallItem
import com.cobblemon.mod.common.util.playSoundServer
import com.cobblemon.mod.common.util.toVec3d
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.util.RandomSource
import net.minecraft.world.Containers
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.HorizontalDirectionalBlock.FACING
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.block.state.properties.DirectionProperty
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.level.pathfinder.PathComputationType
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
import org.jetbrains.annotations.Nullable
import net.minecraft.world.level.block.CampfireBlock as MCCampfireBlock
@Suppress("OVERRIDE_DEPRECATION")
class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWaterloggedBlock {
companion object {
val CODEC = simpleCodec(::CampfireBlock)
val ITEM_DIRECTION = DirectionProperty.create("item_facing")
val LIT = BlockStateProperties.LIT
var SOUL = BooleanProperty.create("soul")
private val campfireAABB = Shapes.box(0.0, 0.0, 0.0, 1.0, 0.4375, 1.0)
private val AABB = Shapes.or(
Shapes.box(0.0, 0.0, 0.0, 1.0, 0.4375, 1.0),
Shapes.box(0.1875, 0.5, 0.125, 0.875, 0.8125, 0.1875),
Shapes.box(0.125, 0.4375, 0.125, 0.875, 0.5, 0.875),
Shapes.box(0.8125, 0.5, 0.1875, 0.875, 0.8125, 0.875),
Shapes.box(0.125, 0.5, 0.125, 0.1875, 0.8125, 0.8125),
Shapes.box(0.125, 0.5, 0.8125, 0.8125, 0.8125, 0.875)
)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(LIT, true)
.setValue(SOUL, false)
.setValue(ITEM_DIRECTION, Direction.NORTH))
}
override fun getStateForPlacement(ctx: BlockPlaceContext): BlockState? {
var blockState = defaultBlockState()
val worldView = ctx.level
val blockPos = ctx.clickedPos
ctx.nearestLookingDirections.forEach { direction ->
if (direction.axis.isHorizontal) {
blockState = blockState
.setValue(FACING, direction)
.setValue(ITEM_DIRECTION, direction)
as BlockState
if (blockState.canSurvive(worldView, blockPos)) {
return blockState
}
}
}
return null
}
override fun getShape(blockState: BlockState, blockGetter: BlockGetter, blockPos: BlockPos, collisionContext: CollisionContext): VoxelShape = campfireAABB
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape = AABB
override fun useWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hitResult: BlockHitResult
): InteractionResult {
val blockEntity = level.getBlockEntity(pos)
if (blockEntity is CampfireBlockEntity) {
val potItem = blockEntity.getPotItem()
if (!potItem?.isEmpty!! && potItem.item is CampfirePotItem) {
if (!level.isClientSide) {
if (player.isCrouching) {
// Remove the pot item and give it to the player
removePotItem(blockEntity, state, level, pos, player)
} else {
// Open the cooking pot's container
openContainer(level, pos, player)
}
}
return InteractionResult.SUCCESS
} else if (player.getItemInHand(InteractionHand.MAIN_HAND).item is CampfirePotItem) {
// Add the pot item to the block entity
if (potItem.isEmpty) {
val heldItem = player.getItemInHand(InteractionHand.MAIN_HAND)
blockEntity.setPotItem(heldItem.split(1))
level.playSoundServer(
position = pos.bottomCenter,
sound = CobblemonSounds.CAMPFIRE_POT_PLACE,
)
return InteractionResult.SUCCESS
}
}
}
return InteractionResult.PASS
}
override fun placeLiquid(level: LevelAccessor, pos: BlockPos, state: BlockState, fluidState: FluidState): Boolean {
val blockEntity = level.getBlockEntity(pos)
if (fluidState.type === Fluids.WATER && blockEntity is CampfireBlockEntity) {
if (!level.isClientSide) {
removePotItem(blockEntity, state, level as Level, pos, null, true)
level.playSoundServer(pos.center, SoundEvents.GENERIC_EXTINGUISH_FIRE, SoundSource.BLOCKS, 1.0F, 1.0F)
}
if (level.isClientSide) for (i in 0..19) MCCampfireBlock.makeParticles(level as Level, pos, false, true)
level.scheduleTick(pos, fluidState.type, fluidState.type.getTickDelay(level))
return true
} else {
return false
}
}
private fun removePotItem(blockEntity: CampfireBlockEntity, blockState: BlockState, level: Level, blockPos: BlockPos, player: Player? = null, byWater: Boolean = false) {
if (!byWater && player != null && !player.getItemInHand(InteractionHand.MAIN_HAND).isEmpty) return
val potItem = blockEntity.getPotItem()
if (!byWater && player != null) {
player.setItemInHand(InteractionHand.MAIN_HAND, potItem)
} else {
val direction = blockState.getValue(FACING) as Direction
val f = 0.25F * direction.stepX.toFloat()
val g = 0.25F * direction.stepZ.toFloat()
val itemEntity = ItemEntity(level, blockPos.x.toDouble() + 0.5 + f.toDouble(), (blockPos.y + 1).toDouble(), blockPos.z.toDouble() + 0.5 + g.toDouble(), potItem)
itemEntity.setDefaultPickUpDelay()
level.addFreshEntity(itemEntity)
}
blockEntity.setPotItem(ItemStack.EMPTY)
level.playSoundServer(blockPos.bottomCenter, CobblemonSounds.CAMPFIRE_POT_RETRIEVE)
blockEntity.setItem(PREVIEW_ITEM_SLOT, ItemStack.EMPTY)
Containers.dropContents(level, blockPos, blockEntity)
val facing = blockState.getValue(FACING)
val isSoul = blockState.getValue(SOUL)
blockEntity.setRemoved()
val newBlockState = if (isSoul) Blocks.SOUL_CAMPFIRE.defaultBlockState().setValue(FACING, facing)
else Blocks.CAMPFIRE.defaultBlockState().setValue(FACING, facing)
level.setBlockAndUpdate(blockPos, if (byWater) newBlockState.setValue(BlockStateProperties.WATERLOGGED, true).setValue(MCCampfireBlock.LIT, false) else newBlockState)
}
fun openContainer(level: Level, pos: BlockPos, player: Player) {
val blockEntity = level.getBlockEntity(pos)
if (blockEntity is CampfireBlockEntity) player.openMenu(blockEntity)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING, ITEM_DIRECTION, LIT, SOUL)
}
override fun updateShape(
state: BlockState,
direction: Direction,
neighborState: BlockState,
world: LevelAccessor,
pos: BlockPos,
neighborPos: BlockPos
): BlockState {
return if (direction == state.getValue(FACING) && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun getRenderShape(state: BlockState) = RenderShape.MODEL
override fun getAnalogOutputSignal(state: BlockState, world: Level, pos: BlockPos): Int {
val stack = (world.getBlockEntity(pos) as DisplayCaseBlockEntity).getStack()
if (stack.isEmpty) return 0
if (stack.item is PokeBallItem) return 3
if (stack.item is BlockItem) return 2
return 1
}
override fun hasAnalogOutputSignal(state: BlockState): Boolean = true
override fun codec(): MapCodec<out BaseEntityBlock> {
return CODEC
}
override fun isPathfindable(state: BlockState, type: PathComputationType): Boolean = false
override fun <T : BlockEntity?> getTicker(
level: Level,
state: BlockState,
blockEntityType: BlockEntityType<T?>
): BlockEntityTicker<T?>? {
return createCookingPotTicker(level, blockEntityType as BlockEntityType<*>, CobblemonBlockEntities.CAMPFIRE) as BlockEntityTicker<T?>?
}
@Nullable
protected fun <T : BlockEntity> createCookingPotTicker(
level: Level,
serverType: BlockEntityType<T>,
clientType: BlockEntityType<out CampfireBlockEntity>
): BlockEntityTicker<T>? {
return if (level.isClientSide) createTickerHelper(serverType, clientType, CampfireBlockEntity::clientTick)
else createTickerHelper(serverType, clientType, CampfireBlockEntity::serverTick)
}
override fun newBlockEntity(
pos: BlockPos,
state: BlockState
): BlockEntity? {
return CampfireBlockEntity(pos, state)
}
override fun animateTick(state: BlockState, level: Level, pos: BlockPos, random: RandomSource) {
if (random.nextInt(10) == 0) {
level.playLocalSound(
pos.x.toDouble() + 0.5,
pos.y.toDouble() + 0.5,
pos.z.toDouble() + 0.5,
SoundEvents.CAMPFIRE_CRACKLE,
SoundSource.BLOCKS,
0.5f + random.nextFloat(),
random.nextFloat() * 0.7f + 0.6f,
false
)
}
}
override fun playerWillDestroy(level: Level, blockPos: BlockPos, blockState: BlockState, player: Player): BlockState {
if (!level.isClientSide) {
val blockEntity = level.getBlockEntity(blockPos)
if (blockEntity is CampfireBlockEntity && !player.isCreative) {
Containers.dropContents(level, blockPos, blockEntity)
val potItem = blockEntity.getPotItem() ?: ItemStack.EMPTY
if (!potItem.isEmpty) {
val direction = blockState.getValue(FACING) as Direction
val f = 0.25F * direction.stepX.toFloat()
val g = 0.25F * direction.stepZ.toFloat()
val itemEntity = ItemEntity(level, blockPos.x.toDouble() + 0.5 + f.toDouble(), (blockPos.y + 1).toDouble(), blockPos.z.toDouble() + 0.5 + g.toDouble(), potItem)
itemEntity.setDefaultPickUpDelay()
level.addFreshEntity(itemEntity)
}
}
}
level.playSoundServer(position = blockPos.toVec3d(), sound = CobblemonSounds.CAMPFIRE_POT_OPEN, volume = 0.25F)
return super.playerWillDestroy(level, blockPos, blockState, player)
}
override fun getCloneItemStack(level: LevelReader, pos: BlockPos, state: BlockState): ItemStack {
return if (state.getValue(SOUL)) ItemStack(Blocks.SOUL_CAMPFIRE) else ItemStack(Blocks.CAMPFIRE)
}
}
@@ -0,0 +1,125 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties.WATERLOGGED
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class CampfirePotBlock(settings: Properties) : HorizontalDirectionalBlock(settings), SimpleWaterloggedBlock {
companion object {
val CODEC: MapCodec<CampfirePotBlock> = RecordCodecBuilder.mapCodec { it.group(propertiesCodec()).apply(it, ::CampfirePotBlock) }
var OPEN = BooleanProperty.create("open")
private val AABB_NS = Shapes.or(
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.375, 0.875),
Shapes.box(0.0, 0.3125, 0.375, 0.125, 0.375, 0.625),
Shapes.box(0.875, 0.3125, 0.375, 1.0, 0.375, 0.625),
Shapes.box(0.4375, 0.375, 0.4375, 0.5625, 0.4375, 0.5625)
)
private val AABB_WE = Shapes.or(
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.375, 0.875),
Shapes.box(0.375, 0.3125, 0.0, 0.625, 0.375, 0.125),
Shapes.box(0.375, 0.3125, 0.875, 0.625, 0.375, 1.0),
Shapes.box(0.4375, 0.375, 0.4375, 0.5625, 0.4375, 0.5625),
)
private val AABB_NS_OPEN = Shapes.or(
Shapes.box(0.1875, 0.0625, 0.125, 0.875, 0.375, 0.1875),
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.0625, 0.875),
Shapes.box(0.8125, 0.0625, 0.1875, 0.875, 0.375, 0.875),
Shapes.box(0.125, 0.0625, 0.125, 0.1875, 0.375, 0.8125),
Shapes.box(0.125, 0.0625, 0.8125, 0.8125, 0.375, 0.875),
Shapes.box(0.0, 0.3125, 0.375, 0.125, 0.375, 0.625),
Shapes.box(0.875, 0.3125, 0.375, 1.0, 0.375, 0.625)
)
private val AABB_WE_OPEN = Shapes.or(
Shapes.box(0.1875, 0.0625, 0.125, 0.875, 0.375, 0.1875),
Shapes.box(0.125, 0.0, 0.125, 0.875, 0.0625, 0.875),
Shapes.box(0.8125, 0.0625, 0.1875, 0.875, 0.375, 0.875),
Shapes.box(0.125, 0.0625, 0.125, 0.1875, 0.375, 0.8125),
Shapes.box(0.125, 0.0625, 0.8125, 0.8125, 0.375, 0.875),
Shapes.box(0.375, 0.3125, 0.0, 0.625, 0.375, 0.125),
Shapes.box(0.375, 0.3125, 0.875, 0.625, 0.375, 1.0)
)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(OPEN, false)
.setValue(WATERLOGGED, false)
)
}
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
val direction = state.getValue(FACING)
val open = state.getValue(OPEN)
return if (open)
if (direction == Direction.NORTH || direction == Direction.SOUTH) AABB_NS_OPEN else AABB_WE_OPEN
else
if (direction == Direction.NORTH || direction == Direction.SOUTH) AABB_NS else AABB_WE
}
override fun getStateForPlacement(ctx: BlockPlaceContext): BlockState? {
var blockState = defaultBlockState().setValue(WATERLOGGED, ctx.level.getFluidState(ctx.clickedPos).type == Fluids.WATER)
val worldView = ctx.level
val blockPos = ctx.clickedPos
ctx.nearestLookingDirections.forEach { direction ->
if (direction.axis.isHorizontal) {
blockState = blockState.setValue(FACING, direction.opposite) as BlockState
if (blockState.canSurvive(worldView, blockPos)) {
return blockState
}
}
}
return null
}
override fun codec(): MapCodec<out HorizontalDirectionalBlock> = CODEC
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, world: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (direction == state.getValue(FACING) && !state.canSurvive(world, pos)) Blocks.AIR.defaultBlockState()
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(FACING)
builder.add(OPEN)
builder.add(WATERLOGGED)
}
override fun getFluidState(blockState: BlockState): FluidState? {
return if (blockState.getValue(WATERLOGGED)) Fluids.WATER.getSource(false) else super.getFluidState(blockState);
}
}
@@ -0,0 +1,179 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.block.entity.CandlePokeCakeBlockEntity
import com.cobblemon.mod.common.client.render.block.CakeBlockEntityRenderer.Companion.TOP_LAYER_HEIGHT
import com.cobblemon.mod.common.util.toEquipmentSlot
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.util.RandomSource
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.AbstractCandleBlock
import net.minecraft.world.level.block.AbstractCandleBlock.extinguish
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks.*
import net.minecraft.world.level.block.CandleBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.level.material.MapColor
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class CandlePokeCakeBlock(settings: Properties) : CakeBlock(settings) {
companion object {
val LIT = BlockStateProperties.LIT
val CANDLE_COLOR = IntegerProperty.create("candle_color", 0, MapColor.MATERIAL_COLORS.size - 1)
fun getCandleByMapColorId(id: Int): CandleBlock {
return when (id) {
MapColor.SAND.id -> CANDLE
MapColor.WOOL.id -> WHITE_CANDLE
MapColor.COLOR_ORANGE.id -> ORANGE_CANDLE
MapColor.COLOR_MAGENTA.id -> MAGENTA_CANDLE
MapColor.COLOR_LIGHT_BLUE.id -> LIGHT_BLUE_CANDLE
MapColor.COLOR_YELLOW.id -> YELLOW_CANDLE
MapColor.COLOR_LIGHT_GREEN.id -> LIME_CANDLE
MapColor.COLOR_PINK.id -> PINK_CANDLE
MapColor.COLOR_GRAY.id -> GRAY_CANDLE
MapColor.COLOR_LIGHT_GRAY.id -> LIGHT_GRAY_CANDLE
MapColor.COLOR_CYAN.id -> CYAN_CANDLE
MapColor.COLOR_PURPLE.id -> PURPLE_CANDLE
MapColor.COLOR_BLUE.id -> BLUE_CANDLE
MapColor.COLOR_BROWN.id -> BROWN_CANDLE
MapColor.COLOR_GREEN.id -> GREEN_CANDLE
MapColor.COLOR_RED.id -> RED_CANDLE
MapColor.COLOR_BLACK.id -> BLACK_CANDLE
else -> CANDLE
} as CandleBlock
}
val CANDLE_SHAPE: VoxelShape = box(7.0, 8.0, 7.0, 9.0, 15.0, 9.0)
val SHAPE: VoxelShape = Shapes.or(SHAPE_BY_BITE[0], CANDLE_SHAPE)
val PARTICLE_OFFSET = Vec3(0.5, 0.5 + TOP_LAYER_HEIGHT, 0.5)
}
init {
registerDefaultState(stateDefinition.any()
.setValue(LIT, false)
.setValue(CANDLE_COLOR, MapColor.SAND.id)
)
}
override fun codec(): MapCodec<out BaseEntityBlock?>? {
return simpleCodec(::CandlePokeCakeBlock)
}
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity? {
return CandlePokeCakeBlockEntity(pos, state)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block?, BlockState?>) {
super.createBlockStateDefinition(builder)
builder.add(LIT, CANDLE_COLOR)
}
override fun useItemOn(
stack: ItemStack,
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hand: InteractionHand,
hitResult: BlockHitResult
): ItemInteractionResult? {
return if (!stack.`is`(Items.FLINT_AND_STEEL) && !stack.`is`(Items.FIRE_CHARGE)) {
if (candleHit(hitResult) && stack.isEmpty && state.getValue(LIT) == true) {
extinguish(player, state, level, pos)
level.addParticle(ParticleTypes.SMOKE, PARTICLE_OFFSET.x + pos.x, pos.y + PARTICLE_OFFSET.y, pos.z + PARTICLE_OFFSET.z, 0.0, 0.1, 0.0)
ItemInteractionResult.sidedSuccess(level.isClientSide)
} else {
super.useItemOn(stack, state, level, pos, player, hand, hitResult)
}
} else if (state.getValue(LIT) == false) {
level.playSound(player, pos, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0f, level.random.nextFloat() * 0.4f + 0.8f)
level.setBlock(pos, state.setValue(BlockStateProperties.LIT, true), UPDATE_ALL_IMMEDIATE)
level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos)
player.getItemInHand(hand).hurtAndBreak(1, player, hand.toEquipmentSlot())
ItemInteractionResult.sidedSuccess(level.isClientSide)
} else {
ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION
}
}
override fun useWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hitResult: BlockHitResult
): InteractionResult? {
val oldCookingComponent = getCookingComponent(level, pos)
val candle = getCandleByMapColorId(state.getValue(CANDLE_COLOR))
val newBlockState = CobblemonBlocks.POKE_CAKE.defaultBlockState()
level.setBlockAndUpdate(pos, newBlockState)
level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos)
val pokeCakeBlock = newBlockState.block as PokeCakeBlock
oldCookingComponent?.let {
pokeCakeBlock.setCookingComponent(level, pos, it)
}
val interactionResult = pokeCakeBlock.eat(level, pos, player)
if (interactionResult.consumesAction()) {
popResource(level, pos, ItemStack(candle.asItem()))
}
return interactionResult
}
private fun candleHit(hit: BlockHitResult): Boolean {
return hit.location.y - hit.blockPos.y.toDouble() > TOP_LAYER_HEIGHT
}
override fun animateTick(
state: BlockState,
level: Level,
pos: BlockPos,
random: RandomSource
) {
if (state.getValue(LIT)) {
AbstractCandleBlock.addParticlesAndSound(level, Vec3(PARTICLE_OFFSET.x + pos.x, PARTICLE_OFFSET.y + pos.y, PARTICLE_OFFSET.z + pos.z), random)
}
}
override fun getShape(
state: BlockState,
level: BlockGetter,
pos: BlockPos,
context: CollisionContext
): VoxelShape? = SHAPE
}
@@ -0,0 +1,215 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.FluidTags
import net.minecraft.util.RandomSource
import net.minecraft.util.StringRepresentable
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.ItemLike
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.CropBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.EnumProperty
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape
@Suppress("OVERRIDE_DEPRECATION")
class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWaterloggedBlock {
override fun getAgeProperty(): IntegerProperty = AGE
override fun getMaxAge(): Int = MATURE_AGE
private val ageAfterHarvest = 3
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(AGE, PART, WATERLOGGED)
}
override fun getBaseSeedId(): ItemLike = CobblemonItems.Hearty_Grains
override fun getShape(state: BlockState, world: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
return AGE_TO_SHAPE[this.getAge(state)]
}
override fun getFluidState(state: BlockState): FluidState {
return if (state.getValue(WATERLOGGED)) Fluids.WATER.defaultFluidState() else Fluids.EMPTY.defaultFluidState()
}
override fun randomTick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
if (state.getValue(PART) == Part.UPPER || this.isMaxAge(state) || !canGrow(world, pos)) return
val lightLevel = world.getMaxLocalRawBrightness(pos)
if (lightLevel >= 9 && random.nextInt(7) == 0) {
val newAge = state.getValue(AGE) + 1
world.setBlock(pos, state.setValue(AGE, newAge), UPDATE_CLIENTS)
if (newAge == MATURE_AGE) {
val abovePos = pos.above()
if (world.getBlockState(abovePos).isAir) {
world.setBlock(
abovePos,
this.defaultBlockState().setValue(AGE, newAge).setValue(PART, Part.UPPER),
UPDATE_CLIENTS
)
}
}
}
}
private fun canGrow(world: LevelReader, pos: BlockPos): Boolean {
return canSurvive(defaultBlockState().setValue(PART, Part.LOWER), world, pos)
}
override fun playerWillDestroy(world: Level, pos: BlockPos, state: BlockState, player: Player): BlockState {
if (state.getValue(PART) == Part.UPPER && state.block is HeartyGrainsBlock) {
val belowPos = pos.below()
val belowState = world.getBlockState(belowPos)
world.destroyBlock(pos, true) // Break the lower block without drops
world.setBlock(
belowPos,
belowState.setValue(AGE, ageAfterHarvest), // Reset age to simulate regrowth preparation
UPDATE_CLIENTS
)
} else if (state.getValue(PART) == Part.LOWER) {
val abovePos = pos.above()
val aboveState = world.getBlockState(abovePos)
if (aboveState.block is HeartyGrainsBlock && aboveState.getValue(PART) == Part.UPPER) {
world.destroyBlock(abovePos, true) // Break the upper block without drops
}
}
return super.playerWillDestroy(world, pos, state, player)
}
override fun updateShape(
state: BlockState,
direction: Direction,
neighborState: BlockState,
world: LevelAccessor,
pos: BlockPos,
neighborPos: BlockPos
): BlockState {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world))
}
return if (!canSurvive(state, world, pos)) Blocks.AIR.defaultBlockState() else super.updateShape(
state,
direction,
neighborState,
world,
pos,
neighborPos
)
}
override fun isValidBonemealTarget(world: LevelReader, pos: BlockPos, state: BlockState): Boolean {
return !this.isMaxAge(state) && state.getValue(PART) == Part.LOWER
}
override fun growCrops(world: Level, pos: BlockPos, state: BlockState) {
if (!canGrow(world, pos)) return
val newAge = (this.getAge(state) + this.getBonemealAgeIncrease(world)).coerceAtMost(this.maxAge)
world.setBlock(pos, state.setValue(this.ageProperty, newAge), UPDATE_CLIENTS)
if (newAge == MATURE_AGE && state.getValue(PART) == Part.LOWER) {
val abovePos = pos.above()
if (world.getBlockState(abovePos).isAir) {
world.setBlock(
abovePos,
this.defaultBlockState().setValue(AGE, newAge).setValue(PART, Part.UPPER),
UPDATE_CLIENTS
)
}
}
}
override fun getBonemealAgeIncrease(world: Level): Int = 1
override fun canSurvive(state: BlockState, world: LevelReader, pos: BlockPos): Boolean {
return when (state.getValue(PART)) {
Part.LOWER -> {
val floor = world.getBlockState(pos.below())
val fluidState = world.getFluidState(pos)
// Check if bottom part is submerged in water
val isSubmergedInWater = fluidState.`is`(FluidTags.WATER) && fluidState.isSource
// Check for adjacency to water
val isNearWater = BlockPos.betweenClosed(pos.offset(-1, 0, -1), pos.offset(1, 0, 1))
.any { world.getFluidState(it).`is`(FluidTags.WATER) }
// Check waterlogging state
val isWaterlogged = state.getValue(WATERLOGGED)
// Check for valid soil or survival conditions
isSubmergedInWater || isWaterlogged || isNearWater || floor.`is`(CobblemonBlockTags.HEARTY_GRAINS_PLANTABLE) || floor.`is`(Blocks.MUD) || floor.`is`(Blocks.FARMLAND)
}
Part.UPPER -> {
val below = world.getBlockState(pos.below())
below.block is HeartyGrainsBlock && below.getValue(PART) == Part.LOWER
// Upper part survives only if the lower part is valid
}
}
}
override fun codec(): MapCodec<out CropBlock> = CODEC
companion object {
val CODEC = simpleCodec(::HeartyGrainsBlock)
const val MATURE_AGE = 4
val WATERLOGGED = BlockStateProperties.WATERLOGGED
val AGE: IntegerProperty = BlockStateProperties.AGE_4
val PART: EnumProperty<Part> = EnumProperty.create("part", Part::class.java)
val AGE_TO_SHAPE = arrayOf(
box(2.0, 0.0, 0.0, 16.0, 17.0, 16.0), // Stage 1
box(2.0, 0.0, 0.0, 16.0, 17.0, 16.0), // Stage 2
box(2.0, 0.0, 0.0, 16.0, 17.0, 16.0), // Stage 3
box(2.0, 0.0, 0.0, 16.0, 17.0, 16.0), // Stage 4 (Bottom)
box(2.0, 0.0, 0.0, 16.0, 17.0, 16.0) // Stage 5 (Bottom)
)
}
enum class Part : StringRepresentable {
LOWER, UPPER;
override fun getSerializedName(): String = name.lowercase()
}
init {
this.registerDefaultState(
this.stateDefinition.any()
.setValue(AGE, 0)
.setValue(PART, Part.LOWER)
.setValue(WATERLOGGED, false)
)
}
}
@@ -113,11 +113,7 @@ class LecternBlock(properties: Properties): BaseEntityBlock(properties) {
return super.playerWillDestroy(level, blockPos, blockState, player)
}
override fun getDrops(blockState: BlockState, builder: LootParams.Builder): MutableList<ItemStack> {
return mutableListOf(ItemStack(Blocks.LECTERN))
}
override fun useWithoutItem(blockState: BlockState, level: Level, blockPos: BlockPos, player: Player, blockHitResult: BlockHitResult?): InteractionResult {
override fun useWithoutItem(blockState: BlockState, level: Level, blockPos: BlockPos, player: Player, blockHitResult: BlockHitResult): InteractionResult {
val blockEntity = level.getBlockEntity(blockPos)
if (blockEntity is LecternBlockEntity) {
val itemStack = blockEntity.getItemStack()
@@ -0,0 +1,159 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.fishing.BaitConsumedEvent
import com.cobblemon.mod.common.api.events.fishing.BaitSetEvent
import com.cobblemon.mod.common.api.fishing.FishingBait
import com.cobblemon.mod.common.api.fishing.FishingBaits
import com.cobblemon.mod.common.block.entity.CakeBlockEntity.Companion.MAX_NUMBER_OF_BITES
import com.cobblemon.mod.common.block.entity.LureCakeBlockEntity
import com.cobblemon.mod.common.item.RodBaitComponent
import com.cobblemon.mod.common.item.components.CookingComponent
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
class LureCakeBlock(settings: Properties): CakeBlock(settings) {
companion object {
fun getBaitOnLureCake(stack: ItemStack): FishingBait? {
return getCookingComponentOnRod(stack) ?: stack.components.get(CobblemonItemComponents.BAIT)?.bait
}
fun getBaitStackOnRod(stack: ItemStack): ItemStack {
return stack.components.get(CobblemonItemComponents.BAIT)?.stack ?: ItemStack.EMPTY
}
fun getCookingComponentOnRod(rodStack: ItemStack): FishingBait? {
// Check if the stack within the RodBaitComponent has a CookingComponent
val cookingComponent = rodStack.get(CobblemonItemComponents.COOKING_COMPONENT) ?: return null
// Combine effects from the CookingComponent
val combinedEffects = listOf(
cookingComponent.bait1.effects,
cookingComponent.bait2.effects,
cookingComponent.bait3.effects
).flatten()
// Return a new FishingBait with combined effects
return FishingBait(
item = BuiltInRegistries.ITEM.getKey(
rodStack.components.get(CobblemonItemComponents.BAIT)?.stack?.item ?: ItemStack.EMPTY.item
), // Use the rodStack's item as the bait identifier
effects = combinedEffects
)
}
fun setBait(stack: ItemStack, bait: ItemStack) {
CobblemonEvents.BAIT_SET.postThen(BaitSetEvent(stack, bait), { event -> }, {
if (bait.isEmpty) {
stack.set<RodBaitComponent>(CobblemonItemComponents.BAIT, null)
stack.set<CookingComponent>(CobblemonItemComponents.COOKING_COMPONENT, null)
return
}
// Retrieve FishingBait and CookingComponent from the bait ItemStack
val fishingBait = FishingBaits.getFromBaitItemStack(bait) ?: return
val cookingComponent = bait.get(CobblemonItemComponents.COOKING_COMPONENT)
// Apply both RodBaitComponent and CookingComponent to the rod ItemStack
stack.set(CobblemonItemComponents.BAIT, RodBaitComponent(fishingBait, bait))
if (cookingComponent != null) {
stack.set(CobblemonItemComponents.COOKING_COMPONENT, cookingComponent)
} else {
// Clear CookingComponent if the new bait does not have it
stack.set<CookingComponent>(CobblemonItemComponents.COOKING_COMPONENT, null)
}
})
}
fun consumeBait(stack: ItemStack) {
CobblemonEvents.BAIT_CONSUMED.postThen(BaitConsumedEvent(stack), { event -> }, {
val baitStack = getBaitStackOnRod(stack)
val baitCount = baitStack.count
val cookingComponent = stack.get(CobblemonItemComponents.COOKING_COMPONENT)
if (baitCount == 1) {
stack.set<RodBaitComponent>(CobblemonItemComponents.BAIT, null)
stack.set<CookingComponent>(CobblemonItemComponents.COOKING_COMPONENT, null)
return
}
if (baitCount > 1) {
val fishingBait = getBaitOnLureCake(stack) ?: return
stack.set<RodBaitComponent>(
CobblemonItemComponents.BAIT,
RodBaitComponent(fishingBait, ItemStack(baitStack.item, baitCount - 1))
)
if (cookingComponent != null) {
stack.set(CobblemonItemComponents.COOKING_COMPONENT, cookingComponent)
}
}
})
}
fun getBaitEffects(stack: ItemStack): List<FishingBait.Effect> {
return getBaitOnLureCake(stack)?.effects ?: return emptyList()
}
}
override fun codec(): MapCodec<LureCakeBlock> {
return simpleCodec(::LureCakeBlock)
}
override fun randomTick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
println("randomTick triggered for LureCakeBlock at $pos")
// Check if block has a BlockEntity
val blockEntity = world.getBlockEntity(pos) as? LureCakeBlockEntity
if (blockEntity == null) {
println("No LureCakeBlockEntity found at $pos")
return
}
// Attempt to spawn Pokémon in a 5-block radius
val randomXOffset = random.nextInt(-5, 6)
val randomZOffset = random.nextInt(-5, 6)
val spawnPos = pos.offset(randomXOffset, 0, randomZOffset)
println("Attempting to spawn Pokémon near $pos at $spawnPos")
val spawnedPokemon = blockEntity.spawnPokemon(world, spawnPos)
if (spawnedPokemon != null) {
println("Pokémon spawned successfully at $spawnPos")
// Increment the age of the block
val newBites = getBites(world, pos) + 1
if (newBites >= MAX_NUMBER_OF_BITES) {
println("Block at $pos has reached max age. Breaking block.")
world.destroyBlock(pos, true) // Break the block and drop its items
} else {
setBites(world, pos, newBites)
println("Block at $pos aged to $newBites")
}
world.sendBlockUpdated(pos, state, state, UPDATE_CLIENTS)
}
}
override fun isRandomlyTicking(state: BlockState): Boolean {
return true
}
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
return LureCakeBlockEntity(pos, state)
}
}
@@ -0,0 +1,134 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.block.CandlePokeCakeBlock.Companion.CANDLE_COLOR
import com.cobblemon.mod.common.block.entity.PokeCakeBlockEntity
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.stats.Stats
import net.minecraft.tags.ItemTags
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionHand.MAIN_HAND
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.CandleBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.phys.BlockHitResult
class PokeCakeBlock(settings: Properties): CakeBlock(settings) {
override fun codec(): MapCodec<out BaseEntityBlock?>? {
return simpleCodec(::PokeCakeBlock)
}
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity? {
return PokeCakeBlockEntity(pos, state)
}
override fun setPlacedBy(
level: Level,
pos: BlockPos,
state: BlockState,
placer: LivingEntity?,
stack: ItemStack
) {
super.setPlacedBy(level, pos, state, placer, stack)
val blockEntity = level.getBlockEntity(pos) as? PokeCakeBlockEntity
blockEntity?.initializeFromItemStack(stack)
}
override fun useWithoutItem(
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hitResult: BlockHitResult
): InteractionResult? {
if (level.isClientSide) {
if (eat(level, pos, player).consumesAction()) {
return InteractionResult.SUCCESS
}
if (player.getItemInHand(MAIN_HAND).isEmpty) {
return InteractionResult.CONSUME
}
}
return eat(level, pos, player)
}
override fun useItemOn(
stack: ItemStack,
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hand: InteractionHand,
hitResult: BlockHitResult
): ItemInteractionResult? {
val item = stack.item
val block = state.block as? PokeCakeBlock
if (stack.`is`(ItemTags.CANDLES) && block?.getBites(level, pos) == 0) {
val candleBlock = byItem(item)
if (candleBlock is CandleBlock) {
stack.shrink(1)
level.playSound(null, pos, SoundEvents.CAKE_ADD_CANDLE, SoundSource.BLOCKS, 1.0f, 1.0f)
val oldCookingComponent = block.getCookingComponent(level, pos)
val newBlockState = CobblemonBlocks.CANDLE_POKE_CAKE.defaultBlockState()
.setValue(CANDLE_COLOR, (item as BlockItem).block.defaultMapColor().id)
level.setBlockAndUpdate(pos, newBlockState)
level.gameEvent(player, GameEvent.BLOCK_CHANGE, pos)
oldCookingComponent?.let {
(newBlockState.block as CakeBlock).setCookingComponent(level, pos, it)
}
player.awardStat(Stats.ITEM_USED[item])
return ItemInteractionResult.SUCCESS
}
}
return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION
}
fun eat(level: LevelAccessor, pos: BlockPos, player: Player): InteractionResult {
if (!player.canEat(false)) {
return InteractionResult.PASS
}
player.getFoodData().eat(2, 0.1F)
level.gameEvent(player, GameEvent.EAT, pos)
var bites = getBites(level, pos)
if (bites < 6) {
setBites(level, pos, bites + 1)
} else {
level.removeBlock(pos, false)
level.gameEvent(player, GameEvent.BLOCK_DESTROY, pos)
}
return InteractionResult.SUCCESS
}
}
@@ -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.block
import com.cobblemon.mod.common.CobblemonItemComponents
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import io.netty.buffer.ByteBuf
import net.minecraft.network.codec.ByteBufCodecs
import net.minecraft.network.codec.StreamCodec
import net.minecraft.world.item.ItemStack
import java.util.Optional
class PotComponent(val potItem: ItemStack) { // No nullable ItemStack
companion object {
// Codec for saving/loading
val CODEC: Codec<PotComponent> = RecordCodecBuilder.create { instance ->
instance.group(
ItemStack.CODEC.fieldOf("potItem").forGetter { it.potItem }
).apply(instance) { potItem -> PotComponent(potItem) }
}
// StreamCodec for network synchronization
val PACKET_CODEC: StreamCodec<ByteBuf, PotComponent> = ByteBufCodecs.fromCodec(CODEC)
// Fetch component from stack
fun getFrom(stack: ItemStack): PotComponent? {
return stack.components.get(CobblemonItemComponents.POT_DATA)
}
}
override fun equals(other: Any?): Boolean {
return other is PotComponent && ItemStack.matches(this.potItem, other.potItem)
}
override fun hashCode(): Int {
return ItemStack.hashItemAndComponents(potItem)
}
}
@@ -0,0 +1,262 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.block.SaccharineStrippedLogBlock.Companion
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.BlockTags
import net.minecraft.util.*
import net.minecraft.world.*
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.LevelReader
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.BonemealableBlock
import net.minecraft.world.level.block.LeavesBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.level.pathfinder.PathComputationType
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.EntityCollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
import javax.swing.text.html.BlockView
import kotlin.random.Random
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings), BonemealableBlock {
init {
this.registerDefaultState(
this.stateDefinition.any()
.setValue(AGE, MIN_AGE)
.setValue(DISTANCE, DISTANCE_MAX)
.setValue(PERSISTENT, false)
.setValue(WATERLOGGED, false)
)
}
override fun getStateForPlacement(ctx: BlockPlaceContext): BlockState {
val level = ctx.level
val pos = ctx.clickedPos
val fluidState = level.getFluidState(pos)
return this.defaultBlockState()
.setValue(PERSISTENT, true)
.setValue(WATERLOGGED, fluidState.`is`(Fluids.WATER))
}
override fun updateShape(state: BlockState, direction: Direction, neighborState: BlockState, level: LevelAccessor, pos: BlockPos, neighborPos: BlockPos): BlockState {
return if (!state.canSurvive(level, pos)) {
Blocks.AIR.defaultBlockState()
} else {
super.updateShape(state, direction, neighborState, level, pos, neighborPos)
}
}
override fun isRandomlyTicking(state: BlockState) = state.getValue(AGE) != 0
override fun randomTick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
if (random.nextInt(2) == 0) {
for (i in 1..10) {
val belowPos = pos.below(i)
val belowState = world.getBlockState(belowPos)
val currentAge = state.getValue(AGE)
if (belowState.isAir) {
continue
} else if (belowState.block is SaccharineLeafBlock) {
val belowAge = belowState.getValue(AGE)
if (belowAge < MAX_AGE) {
// Decrease age from the top leaf block
val newCurrentAge = (currentAge - 1).coerceAtLeast(MIN_AGE)
world.setBlock(pos, state.setValue(AGE, newCurrentAge), 2)
// Increase age from the bottom leaf block
val newBelowAge = (belowAge + 1).coerceAtMost(MAX_AGE)
world.setBlock(belowPos, belowState.setValue(AGE, newBelowAge), 2)
}
break
} else {
break
}
}
}
super.randomTick(state, world, pos, random)
}
@Deprecated("Deprecated in Java")
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
return SHAPE
}
// todo make block
@Deprecated("Deprecated in Java")
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
if (context is EntityCollisionContext && (context.entity as? ItemEntity)?.item?.`is`(CobblemonItemTags.APRICORNS) == true) {
return Shapes.empty()
}
return super.getCollisionShape(state, level, pos, context)
}
override fun isValidBonemealTarget(level: LevelReader, pos: BlockPos, state: BlockState): Boolean = true
override fun isBonemealSuccess(level: Level, random: RandomSource, pos: BlockPos, state: BlockState) = true
override fun performBonemeal(level: ServerLevel, random: RandomSource, pos: BlockPos, state: BlockState) {
level.setBlock(pos, state.setValue(AGE, state.getValue(AGE) + 1), 2)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
super.createBlockStateDefinition(builder)
builder.add(AGE)
}
@Deprecated("Deprecated in Java")
override fun animateTick(state: BlockState, level: Level, pos: BlockPos, random: RandomSource) {
if (state.getValue(AGE) == 2) {
for (i in 0 until random.nextInt(1) + 1) {
this.spawnHoneyParticles(level, pos, state, .75f)
}
} else if (state.getValue(AGE) == 1) {
for (i in 0 until random.nextInt(1) + 1) {
this.spawnHoneyParticles(level, pos, state, .9f)
}
}
// this code was for them aging as time goes on
/* if (world.random.nextInt(5) == 0) {
val currentAge = state.get(AGE)
if (currentAge < MAX_AGE) {
world.setBlockState(pos, state.setValue(AGE, currentAge + 1), 2)
}
}*/
}
private fun spawnHoneyParticles(level: Level, pos: BlockPos, state: BlockState, rate: Float) {
if (state.fluidState.isEmpty && !(level.random.nextFloat() < rate)) {
val voxelShape = state.getCollisionShape(level, pos)
val d = voxelShape.max(Direction.Axis.Y)
if (d >= 1.0 && !state.`is`(BlockTags.IMPERMEABLE)) {
val e = voxelShape.min(Direction.Axis.Y)
if (e > 0.0) {
this.addHoneyParticle(level, pos, voxelShape, pos.y.toDouble() + e - 0.05)
} else {
val blockPos = pos.below()
val blockState = level.getBlockState(blockPos)
val voxelShape2 = blockState.getCollisionShape(level, blockPos)
val f = voxelShape2.max(Direction.Axis.Y)
if ((f < 1.0 || !blockState.isSolidRender(level, blockPos)) && blockState.fluidState.isEmpty) {
this.addHoneyParticle(level, pos, voxelShape, pos.y.toDouble() - 0.05)
}
}
}
}
}
private fun addHoneyParticle(level: Level, pos: BlockPos, shape: VoxelShape, height: Double) {
this.addHoneyParticle(
level, pos.x.toDouble() + shape.min(Direction.Axis.X), pos.x.toDouble() + shape.max(
Direction.Axis.X
), pos.z.toDouble() + shape.min(Direction.Axis.Z), pos.z.toDouble() + shape.max(
Direction.Axis.Z
), height
)
}
private fun addHoneyParticle(level: Level, minX: Double, maxX: Double, minZ: Double, maxZ: Double, height: Double) {
level.addParticle(
ParticleTypes.DRIPPING_HONEY,
Mth.lerp(level.random.nextDouble(), minX, maxX),
height,
Mth.lerp(level.random.nextDouble(), minZ, maxZ),
0.0,
0.0,
0.0
)
}
override fun isPathfindable(state: BlockState, type: PathComputationType): Boolean = false
override fun useItemOn(stack: ItemStack, state: BlockState, level: Level, pos: BlockPos, player: Player, hand: InteractionHand, hit: BlockHitResult): ItemInteractionResult {
// todo if item in players hand is glass bottle and AGE is more than 0
val itemStack = player.getItemInHand(hand)
if (itemStack.`is`(Items.GLASS_BOTTLE) && state.getValue(AGE) > 0)
{
// decrement stack if not in creative mode
if (!player.isCreative)
itemStack.shrink(1)
// give player honey bottle for now
player.addItem(Items.HONEY_BOTTLE.defaultInstance)
// todo reset AGE
level.setBlock(pos, state.setValue(AGE, 0), 2)
val currentAge = state.getValue(AGE)
} else if (itemStack.`is`(Items.HONEY_BOTTLE) && state.getValue(AGE) != 2) {
// decrement stack if not in creative mode
if (!player.isCreative)
itemStack.shrink(1)
// todo set age to 2
level.setBlock(pos, state.setValue(AGE, 2), 2)
}
if (state.getValue(AGE) != MAX_AGE) {
return super.useItemOn(stack, state, level, pos, player, hand, hit)
}
//doHarvest(world, state, pos, player)
return ItemInteractionResult.SUCCESS
}
/*override fun onBlockBreakStart(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity) {
if (state.get(AGE) != MAX_AGE) {
return super.onBlockBreakStart(state, world, pos, player)
}
//doHarvest(world, state, pos, player)
}*/
companion object {
val AGE: IntegerProperty = BlockStateProperties.AGE_2
val DISTANCE: IntegerProperty = BlockStateProperties.DISTANCE
val PERSISTENT: BooleanProperty = BlockStateProperties.PERSISTENT
const val MAX_AGE = 2
const val MIN_AGE = 0
const val DISTANCE_MAX = 7
private val SHAPE: VoxelShape = Block.box(0.0, 0.0, 0.0, 16.0, 16.0, 16.0)
}
}
@@ -0,0 +1,14 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.block.grower.SaccharineTreeGrower
import net.minecraft.world.level.block.SaplingBlock
class SaccharineSaplingBlock(properties : Properties) : SaplingBlock(SaccharineTreeGrower(), properties)
@@ -0,0 +1,157 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.RotatedPillarBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.world.level.block.state.properties.EnumProperty
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.level.material.PushReaction
import net.minecraft.world.level.pathfinder.PathComputationType
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.EntityCollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class SaccharineStrippedLogBlock(properties: Properties) : RotatedPillarBlock(properties) {
private var owner: LivingEntity? = null
init {
this.registerDefaultState(
this.stateDefinition.any()
.setValue(AGE, MIN_AGE)
.setValue(AXIS, Direction.Axis.Y)
)
}
override fun setPlacedBy(
level: Level,
pos: BlockPos,
state: BlockState,
placer: LivingEntity?,
itemStack: ItemStack
) {
owner = placer
super.setPlacedBy(level, pos, state, placer, itemStack)
}
override fun isRandomlyTicking(state: BlockState): Boolean {
return state.getValue(AGE) < MAX_AGE
}
override fun randomTick(state: BlockState, level: ServerLevel, pos: BlockPos, random: RandomSource) {
if (random.nextInt(5) == 0 && owner == null) {
val currentAge = state.getValue(AGE)
if (currentAge < MAX_AGE) {
level.setBlock(pos, state.setValue(AGE, currentAge + 1), 2)
}
}
}
override fun getShape(
state: BlockState,
level: BlockGetter,
pos: BlockPos,
context: CollisionContext
): VoxelShape {
return SHAPE
}
override fun getCollisionShape(
state: BlockState,
level: BlockGetter,
pos: BlockPos,
context: CollisionContext
): VoxelShape {
if (context is EntityCollisionContext && (context.entity as? ItemEntity)?.item?.`is`(CobblemonItemTags.APRICORNS) == true) {
return Shapes.empty()
}
return super.getCollisionShape(state, level, pos, context)
}
override fun updateShape(
state: BlockState,
direction: Direction,
neighborState: BlockState,
level: LevelAccessor,
pos: BlockPos,
neighborPos: BlockPos
): BlockState {
return if (!state.canSurvive(level, pos)) {
Blocks.AIR.defaultBlockState()
} else {
super.updateShape(state, direction, neighborState, level, pos, neighborPos)
}
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(AGE, AXIS)
}
override fun isPathfindable(
state: BlockState,
type: PathComputationType
): Boolean = false
override fun useItemOn(
stack: ItemStack,
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hand: InteractionHand,
hit: BlockHitResult
): ItemInteractionResult {
val itemStack = player.getItemInHand(hand)
if (itemStack.`is`(Items.GLASS_BOTTLE) && state.getValue(AGE) == 1) {
if (!player.isCreative) {
itemStack.shrink(1)
}
player.addItem(CobblemonItems.SWEET_SAP.defaultInstance)
level.setBlock(pos, state.setValue(AGE, 0), 2)
}
if (state.getValue(AGE) != MAX_AGE) {
return super.useItemOn(stack, state, level, pos, player, hand, hit)
}
return ItemInteractionResult.SUCCESS
}
companion object {
val AGE: IntegerProperty = BlockStateProperties.AGE_1
val AXIS: EnumProperty<Direction.Axis> = BlockStateProperties.AXIS
const val MAX_AGE = 1
const val MIN_AGE = 0
private val SHAPE: VoxelShape = Shapes.block()
}
}
@@ -0,0 +1,122 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.block.entity.SweetIncenseBlockEntity
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.util.RandomSource
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Items
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.BaseEntityBlock
import net.minecraft.world.level.block.SimpleWaterloggedBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.level.block.state.properties.BlockStateProperties
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.world.ItemInteractionResult
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.Block
import net.minecraft.world.phys.BlockHitResult
class SweetIncenseBlock(properties: Properties) : BaseEntityBlock(properties), SimpleWaterloggedBlock {
companion object {
val LIT: BooleanProperty = BlockStateProperties.LIT
val WATERLOGGED: BooleanProperty = BlockStateProperties.WATERLOGGED
}
init {
this.registerDefaultState(this.stateDefinition.any()
.setValue(LIT, false)
.setValue(WATERLOGGED, false)
)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
builder.add(LIT, WATERLOGGED)
}
override fun getLightBlock(state: BlockState, level: BlockGetter, pos: BlockPos): Int {
return super.getLightBlock(state, level, pos)
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState {
return this.defaultBlockState()
.setValue(LIT, false)
.setValue(WATERLOGGED, context.level.getFluidState(context.clickedPos).type === Fluids.WATER)
}
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
return SweetIncenseBlockEntity(pos, state)
}
val CODEC: MapCodec<SweetIncenseBlock> = RecordCodecBuilder.mapCodec { instance ->
instance.group(
Codec.BOOL.fieldOf("lit").forGetter { it.defaultBlockState().getValue(LIT) },
propertiesCodec()
).apply(instance) { lit, properties ->
SweetIncenseBlock(properties).apply {
registerDefaultState(this.defaultBlockState().setValue(LIT, lit))
}
}
}
override fun codec(): MapCodec<out SweetIncenseBlock> {
return CODEC
}
override fun useItemOn(
stack: ItemStack,
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hand: InteractionHand,
hit: BlockHitResult
): ItemInteractionResult {
val heldItem = player.getItemInHand(hand)
return if (!state.getValue(LIT) && (heldItem.`is`(Items.FLINT_AND_STEEL) || heldItem.`is`(Items.FIRE_CHARGE))) {
level.setBlock(pos, state.setValue(LIT, true), 3)
level.playSound(null, pos, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0f, 1.0f)
ItemInteractionResult.sidedSuccess(level.isClientSide)
} else if (state.getValue(LIT)) {
level.setBlock(pos, state.setValue(LIT, false), 3)
ItemInteractionResult.sidedSuccess(level.isClientSide)
} else {
ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION
}
}
override fun animateTick(state: BlockState, level: Level, pos: BlockPos, random: RandomSource) {
if (state.getValue(LIT)) {
level.addParticle(
ParticleTypes.CAMPFIRE_SIGNAL_SMOKE,
pos.x + 0.5 + random.nextDouble() / 3.0 * if (random.nextBoolean()) 1 else -1,
pos.y + random.nextDouble() + random.nextDouble(),
pos.z + 0.5 + random.nextDouble() / 3.0 * if (random.nextBoolean()) 1 else -1,
0.0, 0.07, 0.0
)
}
}
}
@@ -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.block.entity
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.item.components.CookingComponent
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtOps
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
open class CakeBlockEntity(
type: BlockEntityType<*>,
pos: BlockPos,
state: BlockState
) : BlockEntity(type, pos, state) {
companion object {
const val MAX_NUMBER_OF_BITES = 6
}
var cookingComponent: CookingComponent? = null
var bites: Int = 0
fun initializeFromItemStack(itemStack: ItemStack) {
cookingComponent = itemStack.get(CobblemonItemComponents.COOKING_COMPONENT)
}
override fun saveAdditional(
tag: CompoundTag,
registries: HolderLookup.Provider
) {
super.saveAdditional(tag, registries)
tag.putInt("Bites", bites)
cookingComponent?.let { component ->
CobblemonItemComponents.COOKING_COMPONENT.codec()
?.encodeStart(NbtOps.INSTANCE, component)
?.result()
?.ifPresent { encodedTag ->
tag.put("CookingComponent", encodedTag)
}
}
}
override fun loadAdditional(
tag: CompoundTag,
registries: HolderLookup.Provider
) {
super.loadAdditional(tag, registries)
bites = tag.getInt("Bites")
if (tag.contains("CookingComponent")) {
CobblemonItemComponents.COOKING_COMPONENT.codec()
?.parse(NbtOps.INSTANCE, tag.getCompound("CookingComponent"))
?.result()
?.ifPresent { component ->
cookingComponent = component
}
}
}
override fun getUpdatePacket(): Packet<ClientGamePacketListener?>? {
return ClientboundBlockEntityDataPacket.create(this)
}
override fun getUpdateTag(registries: HolderLookup.Provider): CompoundTag? {
val tag = CompoundTag()
saveAdditional(tag, registries)
return tag
}
fun toItemStack(): ItemStack {
val stack = ItemStack(this.blockState.block)
cookingComponent?.let { component ->
stack.getComponents().apply {
stack.set(CobblemonItemComponents.COOKING_COMPONENT, component)
}
}
return stack
}
}
@@ -0,0 +1,482 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.CobblemonRecipeTypes
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.cooking.*
import com.cobblemon.mod.common.api.fishing.FishingBait
import com.cobblemon.mod.common.api.fishing.FishingBaits
import com.cobblemon.mod.common.block.PotComponent
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotMenu
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotRecipeBase
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
import com.cobblemon.mod.common.client.particle.ParticleStorm
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.client.sound.BlockEntitySoundTracker
import com.cobblemon.mod.common.client.sound.instances.CancellableSoundInstance
import com.cobblemon.mod.common.item.components.CookingComponent
import com.cobblemon.mod.common.util.playSoundServer
import com.mojang.blaze3d.vertex.PoseStack
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import net.minecraft.client.multiplayer.ClientLevel
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.HolderLookup
import net.minecraft.core.NonNullList
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtOps
import net.minecraft.network.chat.Component
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.FastColor
import net.minecraft.world.ContainerHelper
import net.minecraft.world.WorldlyContainer
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.StackedContents
import net.minecraft.world.inventory.*
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.crafting.CraftingInput
import net.minecraft.world.item.crafting.RecipeHolder
import net.minecraft.world.item.crafting.RecipeManager
import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.Vec3
import org.joml.Vector4f
import java.util.*
class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlockEntity(
CobblemonBlockEntities.CAMPFIRE,
pos,
state
), WorldlyContainer, RecipeCraftingHolder, StackedContentsCompatible, CraftingContainer {
companion object {
const val RESULT_SLOT = 0
val CRAFTING_GRID_SLOTS = 1..9
val SEASONING_SLOTS = 10..12
const val PREVIEW_ITEM_SLOT = 13
const val ITEMS_SIZE = 14
val PLAYER_INVENTORY_SLOTS = 14..40
val PLAYER_HOTBAR_SLOTS = 41..49
const val CRAFTING_GRID_WIDTH = 3
const val PLAYER_INVENTORY_WIDTH = 9
const val COOKING_PROGRESS_PER_TICK = 2
const val COOKING_PROGRESS_INDEX = 0
const val COOKING_PROGRESS_TOTAL_TIME_INDEX = 1
const val IS_LID_OPEN_INDEX = 2
const val BASE_BROTH_COLOR = 0xFFFFFFFF.toInt()
fun clientTick(level: Level, pos: BlockPos, state: BlockState, campfireBlockEntity: CampfireBlockEntity) {
if (!level.isClientSide) return
val isLit = campfireBlockEntity.dataAccess.get(COOKING_PROGRESS_INDEX) > 0
val isSoundActive = BlockEntitySoundTracker.isActive(pos, campfireBlockEntity.runningSound.location)
if (isLit && !isSoundActive) {
BlockEntitySoundTracker.play(pos, CancellableSoundInstance(campfireBlockEntity.runningSound, pos, true, 0.8f, 1.0f))
} else if (!isLit && isSoundActive) {
BlockEntitySoundTracker.stop(pos, campfireBlockEntity.runningSound.location)
}
campfireBlockEntity.brothColor =
getColorMixFromSeasonings(campfireBlockEntity.getSeasonings())
?: BASE_BROTH_COLOR
campfireBlockEntity.bubbleColor =
getColorMixFromSeasonings(campfireBlockEntity.getSeasonings(), true)
?: BASE_BROTH_COLOR
if (campfireBlockEntity.particleCooldown > 0) {
campfireBlockEntity.particleCooldown--
} else {
if (isLit) {
val position = Vec3(pos.x + 0.5, pos.y + 0.5375, pos.z + 0.5)
campfireBlockEntity.particleEntityHandler(
position = position,
level = level,
particle = ResourceLocation("cobblemon", if (campfireBlockEntity.getSeasonings().isEmpty()) "broth_bubbles_basic" else "broth_bubbles")
)
}
campfireBlockEntity.particleCooldown = 20
}
}
fun serverTick(level: Level, pos: BlockPos, state: BlockState, campfireBlockEntity: CampfireBlockEntity) {
if (level.isClientSide) return
val craftingInput = CraftingInput.of(3, 3, campfireBlockEntity.items.subList(1, 10))
fun <T : CookingPotRecipeBase> fetchRecipe(
recipeType: RecipeType<T>
): Optional<RecipeHolder<CookingPotRecipeBase>> {
val optional = level.recipeManager.getRecipeFor(recipeType, craftingInput, level)
@Suppress("UNCHECKED_CAST")
return optional.map { it as RecipeHolder<CookingPotRecipeBase> }
}
// Check for both COOKING_POT_COOKING and COOKING_POT_SHAPELESS recipes
val optionalRecipe = fetchRecipe(CobblemonRecipeTypes.COOKING_POT_COOKING)
.or { fetchRecipe(CobblemonRecipeTypes.COOKING_POT_SHAPELESS) }
if (!optionalRecipe.isPresent) {
campfireBlockEntity.cookingProgress = 0
campfireBlockEntity.setItem(PREVIEW_ITEM_SLOT, ItemStack.EMPTY)
return
}
val cookingPotRecipe = optionalRecipe.get()
val recipe = cookingPotRecipe.value()
val cookedItem = recipe.assemble(craftingInput, level.registryAccess())
val resultSlotItem = campfireBlockEntity.getItem(0)
cookedItem.set(CobblemonItemComponents.COOKING_COMPONENT, campfireBlockEntity.createCookingComponentFromSlots())
campfireBlockEntity.setItem(PREVIEW_ITEM_SLOT, cookedItem)
if (campfireBlockEntity.isLidOpen) {
campfireBlockEntity.cookingProgress = 0
return
}
if (!resultSlotItem.isEmpty && !ItemStack.isSameItemSameComponents(resultSlotItem, cookedItem)) {
campfireBlockEntity.cookingProgress = 0
return
}
campfireBlockEntity.cookingProgress += COOKING_PROGRESS_PER_TICK
if (campfireBlockEntity.cookingProgress == campfireBlockEntity.cookingTotalTime) {
campfireBlockEntity.cookingProgress = 0
if (!cookedItem.isEmpty) {
campfireBlockEntity.recipeUsed = cookingPotRecipe
if (resultSlotItem.isEmpty) {
campfireBlockEntity.setItem(0, cookedItem)
} else {
resultSlotItem.grow(cookedItem.count)
}
campfireBlockEntity.consumeCraftingIngredients()
level.playSoundServer(
position = pos.bottomCenter,
sound = CobblemonSounds.CAMPFIRE_POT_CRAFT,
)
setChanged(level, pos, state);
}
}
}
}
private val runningSound = CobblemonSounds.CAMPFIRE_POT_COOK
private var cookingProgress : Int = 0
private var cookingTotalTime : Int = 200
private var isLidOpen : Boolean = true
private var items : NonNullList<ItemStack?> = NonNullList.withSize(ITEMS_SIZE, ItemStack.EMPTY)
private val recipesUsed: Object2IntOpenHashMap<ResourceLocation> = Object2IntOpenHashMap()
private val quickCheck: RecipeManager.CachedCheck<CraftingInput, *> = RecipeManager.createCheck(CobblemonRecipeTypes.COOKING_POT_COOKING)
private var potComponent: PotComponent? = null
private var particleCooldown: Int = 0
var brothColor: Int = BASE_BROTH_COLOR
var bubbleColor: Int = 0xFFFFFF
var dataAccess : ContainerData = object : ContainerData {
override fun get(index: Int): Int {
return when (index) {
COOKING_PROGRESS_INDEX -> this@CampfireBlockEntity.cookingProgress
COOKING_PROGRESS_TOTAL_TIME_INDEX -> this@CampfireBlockEntity.cookingTotalTime
IS_LID_OPEN_INDEX -> if (this@CampfireBlockEntity.isLidOpen) 1 else 0
else -> 0
}
}
override fun set(index: Int, value: Int) {
when (index) {
COOKING_PROGRESS_INDEX -> this@CampfireBlockEntity.cookingProgress = value
COOKING_PROGRESS_TOTAL_TIME_INDEX -> this@CampfireBlockEntity.cookingTotalTime = value
IS_LID_OPEN_INDEX -> this@CampfireBlockEntity.isLidOpen = value == 1
}
}
override fun getCount(): Int {
return 3
}
}
fun particleEntityHandler(position: Vec3, level: Level, particle: ResourceLocation): ParticleStorm {
val wrapper = MatrixWrapper()
val matrix = PoseStack()
wrapper.updateMatrix(matrix.last().pose())
wrapper.updatePosition(position)
val effect = BedrockParticleOptionsRepository.getEffect(particle) ?: throw IllegalStateException("Particle with resource location $particle not found")
val particleStorm = ParticleStorm(
effect,
wrapper,
wrapper,
level as ClientLevel,
sourceAlive = { cookingProgress > 0 },
sourceVisible = { cookingProgress > 0 },
getParticleColor = {
val red = FastColor.ARGB32.red(bubbleColor) / 255F
val green = FastColor.ARGB32.green(bubbleColor) / 255F
val blue = FastColor.ARGB32.blue(bubbleColor) / 255F
val alpha = FastColor.ARGB32.alpha(bubbleColor) / 255F
Vector4f(red, green, blue, alpha)
}
).also {
it.spawn()
}
return particleStorm
}
fun consumeCraftingIngredients() {
for (i in CRAFTING_GRID_SLOTS.first..SEASONING_SLOTS.last) {
val itemInSlot = getItem(i)
if (!itemInSlot.isEmpty) {
when (itemInSlot.item) {
Items.LAVA_BUCKET, Items.WATER_BUCKET, Items.MILK_BUCKET -> {
// Replace with empty bucket
setItem(i, ItemStack(Items.BUCKET))
}
Items.HONEY_BOTTLE -> {
// Replace with empty glass bottle
setItem(i, ItemStack(Items.GLASS_BOTTLE))
}
else -> {
// Decrease the stack size by 1
itemInSlot.shrink(1)
if (itemInSlot.count <= 0) {
setItem(i, ItemStack.EMPTY) // Clear the slot if empty
}
}
}
}
}
}
fun createCookingComponentFromSlots(): CookingComponent {
// Variables to store bait and seasoning
var bait1 = FishingBait.BLANK_BAIT
var bait2 = FishingBait.BLANK_BAIT
var bait3 = FishingBait.BLANK_BAIT
var seasoning1 = Seasoning.BLANK_SEASONING
var seasoning2 = Seasoning.BLANK_SEASONING
var seasoning3 = Seasoning.BLANK_SEASONING
// Iterate through slots 10-12
for ((index, slot) in SEASONING_SLOTS.withIndex()) {
val itemInSlot = getItem(slot)
if (!itemInSlot.isEmpty) {
// Check if item is a bait
val bait = FishingBaits.getFromBaitItemStack(itemInSlot)
if (bait != null) {
when (index) {
0 -> bait1 = bait
1 -> bait2 = bait
2 -> bait3 = bait
}
}
// Check if item is a seasoning
val seasoning = Seasonings.getFromItemStack(itemInSlot)
if (seasoning != null) {
when (index) {
0 -> seasoning1 = seasoning
1 -> seasoning2 = seasoning
2 -> seasoning3 = seasoning
}
}
}
}
// Create CookingComponent and attach to result item
return CookingComponent(
bait1 = bait1,
bait2 = bait2,
bait3 = bait3,
seasoning1 = seasoning1,
seasoning2 = seasoning2,
seasoning3 = seasoning3
)
}
fun getSeasonings(): List<ItemStack> =
items.subList(SEASONING_SLOTS.first, SEASONING_SLOTS.last + 1)
.filterNotNull()
.filter { !it.isEmpty }
fun getIngredients(): List<ItemStack> =
items.subList(CRAFTING_GRID_SLOTS.first, CRAFTING_GRID_SLOTS.last + 1)
.filterNotNull()
.filter { !it.isEmpty }
override fun getDefaultName(): Component {
return Component.translatable("cobblemon.container.campfire_pot")
}
override fun getWidth(): Int {
return CRAFTING_GRID_WIDTH
}
override fun getHeight(): Int {
return CRAFTING_GRID_WIDTH
}
override fun getItems(): NonNullList<ItemStack?> {
level?.let { onItemUpdate(it) }
return this.items
}
override fun setItems(items: NonNullList<ItemStack?>) {
this.items.clear()
this.items.addAll(items)
level?.let { onItemUpdate(it) }
}
override fun createMenu(
containerId: Int,
inventory: Inventory
): AbstractContainerMenu {
return CookingPotMenu(containerId, inventory, this, this.dataAccess)
}
override fun getContainerSize(): Int {
return this.items.size
}
override fun getSlotsForFace(side: Direction): IntArray {
return intArrayOf(0, 1, 2)
}
override fun canPlaceItemThroughFace(
index: Int,
itemStack: ItemStack,
direction: Direction?
): Boolean {
return false
}
override fun canTakeItemThroughFace(
index: Int,
stack: ItemStack,
direction: Direction
): Boolean {
return false
}
override fun setRecipeUsed(recipe: RecipeHolder<*>?) {
if (recipe != null) {
val resourceLocation = recipe.id()
this.recipesUsed.addTo(resourceLocation, 1)
}
}
override fun getRecipeUsed(): RecipeHolder<*>? {
return null
}
override fun fillStackedContents(contents: StackedContents) {
for (itemStack in this.items) {
contents.accountSimpleStack(itemStack);
}
}
override fun getItem(slot: Int): ItemStack {
return if (slot in 0 until items.size) items[slot] else ItemStack.EMPTY
}
fun getPotItem(): ItemStack? {
return potComponent?.potItem
}
fun setPotItem(stack: ItemStack?) {
this.potComponent = PotComponent(stack ?: ItemStack.EMPTY) // Ensure a non-null value is passed
setChanged()
level?.sendBlockUpdated(blockPos, blockState, blockState, Block.UPDATE_CLIENTS)
}
override fun saveAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.saveAdditional(tag, registries)
tag.putInt("CookingProgress", this.cookingProgress)
tag.putBoolean("IsLidOpen", this.isLidOpen)
ContainerHelper.saveAllItems(tag, this.items, registries)
potComponent?.let { component ->
PotComponent.CODEC.encodeStart(NbtOps.INSTANCE, component)
.result()
?.ifPresent { encoded -> tag.put("PotComponent", encoded) }
}
}
override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
super.loadAdditional(tag, registries)
this.cookingProgress = tag.getInt("CookingProgress")
this.isLidOpen = tag.getBoolean("IsLidOpen")
// Minecraft doesn't save empty item stacks to the items tag, so we have to manually clear them
// otherwise they would never clear and seasonings would always render wrongly
clearContent()
ContainerHelper.loadAllItems(tag, this.items, registries)
if (tag.contains("PotComponent")) {
val component = PotComponent.CODEC.parse(NbtOps.INSTANCE, tag.getCompound("PotComponent"))
.result()
?.orElse(null)
potComponent = component
}
}
override fun getUpdatePacket(): Packet<ClientGamePacketListener>? {
return ClientboundBlockEntityDataPacket.create(this)
}
override fun getUpdateTag(registryLookup: HolderLookup.Provider): CompoundTag {
return saveWithoutMetadata(registryLookup)
}
private fun onItemUpdate(level: Level) {
val oldState = level.getBlockState(blockPos)
level.sendBlockUpdated(blockPos, oldState, level.getBlockState(blockPos), Block.UPDATE_ALL)
level.updateNeighbourForOutputSignal(blockPos, level.getBlockState(blockPos).block)
setChanged()
level.sendBlockUpdated(blockPos, oldState, level.getBlockState(blockPos), Block.UPDATE_ALL)
}
override fun setRemoved() {
cookingProgress = 0
super.setRemoved()
if (level?.isClientSide == true) {
BlockEntitySoundTracker.stop(blockPos, runningSound.location)
}
}
}
@@ -0,0 +1,18 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState
class CandlePokeCakeBlockEntity(
pos: BlockPos,
state: BlockState
) : CakeBlockEntity(CobblemonBlockEntities.CANDLE_POKE_CAKE, pos, state)
@@ -0,0 +1,144 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.api.fishing.FishingBait
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.bait.BaitSpawnCause
import com.cobblemon.mod.common.api.spawning.detail.EntitySpawnResult
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
class LureCakeBlockEntity(
pos: BlockPos,
state: BlockState
) : CakeBlockEntity(CobblemonBlockEntities.LURE_CAKE, pos, state) {
/**
* Generate a `FishingBait` by combining effects from all `RodBaitComponent` data in the `CookingComponent`.
*/
fun getBaitFromLureCake(): FishingBait? {
val component = cookingComponent ?: return null
val combinedEffects = listOf(
component.bait1.effects,
component.bait2.effects,
component.bait3.effects
).flatten()
return FishingBait(
item = cobblemonResource("lure_cake"), // Directly specify the lure_cake ResourceLocation
effects = combinedEffects
)
}
/**
* Spawns a Pokémon of the given species around the specified position.
* @param world The world in which to spawn the Pokémon.
* @param pos The position around which to spawn the Pokémon.
* @param species The species of the Pokémon to spawn.
* @param isMale Whether the Pokémon should be male or female.
* @return The spawned Pokémon entity, or null if spawning failed.
*/
fun spawnPokemon(
world: Level,
pos: BlockPos
): PokemonEntity? {
println("Attempting to spawn Pokémon at $pos in world: ${world.dimension().location()}")
if (world !is ServerLevel) {
println("World is not a ServerLevel, aborting spawn.")
return null
}
val spawnPos = pos.offset(world.random.nextInt(-SPAWN_RADIUS.toInt(), SPAWN_RADIUS.toInt()), 0, world.random.nextInt(-SPAWN_RADIUS.toInt(), SPAWN_RADIUS.toInt()))
println("Spawn position determined as $spawnPos")
val spawner = BestSpawner.baitSpawner
val buckets = Cobblemon.bestSpawner.config.buckets
val chosenBucket = chooseSpawnBucket(buckets)
println("Chosen spawn bucket: $chosenBucket")
val spawnCause = BaitSpawnCause(
spawner = spawner,
bucket = chosenBucket,
entity = null,
baitStack = this.toItemStack(),
baitEffect = this.getBaitFromLureCake()
)
println("Spawn cause created: $spawnCause")
val result = spawner.run(spawnCause, level as ServerLevel, spawnPos)
var spawnedPokemon: PokemonEntity? = null
val resultingSpawn = result?.get()
if (resultingSpawn is EntitySpawnResult) {
for (entity in resultingSpawn.entities) {
// we can query the spawned pokemon here
spawnedPokemon = (entity as PokemonEntity)
// CRAB IDEA: todo maybe some grass particles? as if they appeared from the grass or forest?
return spawnedPokemon
}
}
return null
}
fun chooseSpawnBucket(buckets: List<SpawnBucket>): SpawnBucket {
val baseIncreases = listOf(2.5F, 1.0F, 0.6F) // 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
}
}.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
}
companion object {
private const val RANDOM_SPAWN_CHANCE = 100
private const val SPAWN_RADIUS = 5.0
/*val TICKER = BlockEntityTicker<LureCakeBlockEntity> { world, pos, state, blockEntity ->
println("Ticker running for LureCakeBlockEntity at $pos in world: ${world.dimension().location()}")
if (world.random.nextInt(RANDOM_SPAWN_CHANCE) == 0) {
println("Triggering Pokémon spawn")
blockEntity.spawnPokemon(world, pos)
world.sendBlockUpdated(pos, state, state, Block.UPDATE_CLIENTS)
}
}*/
}
}
@@ -0,0 +1,18 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState
class PokeCakeBlockEntity(
pos: BlockPos,
state: BlockState
) : CakeBlockEntity(CobblemonBlockEntities.POKE_CAKE, pos, state)
@@ -0,0 +1,16 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
class SweetIncenseBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(CobblemonBlockEntities.INCENSE_SWEET, pos, state)
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.block.grower
import com.cobblemon.mod.common.world.feature.CobblemonConfiguredFeatures
import net.minecraft.world.level.block.grower.TreeGrower
import java.util.*
class SaccharineTreeGrower() : TreeGrower(
"saccharine_tree",
Optional.empty(),
Optional.of(CobblemonConfiguredFeatures.SACCHARINE_TREE_KEY),
Optional.empty()
)
@@ -23,25 +23,20 @@ import com.cobblemon.mod.common.client.battle.ClientBattle
import com.cobblemon.mod.common.client.gui.PartyOverlay
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
import com.cobblemon.mod.common.client.render.block.CandlePokeCakeBlockEntityRenderer
import com.cobblemon.mod.common.client.render.ClientPlayerIcon
import com.cobblemon.mod.common.client.render.DeferredRenderer
import com.cobblemon.mod.common.client.render.block.BerryBlockRenderer
import com.cobblemon.mod.common.client.render.block.DisplayCaseRenderer
import com.cobblemon.mod.common.client.render.block.FossilAnalyzerRenderer
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.LecternBlockEntityRenderer
import com.cobblemon.mod.common.client.render.block.RestorationTankRenderer
import com.cobblemon.mod.common.client.render.block.*
import com.cobblemon.mod.common.client.render.boat.CobblemonBoatRenderer
import com.cobblemon.mod.common.client.render.color.AprijuiceItemColorProvider
import com.cobblemon.mod.common.client.render.color.PokeBaitItemColorProvider
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
import com.cobblemon.mod.common.client.render.layer.PokemonOnShoulderRenderer
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockAnimationRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.BerryModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.MiscModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.*
import com.cobblemon.mod.common.client.render.npc.NPCRenderer
import com.cobblemon.mod.common.client.render.pokeball.PokeBallRenderer
import com.cobblemon.mod.common.client.render.pokemon.PokemonRenderer
@@ -49,10 +44,7 @@ import com.cobblemon.mod.common.client.requests.ClientPlayerActionRequests
import com.cobblemon.mod.common.client.sound.BattleMusicController
import com.cobblemon.mod.common.client.sound.EntitySoundTracker
import com.cobblemon.mod.common.client.storage.ClientStorageManager
import com.cobblemon.mod.common.client.tooltips.CobblemonTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.FishingBaitTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.FishingRodTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.TooltipManager
import com.cobblemon.mod.common.client.tooltips.*
import com.cobblemon.mod.common.client.trade.ClientTrade
import com.cobblemon.mod.common.data.CobblemonDataProvider
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
@@ -127,6 +119,7 @@ object CobblemonClient {
//registerColors()
registerFlywheelRenderers()
this.registerEntityRenderers()
this.registerItemColors()
Berries.observable.subscribe {
BerryModelRepository.patchModels()
}
@@ -173,6 +166,8 @@ object CobblemonClient {
TooltipManager.registerTooltipGenerator(CobblemonTooltipGenerator)
TooltipManager.registerTooltipGenerator(FishingBaitTooltipGenerator)
TooltipManager.registerTooltipGenerator(FishingRodTooltipGenerator)
TooltipManager.registerTooltipGenerator(SeasoningTooltipGenerator)
TooltipManager.registerTooltipGenerator(AprijuiceTooltipGenerator)
}
fun registerFlywheelRenderers() {
@@ -236,6 +231,7 @@ object CobblemonClient {
CobblemonBlocks.BIG_ROOT,
CobblemonBlocks.REVIVAL_HERB,
CobblemonBlocks.VIVICHOKE_SEEDS,
CobblemonBlocks.HEARTY_GRAINS,
CobblemonBlocks.PEP_UP_FLOWER,
CobblemonBlocks.POTTED_PEP_UP_FLOWER,
CobblemonBlocks.REVIVAL_HERB,
@@ -256,7 +252,25 @@ object CobblemonClient {
CobblemonBlocks.SKY_TUMBLESTONE_CLUSTER,
CobblemonBlocks.GIMMIGHOUL_CHEST,
CobblemonBlocks.DISPLAY_CASE,
CobblemonBlocks.LECTERN
CobblemonBlocks.SACCHARINE_DOOR,
CobblemonBlocks.SACCHARINE_TRAPDOOR,
CobblemonBlocks.SACCHARINE_SIGN,
CobblemonBlocks.SACCHARINE_WALL_SIGN,
CobblemonBlocks.SACCHARINE_HANGING_SIGN,
CobblemonBlocks.SACCHARINE_WALL_HANGING_SIGN,
CobblemonBlocks.SACCHARINE_SAPLING,
CobblemonBlocks.LURE_CAKE,
CobblemonBlocks.POKE_CAKE,
CobblemonBlocks.LECTERN,
CobblemonBlocks.CAMPFIRE,
CobblemonBlocks.BLACK_CAMPFIRE_POT,
CobblemonBlocks.BLUE_CAMPFIRE_POT,
CobblemonBlocks.GREEN_CAMPFIRE_POT,
CobblemonBlocks.PINK_CAMPFIRE_POT,
CobblemonBlocks.RED_CAMPFIRE_POT,
CobblemonBlocks.WHITE_CAMPFIRE_POT,
CobblemonBlocks.YELLOW_CAMPFIRE_POT
)
this.createBoatModelLayers()
@@ -290,6 +304,10 @@ object CobblemonClient {
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.GILDED_CHEST, ::GildedChestBlockRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.DISPLAY_CASE, ::DisplayCaseRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.LECTERN, ::LecternBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.CAMPFIRE, ::CampfireBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.LURE_CAKE, ::CakeBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.POKE_CAKE, ::CakeBlockEntityRenderer)
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.CANDLE_POKE_CAKE, ::CandlePokeCakeBlockEntityRenderer)
}
private fun registerEntityRenderers() {
@@ -311,6 +329,11 @@ object CobblemonClient {
this.implementation.registerEntityRenderer(CobblemonEntities.NPC, ::NPCRenderer)
}
private fun registerItemColors() {
implementation.registerItemColors(PokeBaitItemColorProvider, CobblemonItems.POKE_BAIT)
implementation.registerItemColors(AprijuiceItemColorProvider, *CobblemonItems.aprijuices.toTypedArray())
}
fun reloadCodedAssets(resourceManager: ResourceManager) {
LOGGER.info("Loading assets...")
// Particles come first because animations need them.
@@ -0,0 +1,21 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.cooking
enum class AprijuiceTypes {
BLACK,
BLUE,
GREEN,
PINK,
RED,
WHITE,
YELLOW;
}
@@ -0,0 +1,52 @@
/*
* 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.gui.cookingpot
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.Component
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
class CookButton(
pX: Int, pY: Int,
var selected: Boolean = false,
onPress: OnPress
): Button(pX, pY, WIDTH.toInt(), HEIGHT.toInt(), Component.literal("Cook"), onPress, DEFAULT_NARRATION) {
companion object {
private const val WIDTH = 20F
private const val HEIGHT = 19F
private val buttonResource = cobblemonResource("textures/gui/campfirepot/button_cook.png")
private val buttonSelectedResource = cobblemonResource("textures/gui/campfirepot/button_cook_selected.png")
}
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
blitk(
matrixStack = context.pose(),
texture = if (selected) buttonSelectedResource else buttonResource,
x = x,
y = y,
width = WIDTH,
height = HEIGHT,
vOffset = if (isMouseOver(pMouseX.toDouble(), pMouseY.toDouble())) HEIGHT else 0,
textureHeight = HEIGHT * 2
)
}
override fun playDownSound(soundManager: SoundManager) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F))
}
}
@@ -0,0 +1,55 @@
/*
* 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.gui.cookingpot
import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import io.netty.buffer.ByteBuf
import net.minecraft.network.codec.ByteBufCodecs
import net.minecraft.network.codec.StreamCodec
import net.minecraft.util.ByIdMap
import net.minecraft.util.StringRepresentable
import java.util.function.IntFunction
enum class CookingPotBookCategory(private val categoryName: String, private val categoryId: Int) : StringRepresentable {
FOODS("foods", 0),
MEDICINES("medicines", 1),
BAITS("baits", 2),
MISC("misc", 3);
override fun getSerializedName(): String {
return this.categoryName
}
private fun getCategoryId(): Int {
return this.categoryId
}
companion object {
val CODEC: Codec<CookingPotBookCategory> = Codec.STRING.flatXmap(
{ name ->
val category = CookingPotBookCategory.values().firstOrNull { it.categoryName.equals(name, ignoreCase = true) }
if (category != null) {
println("Successfully parsed category: $name -> ${category.name}")
DataResult.success(category)
} else {
println("Failed to parse category: $name")
DataResult.error { "Unknown category: $name" }
}
},
{ category ->
println("Serializing category: ${category.categoryName}")
DataResult.success(category.categoryName)
}
)
val BY_ID: IntFunction<CookingPotBookCategory> = ByIdMap.continuous(CookingPotBookCategory::getCategoryId,
CookingPotBookCategory.entries.toTypedArray(), ByIdMap.OutOfBoundsStrategy.ZERO)
val STREAM_CODEC: StreamCodec<ByteBuf, CookingPotBookCategory> = ByteBufCodecs.idMapper(BY_ID, CookingPotBookCategory::getCategoryId)
}
}
@@ -0,0 +1,96 @@
/*
* 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.gui.cookingpot
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.CRAFTING_GRID_WIDTH
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.ITEMS_SIZE
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.RESULT_SLOT
import net.minecraft.core.NonNullList
import net.minecraft.world.ContainerHelper
import net.minecraft.world.entity.player.Player
import net.minecraft.world.entity.player.StackedContents
import net.minecraft.world.inventory.TransientCraftingContainer
import net.minecraft.world.item.ItemStack
import java.util.List
class CookingPotContainer : TransientCraftingContainer {
val menu : CookingPotMenu?
val items : NonNullList<ItemStack>
var outputSlot: ItemStack = ItemStack.EMPTY
constructor(menu : CookingPotMenu, width: Int, height: Int) : super(menu, width, height) {
this.menu = menu
this.items = NonNullList.withSize(ITEMS_SIZE, ItemStack.EMPTY)
}
override fun getContainerSize(): Int {
return this.items.size
}
override fun isEmpty(): Boolean {
for (itemStack in this.items) {
if (!itemStack.isEmpty) {
return false
}
}
return true
}
override fun getItem(slot: Int): ItemStack {
return if (slot >= this.getContainerSize()) ItemStack.EMPTY else this.items[slot]
}
override fun setItem(slot: Int, stack: ItemStack) {
this.items[slot] = stack
this.menu?.slotsChanged(this)
}
override fun removeItemNoUpdate(slot: Int): ItemStack {
return ContainerHelper.takeItem(this.items, slot)
}
override fun removeItem(slot: Int, amount: Int): ItemStack {
val itemStack = ContainerHelper.removeItem(this.items, slot, amount)
if (!itemStack.isEmpty) {
this.menu?.slotsChanged(this)
}
return itemStack
}
override fun setChanged() {}
override fun stillValid(player: Player): Boolean {
return true
}
override fun clearContent() {
for (index in 0 until this.items.size) {
this.items[index] = ItemStack.EMPTY
}
}
override fun getHeight(): Int {
return CRAFTING_GRID_WIDTH
}
override fun getWidth(): Int {
return CRAFTING_GRID_WIDTH
}
override fun getItems(): kotlin.collections.List<ItemStack?>? {
return List.copyOf(this.items)
}
override fun fillStackedContents(contents: StackedContents) {
for (itemStack in this.items) {
contents.accountSimpleStack(itemStack)
}
}
}
@@ -0,0 +1,240 @@
/*
* 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.gui.cookingpot
import com.cobblemon.mod.common.CobblemonMenuType
import com.cobblemon.mod.common.CobblemonRecipeTypes
import com.cobblemon.mod.common.api.cooking.Seasonings
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.CRAFTING_GRID_SLOTS
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.CRAFTING_GRID_WIDTH
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.ITEMS_SIZE
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.PLAYER_HOTBAR_SLOTS
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.PLAYER_INVENTORY_SLOTS
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.PLAYER_INVENTORY_WIDTH
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.PREVIEW_ITEM_SLOT
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.RESULT_SLOT
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.SEASONING_SLOTS
import net.minecraft.recipebook.ServerPlaceRecipe
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.entity.player.StackedContents
import net.minecraft.world.inventory.*
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.CraftingInput
import net.minecraft.world.item.crafting.RecipeHolder
import net.minecraft.world.item.crafting.RecipeManager
import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.level.Level
class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, ContainerListener {
private val player: Player
private val level: Level
private val playerInventory: Inventory
private val container: CraftingContainer
private val resultContainer: ResultContainer
val containerData: ContainerData
private val recipeType: RecipeType<CookingPotRecipe> = CobblemonRecipeTypes.COOKING_POT_COOKING
private val quickCheck = RecipeManager.createCheck(CobblemonRecipeTypes.COOKING_POT_COOKING)
constructor(containerId: Int, playerInventory: Inventory) : super(CobblemonMenuType.COOKING_POT, containerId) {
this.playerInventory = playerInventory
this.container = CookingPotContainer(this, CRAFTING_GRID_WIDTH, CRAFTING_GRID_WIDTH)
this.resultContainer = ResultContainer()
this.containerData = SimpleContainerData(3)
this.addDataSlots(containerData)
this.player = playerInventory.player
this.level = playerInventory.player.level()
initializeSlots(playerInventory)
}
constructor(containerId: Int, playerInventory: Inventory, container: CraftingContainer, containerData: ContainerData) : super(CobblemonMenuType.COOKING_POT, containerId) {
this.playerInventory = playerInventory
this.container = container
this.containerData = containerData
this.addDataSlots(containerData)
this.resultContainer = ResultContainer()
this.player = playerInventory.player
this.level = playerInventory.player.level()
container.startOpen(playerInventory.player)
initializeSlots(playerInventory)
this.addSlotListener(this)
}
private fun initializeSlots(playerInventory: Inventory) {
val resultSlotX = 128
val resultSlotY = 55
addSlot(CookingPotResultSlot(this.container, RESULT_SLOT, resultSlotX, resultSlotY))
for ((index, slotIndex) in CRAFTING_GRID_SLOTS.withIndex()) {
val i = index / CRAFTING_GRID_WIDTH
val j = index % CRAFTING_GRID_WIDTH
addSlot(Slot(this.container, slotIndex, 33 + j * 18, 18 + i * 18))
}
for ((index, slotIndex) in SEASONING_SLOTS.withIndex()) {
addSlot(SeasoningSlot(this.container, slotIndex, 110 + index * 18, 18))
}
addSlot(CookingPotPreviewSlot(this.container, PREVIEW_ITEM_SLOT, resultSlotX, resultSlotY))
for ((index, _) in PLAYER_INVENTORY_SLOTS.withIndex()) {
val i = index / PLAYER_INVENTORY_WIDTH
val j = index % PLAYER_INVENTORY_WIDTH
addSlot(Slot(playerInventory, index + 9, 8 + j * 18, 84 + i * 18))
}
for ((index, _) in PLAYER_HOTBAR_SLOTS.withIndex()) {
addSlot(Slot(playerInventory, index, 8 + index * 18, 142))
}
}
override fun broadcastChanges() {
super.broadcastChanges()
}
override fun handlePlacement(placeAll: Boolean, recipe: RecipeHolder<*>, player: ServerPlayer) {
// Check if the recipe value implements CookingPotRecipeBase
val recipeValue = recipe.value()
if (recipeValue is CookingPotRecipeBase) {
@Suppress("UNCHECKED_CAST")
val castedRecipe = recipe as RecipeHolder<CookingPotRecipeBase>
this.beginPlacingRecipe()
try {
val serverPlaceRecipe = ServerPlaceRecipe(this)
serverPlaceRecipe.recipeClicked(player, castedRecipe, placeAll)
} finally {
this.finishPlacingRecipe(castedRecipe)
}
} else {
throw IllegalArgumentException("Unsupported recipe type: ${recipeValue::class.java.name}")
}
}
override fun removed(player: Player) {
super.removed(player)
}
override fun fillCraftSlotsStackedContents(itemHelper: StackedContents) {
this.container.fillStackedContents(itemHelper)
}
override fun clearCraftingContent() {
container.clearContent()
}
override fun recipeMatches(recipe: RecipeHolder<CookingPotRecipeBase>): Boolean {
val recipeValue = recipe.value()
return if (recipeValue is CookingPotRecipeBase) {
recipeValue.matches(container.asCraftInput(), level)
} else {
false
}
}
override fun getResultSlotIndex(): Int {
return RESULT_SLOT
}
override fun getGridWidth(): Int {
return CRAFTING_GRID_WIDTH
}
override fun getGridHeight(): Int {
return CRAFTING_GRID_WIDTH
}
override fun getSize(): Int {
return ITEMS_SIZE
}
fun getBurnProgress(): Float {
val i = this.containerData.get(0)
val j = this.containerData.get(1)
return if (j != 0 && i != 0) {
Math.clamp((i.toFloat() / j.toFloat()), 0.0F, 1.0F)
} else {
0.0F;
}
}
override fun getRecipeBookType(): RecipeBookType {
return RecipeBookType.valueOf("COOKING_POT")
}
override fun shouldMoveToInventory(slotIndex: Int): Boolean {
return slotIndex != RESULT_SLOT && slotIndex != PREVIEW_ITEM_SLOT
}
override fun quickMoveStack(
player: Player,
index: Int
): ItemStack {
var itemStack = ItemStack.EMPTY
val slot = slots[index]
if (slot.hasItem()) {
val slotItemStack = slot.item
itemStack = slotItemStack.copy()
if (index == RESULT_SLOT) {
if (!this.moveItemStackTo(slotItemStack, PLAYER_INVENTORY_SLOTS.first, PLAYER_HOTBAR_SLOTS.last + 1, false)) {
return ItemStack.EMPTY
}
slot.onQuickCraft(slotItemStack, itemStack);
} else if (index in PLAYER_INVENTORY_SLOTS || index in PLAYER_HOTBAR_SLOTS) {
if (Seasonings.isSeasoning(slotItemStack)) {
if (!this.moveItemStackTo(slotItemStack, SEASONING_SLOTS.first, SEASONING_SLOTS.last + 1, false) &&
!this.moveItemStackTo(slotItemStack, CRAFTING_GRID_SLOTS.first, CRAFTING_GRID_SLOTS.last + 1, false)
) {
return ItemStack.EMPTY
}
} else if (!this.moveItemStackTo(slotItemStack, CRAFTING_GRID_SLOTS.first, CRAFTING_GRID_SLOTS.last + 1, false)) {
return ItemStack.EMPTY
}
} else if (index in CRAFTING_GRID_SLOTS || index in SEASONING_SLOTS) {
if (!this.moveItemStackTo(slotItemStack, PLAYER_INVENTORY_SLOTS.first, PLAYER_HOTBAR_SLOTS.last + 1, false)) {
return ItemStack.EMPTY
}
}
if (slotItemStack.isEmpty) {
slot.setByPlayer(ItemStack.EMPTY)
} else {
slot.setChanged()
}
if (slotItemStack.count == itemStack.count) {
return ItemStack.EMPTY
}
slot.onTake(player, slotItemStack)
}
return itemStack
}
override fun stillValid(player: Player): Boolean {
return this.container.stillValid(player)
}
override fun slotChanged(containerToSend: AbstractContainerMenu, dataSlotIndex: Int, stack: ItemStack) {
broadcastChanges()
}
override fun dataChanged(
containerMenu: AbstractContainerMenu,
dataSlotIndex: Int,
value: Int
) {
}
}
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.cookingpot
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.RESULT_SLOT
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.CraftingContainer
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.ItemStack
class CookingPotPreviewSlot(
container: CraftingContainer,
index: Int,
x: Int,
y: Int
) : Slot(container, index, x, y) {
override fun isActive(): Boolean {
return container.getItem(RESULT_SLOT).isEmpty
}
override fun isFake(): Boolean = true
override fun mayPlace(stack: ItemStack): Boolean = false
override fun mayPickup(player: Player): Boolean = false
override fun isHighlightable() = false
}
@@ -0,0 +1,149 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.cookingpot
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonRecipeSerializers
import com.cobblemon.mod.common.CobblemonRecipeTypes
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.HolderLookup
import net.minecraft.core.NonNullList
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.codec.StreamCodec
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.*
import net.minecraft.world.level.Level
class CookingPotRecipe(
val pattern: ShapedRecipePattern,
val result: ItemStack,
val groupName: String,
val category: CookingPotBookCategory,
val showNotification: Boolean
) : Recipe<CraftingInput>, CookingPotRecipeBase {
override fun getGroup(): String {
return groupName
}
/*override fun matches(input: CraftingInput, level: Level): Boolean {
val matches = this.pattern.matches(input)
return matches
}*/
/*override fun matches(input: CraftingInput, level: Level): Boolean {
val craftingItems = (1..9).map { input.getItem(it) }
val filteredInput = CraftingInput.of(3, 3, craftingItems)
return this.pattern.matches(filteredInput)
}*/
override fun matches(input: CraftingInput, level: Level): Boolean {
//println("Validating recipe match in CookingPotRecipe...")
// Create a filtered CraftingInput with only slots 1-9
val filteredItems = (0..8).mapNotNull { index ->
if (index < input.size()) input.getItem(index) else ItemStack.EMPTY
}
val filteredInput = CraftingInput.of(3, 3, filteredItems)
/*// Debugging: Log filtered crafting grid contents
for (i in 0 until filteredInput.size()) {
val itemStack = filteredInput.getItem(i)
println("Filtered crafting slot $i: ${itemStack.item} (${itemStack.count})")
}*/
// Perform pattern matching on the filtered input
val matches = this.pattern.matches(filteredInput)
//println("Pattern match result: $matches")
/*// Additional logic for specific recipes
if (this.pattern.width() == 3 && this.pattern.height() == 3) {
if (this.result.item == CobblemonItems.DAWN_STONE_BLOCK.asItem()) {
println("Special case for Dawn Stone Block recipe.")
}
}*/
if (matches == true) {
val test = 1
}
return matches
}
override fun assemble(
input: CraftingInput,
registries: HolderLookup.Provider
): ItemStack {
return this.getResultItem(registries).copy()
}
override fun canCraftInDimensions(width: Int, height: Int): Boolean {
return true
}
override fun getResultItem(registries: HolderLookup.Provider): ItemStack {
return this.result
}
override fun getSerializer(): RecipeSerializer<*> {
return CobblemonRecipeSerializers.COOKING_POT_COOKING
}
override fun getType(): RecipeType<CookingPotRecipe> {
return CobblemonRecipeTypes.COOKING_POT_COOKING
}
override fun getIngredients(): NonNullList<Ingredient> {
return this.pattern.ingredients()
}
class Serializer : RecipeSerializer<CookingPotRecipe> {
companion object {
val CODEC: MapCodec<CookingPotRecipe> = RecordCodecBuilder.mapCodec { instance ->
instance.group(
ShapedRecipePattern.MAP_CODEC.forGetter { recipe -> recipe.pattern },
ItemStack.STRICT_CODEC.fieldOf("result").forGetter { recipe -> recipe.result },
Codec.STRING.optionalFieldOf("group", "").forGetter { recipe -> recipe.group },
CookingPotBookCategory.CODEC.fieldOf("category").orElse(CookingPotBookCategory.MISC).forGetter { recipe -> recipe.category },
Codec.BOOL.optionalFieldOf("show_notification", true).forGetter { recipe -> recipe.showNotification }
).apply(instance, ::CookingPotRecipe)
}
val STREAM_CODEC: StreamCodec<RegistryFriendlyByteBuf, CookingPotRecipe> = StreamCodec.of(::toNetwork, ::fromNetwork)
private fun fromNetwork(buffer: RegistryFriendlyByteBuf): CookingPotRecipe {
val group = buffer.readUtf()
val category = buffer.readEnum(CookingPotBookCategory::class.java)
val pattern = ShapedRecipePattern.STREAM_CODEC.decode(buffer)
val result = ItemStack.STREAM_CODEC.decode(buffer)
val showNotification = buffer.readBoolean()
return CookingPotRecipe(pattern, result, group, category, showNotification)
}
private fun toNetwork(buffer: RegistryFriendlyByteBuf, recipe: CookingPotRecipe) {
buffer.writeUtf(recipe.groupName)
buffer.writeEnum(recipe.category)
ShapedRecipePattern.STREAM_CODEC.encode(buffer, recipe.pattern)
ItemStack.STREAM_CODEC.encode(buffer, recipe.result)
buffer.writeBoolean(recipe.showNotification)
}
}
override fun codec(): MapCodec<CookingPotRecipe> {
return CODEC
}
override fun streamCodec(): StreamCodec<RegistryFriendlyByteBuf, CookingPotRecipe> {
return STREAM_CODEC
}
}
}
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.cookingpot
import net.minecraft.core.HolderLookup
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.CraftingInput
import net.minecraft.world.item.crafting.Recipe
import net.minecraft.world.level.Level
interface CookingPotRecipeBase: Recipe<CraftingInput> {
override fun matches(input: CraftingInput, level: Level): Boolean
override fun assemble(input: CraftingInput, registries: HolderLookup.Provider): ItemStack
}
@@ -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.client.gui.cookingpot
import com.cobblemon.mod.common.CobblemonSounds
import net.minecraft.client.Minecraft
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.CraftingContainer
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.ItemStack
class CookingPotResultSlot(
container: CraftingContainer,
index: Int,
x: Int,
y: Int
) : Slot(container, index, x, y) {
override fun onTake(player: Player, stack: ItemStack) {
val menu = player.containerMenu
if (menu is CookingPotMenu) {
// val cookingComponent = menu.createCookingComponentFromSlots()
// Attach the CookingComponent to the result stack
// stack.set(CobblemonItemComponents.COOKING_COMPONENT, cookingComponent)
// menu.consumeCraftingIngredients() // Decrement ingredients
menu.broadcastChanges() // Notify the client
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.CAMPFIRE_POT_USE, 1.0f, 1.0f))
} else {
println("Player menu is not CookingPotMenu!")
}
super.onTake(player, stack)
}
override fun mayPlace(stack: ItemStack): Boolean = false
override fun isActive(): Boolean = hasItem()
}
@@ -0,0 +1,212 @@
/*
* 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.gui.cookingpot
import com.cobblemon.mod.common.CobblemonNetwork.sendToServer
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.IS_LID_OPEN_INDEX
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.PREVIEW_ITEM_SLOT
import com.cobblemon.mod.common.mixin.accessor.RecipeBookComponentAccessor
import com.cobblemon.mod.common.net.messages.client.cooking.ToggleCookingPotLidPacket
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.blaze3d.systems.RenderSystem
import net.fabricmc.api.EnvType
import net.fabricmc.api.Environment
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.ImageButton
import net.minecraft.client.gui.components.StateSwitchingButton
import net.minecraft.client.gui.components.Tooltip
import net.minecraft.client.gui.components.WidgetSprites
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent
import net.minecraft.client.gui.screens.recipebook.RecipeUpdateListener
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.inventory.RecipeBookMenu
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.crafting.CraftingInput
import kotlin.math.ceil
@Environment(EnvType.CLIENT)
class CookingPotScreen(
menu: CookingPotMenu,
playerInventory: Inventory,
title: Component
) : AbstractContainerScreen<CookingPotMenu>(
menu,
playerInventory,
Component.translatable("cobblemon.container.campfire_pot")
), RecipeUpdateListener {
companion object {
private const val BACKGROUND_HEIGHT = 166
private const val BACKGROUND_WIDTH = 176
private const val COOK_PROGRESS_HEIGHT = 12
private const val COOK_PROGRESS_WIDTH = 22
private val BACKGROUND = cobblemonResource("textures/gui/campfirepot/campfire_pot.png")
private val COOK_PROGRESS_SPRITE = cobblemonResource("textures/gui/campfirepot/cook_progress.png")
private val FILTER_BUTTON_SPRITES = WidgetSprites(
ResourceLocation.withDefaultNamespace("recipe_book/campfire_pot_filter_enabled"),
ResourceLocation.withDefaultNamespace("recipe_book/campfire_pot_filter_disabled"),
ResourceLocation.withDefaultNamespace("recipe_book/campfire_pot_filter_enabled_highlighted"),
ResourceLocation.withDefaultNamespace("recipe_book/campfire_pot_filter_disabled_highlighted")
)
}
private val recipeBookComponent: RecipeBookComponent = RecipeBookComponent()
private var widthTooNarrow : Boolean = false
private lateinit var cookButton: CookButton
init {
this.imageWidth = BACKGROUND_WIDTH
this.imageHeight = BACKGROUND_HEIGHT
}
override fun containerTick() {
super.containerTick()
this.recipeBookComponent.tick()
}
override fun init() {
super.init()
this.widthTooNarrow = this.width < 379
this.recipeBookComponent.init(this.width, this.height, this.minecraft!!, this.widthTooNarrow, this.menu as RecipeBookMenu<CraftingInput, CookingPotRecipe>)
if (this.recipeBookComponent.isVisible) {
val recipeBookFilterButton = (this.recipeBookComponent as RecipeBookComponentAccessor).filterButton
overrideRecipeBookFilterButton(recipeBookFilterButton)
}
this.leftPos = this.recipeBookComponent.updateScreenPosition(this.width, this.imageWidth)
val topPos = ((height - BACKGROUND_HEIGHT) / 2)
val recipeBookButton = ImageButton(
this.leftPos + 5, topPos + 35, 20, 18, RecipeBookComponent.RECIPE_BUTTON_SPRITES
) { button ->
this.recipeBookComponent.toggleVisibility()
if (this.recipeBookComponent.isVisible) overrideRecipeBookFilterButton((this.recipeBookComponent as RecipeBookComponentAccessor).filterButton)
this.leftPos = this.recipeBookComponent.updateScreenPosition(this.width, this.imageWidth)
button.setPosition(this.leftPos + 5, topPos + 35)
}
this.addWidget(this.recipeBookComponent)
this.addRenderableWidget(recipeBookButton)
this.titleLabelX = this.imageWidth - (BACKGROUND_WIDTH / 2) - (this.font.width(this.title) / 2)
this.titleLabelY = 6
if (::cookButton.isInitialized) removeWidget(cookButton)
cookButton = CookButton(this.leftPos + 97, topPos + 57, menu.containerData.get(IS_LID_OPEN_INDEX) == 0) {
val isLidClosed = menu.containerData.get(IS_LID_OPEN_INDEX) == 0
sendToServer(ToggleCookingPotLidPacket(isLidClosed))
}
addRenderableWidget(cookButton)
}
override fun renderLabels(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int) {
guiGraphics.drawString(this.font, this.title, this.titleLabelX, this.titleLabelY, 4210752, false)
}
override fun renderBg(
context: GuiGraphics,
partialTick: Float,
mouseX: Int,
mouseY: Int
) {
blitk(
matrixStack = context.pose(),
texture = BACKGROUND,
x = leftPos, y = (height - BACKGROUND_HEIGHT) / 2,
width = BACKGROUND_WIDTH, height = BACKGROUND_HEIGHT
)
val cookProgress = ceil(menu.getBurnProgress() * COOK_PROGRESS_WIDTH).toInt()
blitk(
matrixStack = context.pose(),
texture = COOK_PROGRESS_SPRITE,
x = leftPos + 96,
y = topPos + 39,
width = cookProgress,
height = COOK_PROGRESS_HEIGHT,
textureWidth = COOK_PROGRESS_WIDTH
)
}
override fun render(
context: GuiGraphics,
mouseX: Int,
mouseY: Int,
partialTicks: Float
) {
cookButton.selected = menu.containerData.get(IS_LID_OPEN_INDEX) == 0
cookButton.setPosition(this.leftPos + 97, topPos + 57)
if (this.recipeBookComponent.isVisible && this.widthTooNarrow) {
this.renderBackground(context, mouseX, mouseY, partialTicks)
this.recipeBookComponent.render(context, mouseX, mouseY, partialTicks)
} else {
super.render(context, mouseX, mouseY, partialTicks)
this.recipeBookComponent.render(context, mouseX, mouseY, partialTicks)
this.recipeBookComponent.renderGhostRecipe(context, this.leftPos, this.topPos, true, partialTicks)
}
this.renderTooltip(context, mouseX, mouseY)
this.recipeBookComponent.renderTooltip(context, this.leftPos, this.topPos, mouseX, mouseY)
}
override fun renderSlot(guiGraphics: GuiGraphics, slot: Slot) {
if (slot.index != PREVIEW_ITEM_SLOT) {
super.renderSlot(guiGraphics, slot)
return
}
RenderSystem.enableBlend()
RenderSystem.setShaderColor(1F, 1F, 1F, 0.5F)
guiGraphics.renderFakeItem(slot.item, slot.x, slot.y);
guiGraphics.renderItemDecorations(minecraft!!.font, slot.item, slot.x, slot.y)
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F)
RenderSystem.disableBlend()
}
override fun recipesUpdated() {
this.recipeBookComponent.recipesUpdated()
}
override fun getRecipeBookComponent(): RecipeBookComponent = this.recipeBookComponent
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (this.recipeBookComponent.mouseClicked(mouseX, mouseY, button)) {
this.focused = this.recipeBookComponent
if (this.recipeBookComponent.isVisible) {
val recipeBookFilterButton = (this.recipeBookComponent as RecipeBookComponentAccessor).filterButton
if (recipeBookFilterButton.isMouseOver(mouseX, mouseY)) overrideRecipeBookFilterButton(recipeBookFilterButton)
}
return true
} else {
return if (this.widthTooNarrow && this.recipeBookComponent.isVisible) true else super.mouseClicked(mouseX, mouseY, button)
}
}
private fun overrideRecipeBookFilterButton(filterButton: StateSwitchingButton) {
filterButton.initTextureValues(FILTER_BUTTON_SPRITES)
filterButton.setTooltip(
if (filterButton.isStateTriggered()) Tooltip.create(Component.translatable("cobblemon.container.campfire_pot.recipe_book.toggle_recipes"))
else Tooltip.create(Component.translatable("gui.recipebook.toggleRecipes.all"))
)
}
}
@@ -0,0 +1,155 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.cookingpot
import com.cobblemon.mod.common.CobblemonRecipeSerializers
import com.cobblemon.mod.common.CobblemonRecipeTypes
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotBookCategory
import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import it.unimi.dsi.fastutil.ints.IntList
import net.minecraft.core.HolderLookup
import net.minecraft.core.NonNullList
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.codec.StreamCodec
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.CraftingInput
import net.minecraft.world.item.crafting.CraftingRecipe
import net.minecraft.world.item.crafting.Ingredient
import net.minecraft.world.item.crafting.Recipe
import net.minecraft.world.item.crafting.RecipeSerializer
import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.level.Level
class CookingPotShapelessRecipe(
private val group: String,
private val category: CookingPotBookCategory,
private val result: ItemStack,
private val ingredients: NonNullList<Ingredient>
) : Recipe<CraftingInput>, CookingPotRecipeBase {
override fun getSerializer(): RecipeSerializer<*> {
return CobblemonRecipeSerializers.COOKING_POT_SHAPELESS
}
override fun getType(): RecipeType<CookingPotShapelessRecipe> = CobblemonRecipeTypes.COOKING_POT_SHAPELESS
override fun getGroup(): String {
return group
}
fun category(): CookingPotBookCategory {
return category
}
override fun getResultItem(registries: HolderLookup.Provider): ItemStack {
return result
}
override fun getIngredients(): NonNullList<Ingredient> {
return ingredients
}
override fun matches(input: CraftingInput, level: Level): Boolean {
//println("Debug: Starting matches check for CookingPotShapelessRecipe")
// Debug input crafting grid
for (i in 0 until input.size()) {
val stack = input.getItem(i)
//println("Debug: Slot $i contains item ${stack.item} with count ${stack.count}")
}
// Check ingredient count
if (input.ingredientCount() != ingredients.size) {
//println("Debug: Ingredient count mismatch. Expected ${ingredients.size}, but got ${input.ingredientCount()}")
return false
}
// Match ingredients in any order
val matchedIngredients = mutableListOf<Ingredient>()
for (item in input.items()) {
if (item.isEmpty) continue
// Find matching ingredient
val matchingIngredient = ingredients.find { it.test(item) && it !in matchedIngredients }
if (matchingIngredient != null) {
matchedIngredients.add(matchingIngredient)
//println("Debug: Matched item ${item.item} with ingredient $matchingIngredient")
} else {
//println("Debug: No matching ingredient found for item ${item.item}")
return false
}
}
val matches = matchedIngredients.size == ingredients.size
//println("Debug: Final matches result: $matches")
return matches
}
override fun assemble(input: CraftingInput, registries: HolderLookup.Provider): ItemStack {
return result.copy()
}
override fun canCraftInDimensions(width: Int, height: Int): Boolean {
return width * height >= ingredients.size
}
class Serializer : RecipeSerializer<CookingPotShapelessRecipe> {
companion object {
private val CODEC: MapCodec<CookingPotShapelessRecipe> = RecordCodecBuilder.mapCodec { instance ->
instance.group(
Codec.STRING.optionalFieldOf("group", "").forGetter { it.group },
CookingPotBookCategory.CODEC.fieldOf("category").orElse(CookingPotBookCategory.MISC).forGetter { it.category },
ItemStack.STRICT_CODEC.fieldOf("result").forGetter { it.result },
Ingredient.CODEC_NONEMPTY.listOf().fieldOf("ingredients").flatXmap({ list ->
val ingredients = list.filter { !it.isEmpty }.toTypedArray()
when {
ingredients.isEmpty() -> DataResult.error { "No ingredients for shapeless recipe" }
ingredients.size > 9 -> DataResult.error { "Too many ingredients for shapeless recipe" }
else -> DataResult.success(NonNullList.of(Ingredient.EMPTY, *ingredients))
}
}, { DataResult.success(it) }).forGetter { it.ingredients }
).apply(instance, ::CookingPotShapelessRecipe)
}
val STREAM_CODEC: StreamCodec<RegistryFriendlyByteBuf, CookingPotShapelessRecipe> =
StreamCodec.of(::toNetwork, ::fromNetwork)
private fun fromNetwork(buffer: RegistryFriendlyByteBuf): CookingPotShapelessRecipe {
val group = buffer.readUtf()
val category = buffer.readEnum(CookingPotBookCategory::class.java)
val size = buffer.readVarInt()
val ingredients = NonNullList.withSize(size, Ingredient.EMPTY)
ingredients.replaceAll { Ingredient.CONTENTS_STREAM_CODEC.decode(buffer) }
val result = ItemStack.STREAM_CODEC.decode(buffer)
return CookingPotShapelessRecipe(group, category, result, ingredients)
}
private fun toNetwork(buffer: RegistryFriendlyByteBuf, recipe: CookingPotShapelessRecipe) {
buffer.writeUtf(recipe.group)
buffer.writeEnum(recipe.category)
buffer.writeVarInt(recipe.ingredients.size)
recipe.ingredients.forEach { ingredient ->
Ingredient.CONTENTS_STREAM_CODEC.encode(buffer, ingredient)
}
ItemStack.STREAM_CODEC.encode(buffer, recipe.result)
}
}
override fun codec(): MapCodec<CookingPotShapelessRecipe> {
return CODEC
}
override fun streamCodec(): StreamCodec<RegistryFriendlyByteBuf, CookingPotShapelessRecipe> {
return STREAM_CODEC
}
}
}
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.cookingpot
import com.cobblemon.mod.common.api.cooking.Seasonings
import net.minecraft.world.Container
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.ItemStack
class SeasoningSlot(container: Container, slot: Int, x: Int, y: Int) : Slot(container, slot, x, y) {
override fun mayPlace(stack: ItemStack): Boolean {
return Seasonings.isSeasoning(stack) && super.mayPlace(stack)
}
}
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.cooking
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.block.CampfireBlock
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.IS_LID_OPEN_INDEX
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotMenu
import com.cobblemon.mod.common.net.messages.client.cooking.ToggleCookingPotLidPacket
import com.cobblemon.mod.common.util.playSoundServer
import com.cobblemon.mod.common.util.raycast
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.level.ClipContext
object ToggleCookingPotLidHandler : ServerNetworkPacketHandler<ToggleCookingPotLidPacket> {
override fun handle(
packet: ToggleCookingPotLidPacket,
server: MinecraftServer,
player: ServerPlayer
) {
if (player.containerMenu !is CookingPotMenu) {
Cobblemon.LOGGER.debug("Player {} interacted with invalid menu {}", player, player.containerMenu);
return
}
val menu = player.containerMenu as? CookingPotMenu ?: return
val isLidOpen = if (packet.value) 1 else 0
menu.containerData.set(IS_LID_OPEN_INDEX, isLidOpen)
val raycastBlockPos = player.raycast(player.blockInteractionRange().toFloat() + 1F, ClipContext.Fluid.ANY).blockPos
if (player.level().getBlockState(raycastBlockPos).block is CampfireBlock) {
player.level().playSoundServer(
position = raycastBlockPos.center,
sound = if (isLidOpen == 1) CobblemonSounds.CAMPFIRE_POT_OPEN else CobblemonSounds.CAMPFIRE_POT_CLOSE
)
}
}
}
@@ -36,6 +36,7 @@ import net.minecraft.world.phys.Vec3
import kotlin.random.Random
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.level.Level
import org.joml.Vector4f
/**
* An instance of a bedrock particle effect.
@@ -57,6 +58,7 @@ class ParticleStorm(
val sourceVisible: () -> Boolean = { true },
val targetPos: (() -> Vec3)? = null,
val onDespawn: () -> Unit = {},
val getParticleColor: () -> Vector4f? = { null },
val runtime: MoLangRuntime = MoLangRuntime(),
val entity: Entity? = null,
): NoRenderParticle(world, emitterSpaceMatrix.getOrigin().x, emitterSpaceMatrix.getOrigin().y, emitterSpaceMatrix.getOrigin().z) {
@@ -239,7 +241,12 @@ class ParticleStorm(
remove()
}
if (stopped || !sourceVisible()) {
if (!sourceVisible()) {
this.setInvisible()
return
}
if (stopped) {
return
}
@@ -317,4 +324,8 @@ class ParticleStorm(
fun distanceTo(destinationPos: Vec3): Vec3 {
return emitterSpaceMatrix.transformWorldToParticle(Vec3(x, y, z)).subtract(emitterSpaceMatrix.transformWorldToParticle(destinationPos))
}
}
fun setInvisible() {
particles.forEach { particle -> particle.invisible = true }
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.pot
import com.cobblemon.mod.common.api.text.green
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.util.lang
import net.minecraft.network.chat.Component
enum class CookingQuality {
LOW {
override fun getLang(): Component = lang("cooking.cooking_quality.low").red()
},
MEDIUM {
override fun getLang(): Component = lang("cooking.cooking_quality.medium").yellow()
},
HIGH {
override fun getLang(): Component = lang("cooking.cooking_quality.high").green()
};
abstract fun getLang(): Component
}
@@ -130,6 +130,10 @@ class SnowstormParticle(
}
override fun render(vertexConsumer: VertexConsumer, camera: Camera, tickDelta: Float) {
if (invisible) {
return
}
if (Cobblemon.implementation.modAPI != ModAPI.FORGE) {
if (!Minecraft.getInstance().levelRenderer.cullingFrustum.isVisible(boundingBox)) {
return
@@ -199,7 +203,7 @@ class SnowstormParticle(
}
val uvs = storm.effect.particle.uvMode.get(runtime, age / 20.0, lifetime / 20.0, uvDetails)
val colour = storm.effect.particle.tinting.getTint(runtime)
val colour = storm.getParticleColor() ?: storm.effect.particle.tinting.getTint(runtime)
val spriteURange = sprite.u1 - sprite.u0
val spriteVRange = sprite.v1 - sprite.v0
@@ -531,4 +535,4 @@ class SnowstormParticle(
super.remove()
storm.particles.remove(this)
}
}
}
@@ -0,0 +1,280 @@
/*
* 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.block
import com.cobblemon.mod.common.api.cooking.getColor
import com.cobblemon.mod.common.block.entity.CakeBlockEntity
import com.cobblemon.mod.common.block.entity.CakeBlockEntity.Companion.MAX_NUMBER_OF_BITES
import com.cobblemon.mod.common.item.components.CookingComponent
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.math.Axis
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.inventory.InventoryMenu.BLOCK_ATLAS
import org.joml.Vector3d
import kotlin.random.Random
open class CakeBlockEntityRenderer(ctx: BlockEntityRendererProvider.Context) : BlockEntityRenderer<CakeBlockEntity> {
companion object {
const val PIXEL_SHIFT = 1.0 / 16.0
const val WHITE_OPAQUE_COLOR = 0xFFFFFFFF.toInt()
const val ATLAS_START = 0.0f
const val ATLAS_END = 1.0f
const val HEIGHT = 0.5f
const val TOP_LAYER_HEIGHT = 0.5625f
const val BOTTOM_LAYER_HEIGHT = 0.0f
object DefaultCakeLayout : CakeLayout {
override val topLayers: List<CakeTexture> = mutableListOf(
CakeTexture("poke_snack_top_layer2", LayerColor.SECONDARY),
CakeTexture("poke_snack_top_layer3", LayerColor.PRIMARY),
)
override val sideLayers: List<CakeTexture> = mutableListOf(
CakeTexture("poke_snack_side_layer1", LayerColor.TERTIARY),
CakeTexture("poke_snack_side_layer2", LayerColor.SECONDARY),
CakeTexture("poke_snack_side_layer3", LayerColor.PRIMARY),
)
override val bottomLayers: List<CakeTexture> = mutableListOf(
CakeTexture("poke_snack_bottom_layer1", LayerColor.TERTIARY),
)
override val innerLayers: List<CakeTexture> = mutableListOf(
CakeTexture("poke_snack_inner_layer1", LayerColor.TERTIARY),
CakeTexture("poke_snack_inner_layer2", LayerColor.SECONDARY),
CakeTexture("poke_snack_inner_layer3", LayerColor.PRIMARY),
)
}
object HeartCakeLayout : CakeLayout {
override val topLayers: List<CakeTexture> = mutableListOf(
CakeTexture("poke_snack_top_layer4", LayerColor.SECONDARY),
CakeTexture("poke_snack_top_layer5", LayerColor.PRIMARY),
)
override val sideLayers: List<CakeTexture> = mutableListOf(
CakeTexture("poke_snack_side_layer1", LayerColor.TERTIARY),
CakeTexture("poke_snack_side_layer2", LayerColor.SECONDARY),
CakeTexture("poke_snack_side_layer3", LayerColor.PRIMARY),
)
override val bottomLayers: List<CakeTexture> = mutableListOf(
CakeTexture("poke_snack_bottom_layer1", LayerColor.TERTIARY),
)
override val innerLayers: List<CakeTexture> = mutableListOf(
CakeTexture("poke_snack_inner_layer1", LayerColor.TERTIARY),
CakeTexture("poke_snack_inner_layer2", LayerColor.SECONDARY),
CakeTexture("poke_snack_inner_layer3", LayerColor.PRIMARY),
)
}
val CAKE_LAYOUTS = listOf(DefaultCakeLayout, HeartCakeLayout)
fun loadTexture(textureName: String): TextureAtlasSprite {
return Minecraft.getInstance().getTextureAtlas(BLOCK_ATLAS).apply(ResourceLocation("cobblemon", textureName))
}
}
interface CakeLayout {
val topLayers: List<CakeTexture>
val sideLayers: List<CakeTexture>
val bottomLayers: List<CakeTexture>
val innerLayers: List<CakeTexture>
}
class CakeTexture(textureName: String, val color: LayerColor) {
val texture = loadTexture("block/${textureName}")
fun getColor(primaryColor: Int, secondaryColor: Int, tertiaryColor: Int): Int {
return when (color) {
LayerColor.PRIMARY -> primaryColor
LayerColor.SECONDARY -> secondaryColor
LayerColor.TERTIARY -> tertiaryColor
}
}
}
enum class LayerColor {
PRIMARY, SECONDARY, TERTIARY
}
override fun render(
blockEntity: CakeBlockEntity,
partialTick: Float,
poseStack: PoseStack,
bufferSource: MultiBufferSource,
packedLight: Int,
packedOverlay: Int
) {
val cookingComponent = blockEntity.cookingComponent
renderCake(cookingComponent, poseStack, bufferSource, packedLight, blockEntity.bites)
}
fun renderCake(
cookingComponent: CookingComponent?,
poseStack: PoseStack,
multiBufferSource: MultiBufferSource,
light: Int,
bites: Int,
) {
val seedString = "${cookingComponent?.seasoning1?.color}${cookingComponent?.seasoning2?.color}${cookingComponent?.seasoning3?.color}"
val random = Random(seedString.hashCode())
val cakeLayout: CakeLayout = CAKE_LAYOUTS[random.nextInt(CAKE_LAYOUTS.size)]
val maxBites = MAX_NUMBER_OF_BITES + 1f
val cakeIsFull = bites.toFloat() == 0f
val percentageToRender = if (cakeIsFull) 1.0f else PIXEL_SHIFT.toFloat() + (PIXEL_SHIFT.toFloat() * 2f * (maxBites - bites).toFloat())
val clippedWidth = (ATLAS_END - ATLAS_START) * percentageToRender
val topLayer = cakeLayout.topLayers.first().texture
val uClipped = topLayer.u0 + (topLayer.u1 - topLayer.u0) * percentageToRender
val vertexConsumer = multiBufferSource.getBuffer(RenderType.cutout())
val primaryColor = cookingComponent?.seasoning1?.color?.let { getColor(it) } ?: WHITE_OPAQUE_COLOR
val secondaryColor = cookingComponent?.seasoning2?.color?.let { getColor(it) } ?: WHITE_OPAQUE_COLOR
val tertiaryColor = cookingComponent?.seasoning3?.color?.let { getColor(it) } ?: WHITE_OPAQUE_COLOR
poseStack.pushPose()
poseStack.translate(0.5, 0.5, 0.5)
poseStack.mulPose(Axis.YP.rotationDegrees(180f))
poseStack.translate(-0.5, -0.5, -0.5)
poseStack.pushPose()
for (layer in cakeLayout.topLayers) {
val texture = layer.texture
drawQuad(
vertexConsumer, poseStack,
ATLAS_START, TOP_LAYER_HEIGHT, ATLAS_START, clippedWidth, TOP_LAYER_HEIGHT, ATLAS_END,
texture.u0, texture.v0, uClipped, texture.v1,
light, layer.getColor(primaryColor, secondaryColor, tertiaryColor)
)
}
poseStack.popPose()
val translations = arrayOf(
Vector3d(-1.0, 1.0, 0.5 - PIXEL_SHIFT),
Vector3d(0.0, 1.0, 0.5 - PIXEL_SHIFT),
Vector3d(0.0, 1.0, -0.5 - PIXEL_SHIFT),
Vector3d(-1.0, 1.0, -0.5 - PIXEL_SHIFT),
)
val rotationsY = arrayOf(
Axis.YP.rotationDegrees(90f),
Axis.YP.rotationDegrees(0f),
Axis.YP.rotationDegrees(-90f),
Axis.YP.rotationDegrees(-180f)
)
for (i in translations.indices) {
poseStack.pushPose()
poseStack.mulPose(rotationsY[i])
poseStack.translate(translations[i].x, translations[i].y, translations[i].z)
poseStack.mulPose(Axis.XP.rotationDegrees(90f))
if (i == 0 && !cakeIsFull) {
poseStack.translate(0.0, -bites * PIXEL_SHIFT * 2, 0.0)
for (layer in cakeLayout.innerLayers) {
val texture = layer.texture
drawQuad(
vertexConsumer, poseStack,
ATLAS_START, HEIGHT, ATLAS_START, ATLAS_END, HEIGHT, ATLAS_END,
texture.u0, texture.v0, texture.u1, texture.v1,
light, layer.getColor(primaryColor, secondaryColor, tertiaryColor)
)
}
} else if (i == 0 || i == 1) {
for (layer in cakeLayout.sideLayers) {
val texture = layer.texture
drawQuad(
vertexConsumer, poseStack,
ATLAS_START, HEIGHT, ATLAS_START, clippedWidth, HEIGHT, ATLAS_END,
texture.u0, texture.v0, uClipped, texture.v1,
light, layer.getColor(primaryColor, secondaryColor, tertiaryColor)
)
}
} else if (i == 2) {
for (layer in cakeLayout.sideLayers) {
val texture = layer.texture
drawQuad(
vertexConsumer, poseStack,
ATLAS_START, HEIGHT, ATLAS_START, ATLAS_END, HEIGHT, ATLAS_END,
texture.u0, texture.v0, texture.u1, texture.v1,
light, layer.getColor(primaryColor, secondaryColor, tertiaryColor)
)
}
} else if (i == 3) {
val clippedStartX = ATLAS_START + (ATLAS_END - ATLAS_START) * (1 - percentageToRender)
for (layer in cakeLayout.sideLayers) {
val texture = layer.texture
val clippedUStart = texture.u0 + (texture.u1 - texture.u0) * (1 - percentageToRender)
drawQuad(
vertexConsumer, poseStack,
clippedStartX, HEIGHT, ATLAS_START, ATLAS_END, HEIGHT, ATLAS_END,
clippedUStart, texture.v0, texture.u1, texture.v1,
light, layer.getColor(primaryColor, secondaryColor, tertiaryColor)
)
}
}
poseStack.popPose()
}
poseStack.pushPose()
poseStack.translate(0.5, 0.0, 0.5)
poseStack.mulPose(Axis.XP.rotationDegrees(180f))
poseStack.translate(-0.5, 0.0, -0.5)
for (layer in cakeLayout.bottomLayers) {
val texture = layer.texture
drawQuad(
vertexConsumer, poseStack,
ATLAS_START, BOTTOM_LAYER_HEIGHT, ATLAS_START, clippedWidth, BOTTOM_LAYER_HEIGHT, ATLAS_END,
texture.u0, texture.v0, uClipped, texture.v1,
light, layer.getColor(primaryColor, secondaryColor, tertiaryColor)
)
}
poseStack.popPose()
poseStack.popPose()
}
private fun drawQuad(
builder: VertexConsumer,
poseStack: PoseStack,
x0: Float, y0: Float, z0: Float,
x1: Float, y1: Float, z1: Float,
u0: Float, v0: Float, u1: Float, v1: Float,
packedLight: Int, color: Int,
) {
drawVertex(builder, poseStack, x0, y0, z0, u0, v0, packedLight, color)
drawVertex(builder, poseStack, x0, y1, z1, u0, v1, packedLight, color)
drawVertex(builder, poseStack, x1, y1, z1, u1, v1, packedLight, color)
drawVertex(builder, poseStack, x1, y0, z0, u1, v0, packedLight, color)
}
private fun drawVertex(
builder: VertexConsumer,
poseStack: PoseStack,
x: Float, y: Float, z: Float,
u: Float, v: Float,
packedLight: Int, color: Int
) {
builder.addVertex(poseStack.last().pose(), x, y, z)
.setColor(color)
.setUv(u, v)
.setLight(packedLight)
.setNormal(0f, 1f, 0f)
}
}
@@ -0,0 +1,220 @@
/*
* 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.block
import com.cobblemon.mod.common.block.CampfireBlock
import com.cobblemon.mod.common.block.CampfirePotBlock
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity
import com.cobblemon.mod.common.block.entity.CampfireBlockEntity.Companion.IS_LID_OPEN_INDEX
import com.cobblemon.mod.common.item.CampfirePotItem
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.math.Axis
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.core.Direction
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.inventory.InventoryMenu
import net.minecraft.world.item.ItemDisplayContext
import net.minecraft.world.level.block.HorizontalDirectionalBlock.FACING
import org.joml.Vector3f
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
class CampfireBlockEntityRenderer(ctx: BlockEntityRendererProvider.Context) : BlockEntityRenderer<CampfireBlockEntity> {
companion object {
const val BROTH_START = 0F
const val BROTH_END = 1F
const val BROTH_HEIGHT = 0.3F
val BROTH_BASIC_SPRITE: TextureAtlasSprite = Minecraft.getInstance().getTextureAtlas(InventoryMenu.BLOCK_ATLAS).apply(ResourceLocation("cobblemon", "block/campfire_pot_broth_basic"))
val BROTH_SPRITE: TextureAtlasSprite = Minecraft.getInstance().getTextureAtlas(InventoryMenu.BLOCK_ATLAS).apply(ResourceLocation("cobblemon", "block/campfire_pot_broth"))
const val CIRCLE_RADIUS = 0.4F
const val ROTATION_SPEED = 1.5F
const val JUMP_AMPLITUDE = 0.025F
const val JUMP_SPEED = 0.1F
}
override fun render(
blockEntity: CampfireBlockEntity,
tickDelta: Float,
poseStack: PoseStack,
multiBufferSource: MultiBufferSource,
light: Int,
overlay: Int
) {
val campfirePotItem = blockEntity.getPotItem()?.item as CampfirePotItem?
if (campfirePotItem == null) return
poseStack.pushPose()
val facing = blockEntity.blockState.getValue(FACING)
val rotationAngle = when (facing) {
Direction.NORTH -> 180F
Direction.SOUTH -> 0F
Direction.WEST -> 90F
Direction.EAST -> -90F
else -> 0f
}
poseStack.translate(0.5, 0.5, 0.5)
poseStack.mulPose(Axis.YP.rotationDegrees(rotationAngle))
poseStack.translate(-0.5, -0.5, -0.5)
renderPot(campfirePotItem, blockEntity, tickDelta, poseStack, multiBufferSource, light, overlay)
if (!blockEntity.getSeasonings().isEmpty() || !blockEntity.getIngredients().isEmpty()) renderBroth(blockEntity, tickDelta, poseStack, multiBufferSource, light, overlay)
renderSeasonings(blockEntity, tickDelta, poseStack, multiBufferSource, light, overlay)
poseStack.popPose()
}
private fun renderPot(
campfirePotItem: CampfirePotItem,
blockEntity: CampfireBlockEntity,
tickDelta: Float,
poseStack: PoseStack,
multiBufferSource: MultiBufferSource,
light: Int,
overlay: Int
) {
val isLidOpen = blockEntity.dataAccess.get(IS_LID_OPEN_INDEX) == 1
val yRot = (blockEntity.blockState.getValue(CampfireBlock.ITEM_DIRECTION).opposite.toYRot() + blockEntity.blockState.getValue(FACING).toYRot()) % 360
poseStack.pushPose()
poseStack.translate(0.0, 0.4375, 0.0)
Minecraft.getInstance().blockRenderer.renderSingleBlock(
campfirePotItem.block.defaultBlockState()
.setValue(CampfirePotBlock.OPEN, isLidOpen)
.setValue(FACING, Direction.fromYRot(yRot.toDouble())),
poseStack,
multiBufferSource,
light,
overlay
)
poseStack.popPose()
}
private fun renderBroth(
blockEntity: CampfireBlockEntity,
tickDelta: Float,
poseStack: PoseStack,
multiBufferSource: MultiBufferSource,
light: Int,
overlay: Int
) {
poseStack.pushPose()
val vertexConsumer = multiBufferSource.getBuffer(RenderType.cutout())
poseStack.translate(0.0, 0.4, 0.0)
poseStack.scale(1.0f, 1.0F, 1.0F)
val brothTextureSprite = if (!blockEntity.getSeasonings().isEmpty()) BROTH_SPRITE else BROTH_BASIC_SPRITE
drawQuad(
vertexConsumer, poseStack,
BROTH_START, BROTH_HEIGHT, BROTH_START, BROTH_END, BROTH_HEIGHT, BROTH_END,
brothTextureSprite.u0, brothTextureSprite.v0, brothTextureSprite.u1, brothTextureSprite.v1,
light,
blockEntity.brothColor
)
poseStack.popPose()
}
private fun renderSeasonings(
blockEntity: CampfireBlockEntity,
tickDelta: Float,
poseStack: PoseStack,
multiBufferSource: MultiBufferSource,
light: Int,
overlay: Int
) {
val seasonings = blockEntity.getSeasonings()
val gameTime = (blockEntity.level?.gameTime ?: 0) + tickDelta
val rotationAngle = (gameTime * ROTATION_SPEED) % 360
seasonings.forEachIndexed { index, seasoning ->
poseStack.pushPose()
poseStack.scale(0.5F, 0.5F, 0.5F)
val angleOffset = index * (360f / seasonings.size)
val angleInRadians = Math.toRadians((rotationAngle + angleOffset).toDouble())
val xOffset = cos(angleInRadians.toDouble()).toFloat() * CIRCLE_RADIUS
val zOffset = sin(angleInRadians.toDouble()).toFloat() * CIRCLE_RADIUS
val jumpOffset = sin(gameTime * JUMP_SPEED + index * 2) * JUMP_AMPLITUDE
poseStack.translate(1F + xOffset, 1.24F + jumpOffset, 1F + zOffset)
val lookAtDirection = Vector3f(1f + xOffset - 1F, 0F, 1F + zOffset - 1F)
poseStack.mulPose(Axis.YP.rotationDegrees((-Math.toDegrees(
atan2(
lookAtDirection.z().toDouble(),
lookAtDirection.x().toDouble()
)
)).toFloat() + 90))
poseStack.pushPose()
poseStack.mulPose(Axis.XP.rotationDegrees(22.5F))
Minecraft.getInstance().itemRenderer.renderStatic(
seasoning,
ItemDisplayContext.GROUND,
light,
OverlayTexture.NO_OVERLAY,
poseStack,
multiBufferSource,
blockEntity.level,
0
)
poseStack.popPose()
poseStack.popPose()
}
}
private fun drawQuad(
builder: VertexConsumer,
poseStack: PoseStack,
x0: Float, y0: Float, z0: Float,
x1: Float, y1: Float, z1: Float,
u0: Float, v0: Float, u1: Float, v1: Float,
packedLight: Int, color: Int
) {
drawVertex(builder, poseStack, x0, y0, z0, u0, v0, packedLight, color)
drawVertex(builder, poseStack, x0, y1, z1, u0, v1, packedLight, color)
drawVertex(builder, poseStack, x1, y1, z1, u1, v1, packedLight, color)
drawVertex(builder, poseStack, x1, y0, z0, u1, v0, packedLight, color)
}
private fun drawVertex(
builder: VertexConsumer,
poseStack: PoseStack,
x: Float, y: Float, z: Float,
u: Float, v: Float,
packedLight: Int, color: Int
) {
builder.addVertex(poseStack.last().pose(), x, y, z)
.setColor(color)
.setUv(u, v)
.setLight(packedLight)
.setNormal(0f, 1f, 0f)
}
}
@@ -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.client.render.block
import com.cobblemon.mod.common.block.CandlePokeCakeBlock.Companion.CANDLE_COLOR
import com.cobblemon.mod.common.block.CandlePokeCakeBlock.Companion.getCandleByMapColorId
import com.cobblemon.mod.common.block.entity.CakeBlockEntity
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.world.level.block.state.properties.BlockStateProperties.LIT
class CandlePokeCakeBlockEntityRenderer(
ctx: BlockEntityRendererProvider.Context,
) : CakeBlockEntityRenderer(ctx) {
override fun render(
blockEntity: CakeBlockEntity,
partialTick: Float,
poseStack: PoseStack,
bufferSource: MultiBufferSource,
packedLight: Int,
packedOverlay: Int
) {
super.render(blockEntity, partialTick, poseStack, bufferSource, packedLight, packedOverlay)
poseStack.pushPose()
poseStack.translate(0.0, TOP_LAYER_HEIGHT.toDouble(), 0.0)
val candleBlock = getCandleByMapColorId(blockEntity.blockState.getValue(CANDLE_COLOR))
Minecraft.getInstance().blockRenderer.renderSingleBlock(
candleBlock.defaultBlockState().setValue(LIT, blockEntity.blockState.getValue(LIT)),
poseStack,
bufferSource,
packedLight,
packedOverlay
)
poseStack.popPose()
}
}
@@ -36,7 +36,7 @@ class LecternBlockEntityRenderer(ctx: BlockEntityRendererProvider.Context) : Blo
if (blockEntity !is LecternBlockEntity) return
if (!blockEntity.isEmpty()) {
val blockState = if (blockEntity.level != null) blockEntity.blockState
else (CobblemonBlocks.LECTERN.defaultBlockState().setValue(HorizontalDirectionalBlock.FACING, Direction.SOUTH) as BlockState)
else (CobblemonBlocks.CAMPFIRE.defaultBlockState().setValue(HorizontalDirectionalBlock.FACING, Direction.SOUTH) as BlockState)
val yRot = blockState.getValue(HorizontalDirectionalBlock.FACING).toYRot()
poseStack.pushPose()
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.render.color
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.cooking.getColorMixFromCookingComponent
import com.cobblemon.mod.common.client.pot.CookingQuality
import net.minecraft.ChatFormatting
import net.minecraft.client.color.item.ItemColor
import net.minecraft.util.FastColor
import net.minecraft.world.item.ItemStack
object AprijuiceItemColorProvider : ItemColor {
private const val LEAF_INDEX = 1
private const val JUICE_INDEX = 2
override fun getColor(stack: ItemStack, layer: Int): Int {
if (layer == 0) return -1
val cookingComponent = stack.get(CobblemonItemComponents.COOKING_COMPONENT) ?: return -1
if (layer == LEAF_INDEX) {
val quality = cookingComponent.getCookingQuality()
val color = when (quality) {
CookingQuality.LOW -> ChatFormatting.RED.color
CookingQuality.MEDIUM -> ChatFormatting.YELLOW.color
CookingQuality.HIGH -> ChatFormatting.GREEN.color
}
color?.let { return FastColor.ARGB32.opaque(color) }
}
if (layer == JUICE_INDEX) {
val colorMix = getColorMixFromCookingComponent(cookingComponent)
if (colorMix != null) return colorMix
}
return -1
}
}
@@ -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.client.render.color
import com.cobblemon.mod.common.CobblemonItemComponents
import net.minecraft.client.color.item.ItemColor
import net.minecraft.world.item.ItemStack
import com.cobblemon.mod.common.api.cooking.getColor
object PokeBaitItemColorProvider : ItemColor {
override fun getColor(stack: ItemStack, layer: Int): Int {
val cookingComponent = stack.get(CobblemonItemComponents.COOKING_COMPONENT) ?: return -1
val primaryColor = cookingComponent.seasoning1.color
val secondaryColor = cookingComponent.seasoning2.color
val tertiaryColor = cookingComponent.seasoning3.color
val color = when (layer) {
0 -> getColor(primaryColor)
1 -> getColor(secondaryColor)
2 -> getColor(tertiaryColor)
else -> -1
} ?: -1
return color
}
}
@@ -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.client.tooltips
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.item.AprijuiceItem
import com.cobblemon.mod.common.util.lang
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
object AprijuiceTooltipGenerator : TooltipGenerator() {
override fun generateCategoryTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
if (stack.item !is AprijuiceItem) return null
return mutableListOf(seasoningHeader)
}
override fun generateAdditionalTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
if (stack.item !is AprijuiceItem) return null
val cookingComponent = stack.get(CobblemonItemComponents.COOKING_COMPONENT) ?: return null
val resultLines = mutableListOf<Component>()
val quality = cookingComponent.getCookingQuality()
resultLines.add(lang("cooking.cooking_quality", quality.getLang()))
val flavors: Map<String, Int> = cookingComponent.getFlavorsSum()
resultLines.addAll(generateAdditionalFlavorTooltip(flavors))
return resultLines
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.client.tooltips
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.fishing.FishingBaits
import com.cobblemon.mod.common.api.pokemon.egg.EggGroup
import com.cobblemon.mod.common.api.text.*
@@ -36,14 +37,25 @@ object FishingBaitTooltipGenerator : TooltipGenerator() {
override fun generateAdditionalTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
val resultLines = mutableListOf<Component>()
val bait =
(if (stack.item is PokerodItem) PokerodItem.getBaitOnRod(stack) else FishingBaits.getFromBaitItemStack(stack))
?: return null
// Determine the FishingBait or combined effects from poke_bait
val baitEffects = when {
(stack.item.asItem()?.toString() == "cobblemon:poke_bait" || stack.item.asItem()?.toString() == "cobblemon:lure_cake") -> {
val cookingComponent = stack.get(CobblemonItemComponents.COOKING_COMPONENT) ?: return null
listOf(cookingComponent.bait1, cookingComponent.bait2, cookingComponent.bait3)
.flatMap { it.effects }
}
stack.item is PokerodItem -> PokerodItem.getBaitOnRod(stack)?.effects
else -> FishingBaits.getFromBaitItemStack(stack)?.effects
} ?: return null
if (baitEffects.isEmpty()) return null
resultLines.addLast(this.fishingBaitHeader)
val formatter = DecimalFormat("0.##")
bait.effects.forEach { effect ->
baitEffects.forEach { effect ->
val effectType = effect.type.path.toString()
val effectSubcategory = effect.subcategory?.path
val effectChance = effect.chance * 100
@@ -51,6 +63,7 @@ object FishingBaitTooltipGenerator : TooltipGenerator() {
"bite_time" -> (effect.value * 100).toInt()
else -> effect.value.toInt()
}
val subcategoryString: Component = if (effectSubcategory != null) {
when (effectType) {
"nature", "ev", "iv" -> com.cobblemon.mod.common.api.pokemon.stats.Stats.getStat(
@@ -74,7 +87,7 @@ object FishingBaitTooltipGenerator : TooltipGenerator() {
} ?: Component.literal("cursed").obfuscate()
} else Component.literal("cursed").obfuscate()
// handle reformatting of shiny chance effectChance
// Adjust shiny chance effectValue
if (effectType == "shiny_reroll") {
effectValue++
}
@@ -91,4 +104,5 @@ object FishingBaitTooltipGenerator : TooltipGenerator() {
return resultLines
}
}
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.tooltips
import com.cobblemon.mod.common.api.text.*
import com.cobblemon.mod.common.util.lang
import net.minecraft.network.chat.Component
val seasoningHeader by lazy { lang("item_class.seasoning").blue() }
private val flavorSubHeader by lazy { lang("seasoning_flavor_header").blue() }
fun generateAdditionalFlavorTooltip(flavors: Map<String, Int>): MutableList<Component> {
val resultLines = mutableListOf<Component>()
resultLines.add(flavorSubHeader)
val combinedFlavorsLine = Component.literal("")
flavors.forEach { (flavor, value) ->
val flavorLangKey = when (flavor.lowercase()) {
"spicy" -> lang("seasoning_flavor.spicy").red()
"dry" -> lang("seasoning_flavor.dry").darkAqua()
"sweet" -> lang("seasoning_flavor.sweet").lightPurple()
"bitter" -> lang("seasoning_flavor.bitter").green()
"sour" -> lang("seasoning_flavor.sour").yellow()
else -> lang("seasoning_flavor.unknown").gray() // Default for unknown flavors
}
if (combinedFlavorsLine.string.isNotEmpty()) {
combinedFlavorsLine.append(" ")
}
combinedFlavorsLine.append(flavorLangKey).append(" $value")
}
resultLines.add(combinedFlavorsLine)
return resultLines
}
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.tooltips
import com.cobblemon.mod.common.api.cooking.Seasonings
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
object SeasoningTooltipGenerator : TooltipGenerator() {
override fun generateCategoryTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
if (!Seasonings.isSeasoning(stack)) return null
return mutableListOf(seasoningHeader)
}
override fun generateAdditionalTooltip(stack: ItemStack, lines: MutableList<Component>): MutableList<Component>? {
val flavors: Map<String, Int>? = Seasonings.getFromItemStack(stack)?.flavors
flavors?.let {
return generateAdditionalFlavorTooltip(flavors)
}
return null
}
}
@@ -15,6 +15,8 @@ import com.cobblemon.mod.common.CobblemonBrainConfigs
import com.cobblemon.mod.common.CobblemonMechanics
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.berry.Berries
import com.cobblemon.mod.common.api.cooking.Seasoning
import com.cobblemon.mod.common.api.cooking.Seasonings
import com.cobblemon.mod.common.api.data.DataProvider
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.pokedex.Dexes
@@ -97,6 +99,7 @@ object CobblemonDataProvider : DataProvider {
CobblemonSpawnPools.load()
this.register(PokeRods)
this.register(Berries)
this.register(Seasonings)
this.register(FishingBaits)
FishingBait.Effects.setupEffects()
@@ -21,14 +21,16 @@ import net.minecraft.world.level.block.state.properties.WoodType
*/
enum class CobblemonBoatType(val mountedOffset: Float) : StringRepresentable {
APRICORN(-0.1f);
APRICORN(-0.1f),
SACCHARINE(-.01f);
/**
* The base [Item] form of this [CobblemonBoatType].
*/
val boatItem: Item
get() = when (this) {
APRICORN -> CobblemonItems.APRICORN_BOAT
APRICORN -> CobblemonItems.APRICORN_BOAT
SACCHARINE -> CobblemonItems.SACCHARINE_BOAT
}
/**
@@ -36,6 +38,7 @@ enum class CobblemonBoatType(val mountedOffset: Float) : StringRepresentable {
*/
val chestBoatItem: Item get() = when (this) {
APRICORN -> CobblemonItems.APRICORN_CHEST_BOAT
SACCHARINE -> CobblemonItems.SACCHARINE_CHEST_BOAT
}
/**
@@ -43,6 +46,7 @@ enum class CobblemonBoatType(val mountedOffset: Float) : StringRepresentable {
*/
val baseBlock: Block get() = when (this) {
APRICORN -> CobblemonBlocks.APRICORN_PLANKS
SACCHARINE -> CobblemonBlocks.SACCHARINE_PLANKS
}
/**
@@ -50,6 +54,7 @@ enum class CobblemonBoatType(val mountedOffset: Float) : StringRepresentable {
*/
val woodType: WoodType get() = when (this) {
APRICORN -> CobblemonBlocks.APRICORN_WOOD_TYPE
SACCHARINE -> CobblemonBlocks.SACCHARINE_WOOD_TYPE
}
@@ -30,6 +30,7 @@ import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.client.sound.EntitySoundTracker
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.interactive.PokerodItem
import com.cobblemon.mod.common.item.interactive.PokerodItem.Companion.getCookingComponentOnRod
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
@@ -317,7 +318,7 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
serverWorld.sendParticles(ParticleTypes.FISHING, this.x, m, this.z, (1.0f + this.bbWidth * 20.0f).toInt(), this.bbWidth.toDouble(), 0.0, this.bbWidth.toDouble(), 0.2)
// check for chance to catch pokemon based on the bait
if (Mth.nextInt(random, 0, 100) < getPokemonSpawnChance(bobberBait)) {
if (Mth.nextInt(random, 0, 100) < getPokemonSpawnChance(this.rodStack ?: bobberBait)) {
this.typeCaught = TypeCaught.POKEMON
val buckets = Cobblemon.bestSpawner.config.buckets
@@ -388,8 +389,8 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
this.waitCountdown = 1
else {
// check for the bait on the hook and see if the waitCountdown is reduced
if (checkReduceBiteTime(bobberBait))
this.waitCountdown = alterBiteTimeAttempt(this.waitCountdown, this.bobberBait)
if (checkReduceBiteTime(this.rodStack ?: bobberBait))
this.waitCountdown = alterBiteTimeAttempt(this.waitCountdown, this.rodStack ?: bobberBait)
}
}
}
@@ -755,19 +756,19 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
// function to return true of false if the given bait affects time to expect a bite
fun checkReduceBiteTime(stack: ItemStack): Boolean {
val bait = FishingBaits.getFromBaitItemStack(stack) ?: return false
val bait = PokerodItem.getBaitOnRod(stack) ?: return false
return bait.effects.any { it.type == FishingBait.Effects.BITE_TIME }
}
// function to return true of false if the given bait to make it so a Pokemon is always reeled in
fun checkPokemonFishRate(stack: ItemStack): Boolean {
val bait = FishingBaits.getFromBaitItemStack(stack) ?: return false
val bait = PokerodItem.getBaitOnRod(stack) ?: return false
return bait.effects.any { it.type == FishingBait.Effects.POKEMON_CHANCE }
}
// check if the bite time is reduced based on the bait bonus
fun alterBiteTimeAttempt(waitCountdown: Int, stack: ItemStack): Int {
val bait = FishingBaits.getFromBaitItemStack(stack) ?: return waitCountdown
val bait = PokerodItem.getBaitOnRod(stack) ?: return waitCountdown
val effect = bait.effects.filter { it.type == FishingBait.Effects.BITE_TIME }.random()
if (!checkBaitSuccessRate(effect.chance)) return waitCountdown
return if (waitCountdown - waitCountdown * (effect.value) <= 0)
@@ -778,7 +779,7 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
// check the chance of a pokemon to spawn and if it is affected by bait
fun getPokemonSpawnChance(stack: ItemStack): Int {
val bait = FishingBaits.getFromBaitItemStack(stack) ?: return this.pokemonSpawnChance
val bait = PokerodItem.getBaitOnRod(stack) ?: return this.pokemonSpawnChance
val effectList = bait.effects.filter { it.type == FishingBait.Effects.POKEMON_CHANCE }
if (effectList.isEmpty()) return this.pokemonSpawnChance
val effect = effectList.random()
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.integration.jei
import com.cobblemon.mod.common.integration.jei.berry.BerryMutationProvider
import com.cobblemon.mod.common.integration.jei.berry.BerryRecipeCategory
import com.cobblemon.mod.common.util.cobblemonResource
import mezz.jei.api.IModPlugin
import mezz.jei.api.JeiPlugin
@@ -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.item
import com.cobblemon.mod.common.client.cooking.AprijuiceTypes
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.UseAnim
class AprijuiceItem(val type: AprijuiceTypes): CobblemonItem(Properties().stacksTo(16)) {
}
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.item
import com.cobblemon.mod.common.block.BugwortBlock
import com.cobblemon.mod.common.block.SaccharineSaplingBlock
import net.minecraft.world.item.ItemNameBlockItem
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.block.state.BlockState
class BugwortItem(block: BugwortBlock) : ItemNameBlockItem(block, Properties()) {
override fun getPlacementState(context: BlockPlaceContext): BlockState? {
return super.getPlacementState(context)
}
}
@@ -0,0 +1,84 @@
/*
* 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
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.block.CampfireBlock.Companion.ITEM_DIRECTION
import com.cobblemon.mod.common.block.CampfireBlock.Companion.SOUL
import net.minecraft.core.Direction
import net.minecraft.sounds.SoundSource
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.context.UseOnContext
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.CampfireBlock
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.entity.CampfireBlockEntity
class CampfirePotItem(block: Block): BlockItem(block, Properties()) {
override fun useOn(context: UseOnContext): InteractionResult {
val world = context.level
val blockPos = context.getClickedPos()
val blockState = world.getBlockState(blockPos)
val blockEntity = world.getBlockEntity(blockPos)
val player = context.player
if (player != null && blockState.block is CampfireBlock && blockEntity is CampfireBlockEntity) {
if (blockState.getValue(CampfireBlock.LIT)) {
// Remove the existing block entity and replace with the custom CampfireBlockEntity
val facing = blockState.getValue(HorizontalDirectionalBlock.FACING)
val itemFacing = Direction.fromYRot(player.getYHeadRot().toDouble())
val isSoul = blockState.block.asItem().toString() == "minecraft:soul_campfire"
// Drop items stored in campfire
for (item in blockEntity.items) {
if (!item.isEmpty) {
val itemEntity = ItemEntity(
world,
blockPos.x + 0.5,
(blockPos.y + 1).toDouble(),
blockPos.z + 0.5, item
)
itemEntity.setDefaultPickUpDelay()
world.addFreshEntity(itemEntity)
}
}
blockEntity.setRemoved()
val newBlockState = CobblemonBlocks.CAMPFIRE.defaultBlockState()
.setValue(HorizontalDirectionalBlock.FACING, facing)
.setValue(ITEM_DIRECTION, itemFacing)
.setValue(SOUL, isSoul)
world.setBlockAndUpdate(blockPos, newBlockState)
// Retrieve the new block entity and set the PotItem
val newBlockEntity = world.getBlockEntity(blockPos)
if (newBlockEntity is com.cobblemon.mod.common.block.entity.CampfireBlockEntity) {
if (newBlockEntity.getPotItem() == null || newBlockEntity.getPotItem()!!.isEmpty) {
newBlockEntity.setPotItem(ItemStack(this).split(1))
if (!player.isCreative) context.itemInHand.consumeAndReturn(1, player)
world.playSound(null, blockPos, CobblemonSounds.CAMPFIRE_POT_PLACE_CAMPFIRE, SoundSource.BLOCKS, 0.7F, 1.0F)
return InteractionResult.SUCCESS
}
}
}
// Don't place pot if campfire is not lit
else if (!player.isCrouching) {
return InteractionResult.FAIL
}
}
return super.useOn(context)
}
}
@@ -0,0 +1,147 @@
/*
* 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
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.block.HeartyGrainsBlock
import net.minecraft.core.BlockPos
import net.minecraft.tags.FluidTags
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.InteractionResultHolder
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemNameBlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.PlaceOnWaterBlockItem
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.item.context.UseOnContext
import net.minecraft.world.level.ClipContext
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.state.BlockState
class HeartyGrainsItem(block: HeartyGrainsBlock) : ItemNameBlockItem(block, Properties()) {
init {
Cobblemon.implementation.registerCompostable(this, 0.65F)
}
override fun useOn(context: UseOnContext): InteractionResult {
val world = context.level
val clickedPos = context.clickedPos
val player = context.player ?: return InteractionResult.FAIL
if (world.isClientSide) {
return InteractionResult.SUCCESS
}
val clickedBlockState = world.getBlockState(clickedPos)
val fluidState = world.getFluidState(clickedPos)
val blockAbove = world.getBlockState(clickedPos.above())
val fluidStateAbove = world.getFluidState(clickedPos.above())
val block2Above = world.getBlockState(clickedPos.above(2))
val fluidState2Above = world.getFluidState(clickedPos.above(2))
val blockBelow = world.getBlockState(clickedPos.below())
// 1. Prioritize source water block
if (fluidState.`is`(FluidTags.WATER) && fluidState.isSource) {
// Check if it's 1 block deep water
if (blockAbove.isAir && !blockBelow.fluidState.isSource) {
if (world.setBlock(clickedPos, this.block.defaultBlockState().setValue(HeartyGrainsBlock.WATERLOGGED, true), Block.UPDATE_CLIENTS)) {
context.itemInHand.shrink(1)
return InteractionResult.SUCCESS
}
}
// Fails if not 1 block deep
return InteractionResult.FAIL
}
// 2. Check for farmland or mud
if (clickedBlockState.`is`(Blocks.FARMLAND) || clickedBlockState.`is`(Blocks.MUD) || (fluidStateAbove.`is`(FluidTags.WATER) && fluidStateAbove.isSource && block2Above.isAir)) {
if (world.setBlock(clickedPos.above(), this.block.defaultBlockState(), Block.UPDATE_CLIENTS)) {
context.itemInHand.shrink(1)
return InteractionResult.SUCCESS
}
}
// 3. Default: fail if neither condition matches
return InteractionResult.FAIL
}
private fun placeOnLand(context: UseOnContext, targetPos: BlockPos): InteractionResult {
val world = context.level
val targetState = this.block.defaultBlockState()
if (world.setBlock(targetPos, targetState, Block.UPDATE_CLIENTS)) {
context.itemInHand.shrink(1)
return InteractionResult.SUCCESS
}
return InteractionResult.FAIL
}
private fun placeInWater(context: UseOnContext, targetPos: BlockPos): InteractionResult {
val world = context.level
val targetState = this.block.defaultBlockState().setValue(HeartyGrainsBlock.WATERLOGGED, true)
if (world.setBlock(targetPos, targetState, Block.UPDATE_CLIENTS)) {
context.itemInHand.shrink(1)
return InteractionResult.SUCCESS
}
return InteractionResult.FAIL
}
// todo is this needed like this? should it be simpler?
override fun use(world: Level, user: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
val blockHitResult = PlaceOnWaterBlockItem.getPlayerPOVHitResult(world, user, ClipContext.Fluid.ANY)
val stack = user.getItemInHand(hand)
if (world.isClientSide) {
return InteractionResultHolder.success(stack)
}
val clickedPos = blockHitResult.blockPos
val clickedBlockState = world.getBlockState(clickedPos)
val fluidState = world.getFluidState(clickedPos)
// 1. Prioritize source water block
if (fluidState.`is`(FluidTags.WATER) && fluidState.isSource) {
val blockAbove = world.getBlockState(clickedPos.above())
val blockBelow = world.getBlockState(clickedPos.below())
// Check if it's 1 block deep water
if (blockAbove.isAir && !blockBelow.fluidState.isSource) {
if (world.setBlock(clickedPos, this.block.defaultBlockState().setValue(HeartyGrainsBlock.WATERLOGGED, true), Block.UPDATE_CLIENTS)) {
stack.shrink(1)
return InteractionResultHolder.success(stack)
}
}
// Fails if not 1 block deep
return InteractionResultHolder.fail(stack)
}
// 2. Check for farmland or mud
if (clickedBlockState.`is`(Blocks.FARMLAND) || clickedBlockState.`is`(Blocks.MUD)) {
if (world.setBlock(clickedPos.above(), this.block.defaultBlockState(), Block.UPDATE_CLIENTS)) {
stack.shrink(1)
return InteractionResultHolder.success(stack)
}
}
// 3. Default: fail if neither condition matches
return InteractionResultHolder.fail(stack)
}
}
@@ -0,0 +1,313 @@
/*
* 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
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.advancement.CobblemonCriteria
import com.cobblemon.mod.common.advancement.criterion.CastPokeRodContext
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.fishing.BaitConsumedEvent
import com.cobblemon.mod.common.api.events.fishing.BaitSetEvent
import com.cobblemon.mod.common.api.events.fishing.PokerodCastEvent
import com.cobblemon.mod.common.api.events.fishing.PokerodReelEvent
import com.cobblemon.mod.common.api.fishing.FishingBait
import com.cobblemon.mod.common.api.fishing.FishingBaits
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.item.components.CookingComponent
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.enchantmentRegistry
import com.cobblemon.mod.common.util.itemRegistry
import com.cobblemon.mod.common.util.playSoundServer
import com.cobblemon.mod.common.util.toEquipmentSlot
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundSource
import net.minecraft.stats.Stats
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResultHolder
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.SlotAccess
import net.minecraft.world.entity.player.Player
import net.minecraft.world.entity.projectile.ThrowableItemProjectile
import net.minecraft.world.inventory.ClickAction
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.FishingRodItem
import net.minecraft.world.item.Item
import net.minecraft.world.item.Item.Properties
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.enchantment.EnchantmentHelper
import net.minecraft.world.item.enchantment.Enchantments
import net.minecraft.world.level.Level
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.phys.Vec3
import kotlin.collections.flatten
import kotlin.ranges.coerceIn
class PokeBaitItem(val pokeRodId: ResourceLocation, settings: Properties) {//: ThrowableItemProjectile(settings) {
companion object {
fun getBaitOnPokeBait(stack: ItemStack): FishingBait? {
return getCookingComponentOnRod(stack) ?: stack.components.get(CobblemonItemComponents.BAIT)?.bait
}
fun getBaitStackOnRod(stack: ItemStack): ItemStack {
return stack.components.get(CobblemonItemComponents.BAIT)?.stack ?: ItemStack.EMPTY
}
fun getCookingComponentOnRod(rodStack: ItemStack): FishingBait? {
// Check if the stack within the RodBaitComponent has a CookingComponent
val cookingComponent = rodStack.get(CobblemonItemComponents.COOKING_COMPONENT) ?: return null
// Combine effects from the CookingComponent
val combinedEffects = listOf(
cookingComponent.bait1.effects,
cookingComponent.bait2.effects,
cookingComponent.bait3.effects
).flatten()
// Return a new FishingBait with combined effects
return FishingBait(
item = BuiltInRegistries.ITEM.getKey(
rodStack.components.get(CobblemonItemComponents.BAIT)?.stack?.item ?: ItemStack.EMPTY.item
), // Use the rodStack's item as the bait identifier
effects = combinedEffects
)
}
fun setBait(stack: ItemStack, bait: ItemStack) {
CobblemonEvents.BAIT_SET.postThen(BaitSetEvent(stack, bait), { event -> }, {
if (bait.isEmpty) {
stack.set<RodBaitComponent>(CobblemonItemComponents.BAIT, null)
stack.set<CookingComponent>(CobblemonItemComponents.COOKING_COMPONENT, null)
return
}
// Retrieve FishingBait and CookingComponent from the bait ItemStack
val fishingBait = FishingBaits.getFromBaitItemStack(bait) ?: return
val cookingComponent = bait.get(CobblemonItemComponents.COOKING_COMPONENT)
// Apply both RodBaitComponent and CookingComponent to the rod ItemStack
stack.set(CobblemonItemComponents.BAIT, RodBaitComponent(fishingBait, bait))
if (cookingComponent != null) {
stack.set(CobblemonItemComponents.COOKING_COMPONENT, cookingComponent)
} else {
// Clear CookingComponent if the new bait does not have it
stack.set<CookingComponent>(CobblemonItemComponents.COOKING_COMPONENT, null)
}
})
}
fun consumeBait(stack: ItemStack) {
CobblemonEvents.BAIT_CONSUMED.postThen(BaitConsumedEvent(stack), { event -> }, {
val baitStack = getBaitStackOnRod(stack)
val baitCount = baitStack.count
val cookingComponent = stack.get(CobblemonItemComponents.COOKING_COMPONENT)
if (baitCount == 1) {
stack.set<RodBaitComponent>(CobblemonItemComponents.BAIT, null)
stack.set<CookingComponent>(CobblemonItemComponents.COOKING_COMPONENT, null)
return
}
if (baitCount > 1) {
val fishingBait = getBaitOnPokeBait(stack) ?: return
stack.set<RodBaitComponent>(
CobblemonItemComponents.BAIT,
RodBaitComponent(fishingBait, ItemStack(baitStack.item, baitCount - 1))
)
if (cookingComponent != null) {
stack.set(CobblemonItemComponents.COOKING_COMPONENT, cookingComponent)
}
}
})
}
fun getBaitEffects(stack: ItemStack): List<FishingBait.Effect> {
return getBaitOnPokeBait(stack)?.effects ?: return emptyList()
}
}
/*// Fishing Rod: Bundle edition
override fun overrideOtherStackedOnMe(
itemStack: ItemStack,
itemStack2: ItemStack,
slot: Slot,
clickAction: ClickAction,
player: Player,
slotAccess: SlotAccess
): Boolean {
if (clickAction != ClickAction.SECONDARY || !slot.allowModification(player))
return false
val baitStack = getBaitStackOnRod(itemStack)
CobblemonEvents.BAIT_SET_PRE.postThen(BaitSetEvent(itemStack, itemStack2), { event ->
return event.isCanceled
}, {
// If not holding an item on cursor
if (itemStack2.isEmpty) {
// Retrieve bait onto cursor
if (baitStack != ItemStack.EMPTY) {
playDetachSound(player)
setBait(itemStack, ItemStack.EMPTY)
slotAccess.set(baitStack.copy())
return true
}
}
// If holding item on cursor
else {
// If item on cursor is a valid bait
if (FishingBaits.getFromBaitItemStack(itemStack2) != null) {
// Add as much as possible
if (baitStack != ItemStack.EMPTY) {
if (baitStack.item == itemStack2.item) {
playAttachSound(player)
// Calculate how much bait to add
val diff = (baitStack.maxStackSize - baitStack.count).coerceIn(0, itemStack2.count)
itemStack2.shrink(diff)
baitStack.grow(diff)
setBait(itemStack, baitStack)
return true
}
// If Item on rod is different from cursor item, swap them
playAttachSound(player)
setBait(itemStack, itemStack2.copy())
slotAccess.set(baitStack.copy())
return true
}
// If no bait currently on rod, add all
playAttachSound(player)
setBait(itemStack, itemStack2.copy())
itemStack2.shrink(itemStack2.count)
return true
}
}
})
return false
}
override fun use(world: Level, user: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
val itemStack = user.getItemInHand(hand)
val offHandItem = user.getItemInHand(InteractionHand.OFF_HAND)
val offHandBait = FishingBaits.getFromBaitItemStack(offHandItem)
var baitOnRod = getBaitOnRod(itemStack)
// Check if offhand item is valid bait and the rod is not in use, if so then apply bait from offhand
if (!world.isClientSide && user.fishing == null && offHandBait != null) {
CobblemonEvents.BAIT_SET_PRE.postThen(BaitSetEvent(itemStack, offHandItem), { event ->
return InteractionResultHolder.fail(itemStack)
}, {
playAttachSound(user)
// if there is bait on the rod already then drop it on the ground before applying offhand bait
val baitStack = baitOnRod?.toItemStack(world.itemRegistry)
if (baitStack != null && offHandItem.item != baitStack.item) {
if (!baitStack.isEmpty) {
baitStack.count = getBaitStackOnRod(itemStack).count
user.drop(baitStack, true) // Drop the full stack
}
// apply single bait item from offhand
val singleBait = offHandItem.copy()
singleBait.count = 1
setBait(itemStack, singleBait)
offHandItem.shrink(1)
}
})
}
var i: Int
if (user.fishing != null) { // if the bobber is out yet
if (!world.isClientSide) {
CobblemonEvents.POKEROD_REEL.postThen(
PokerodReelEvent(itemStack),
{ event -> return InteractionResultHolder.fail(itemStack) },
{ event ->
i = user.fishing!!.retrieve(itemStack)
itemStack.hurtAndBreak(i, user, hand.toEquipmentSlot())
world.playSoundServer(
Vec3(
user.x,
user.y,
user.z
),
CobblemonSounds.FISHING_ROD_REEL_IN,
SoundSource.PLAYERS,
1.0f,
1.0f / (world.getRandom().nextFloat() * 0.4f + 0.8f)
)
}
)
}
world.playSound(null as Player?, user.x, user.y, user.z, CobblemonSounds.FISHING_ROD_REEL_IN, SoundSource.PLAYERS, 1.0f, 1.0f)
user.gameEvent(GameEvent.ITEM_INTERACT_FINISH)
} else { // if the bobber is not out yet
if (!world.isClientSide) {
val lureLevel = world.enchantmentRegistry.getHolder(Enchantments.LURE).map { EnchantmentHelper.getItemEnchantmentLevel(it, itemStack) }.orElse(0)
val luckLevel = world.enchantmentRegistry.getHolder(Enchantments.LUCK_OF_THE_SEA).map { EnchantmentHelper.getItemEnchantmentLevel(it, itemStack) }.orElse(0)
val bobberEntity = PokeRodFishingBobberEntity(
user,
pokeRodId,
getBaitOnRod(itemStack)?.toItemStack(world.itemRegistry) ?: ItemStack.EMPTY,
world,
luckLevel,
lureLevel,
itemStack
)
CobblemonEvents.POKEROD_CAST_PRE.postThen(
PokerodCastEvent.Pre(itemStack, bobberEntity, getBaitStackOnRod(itemStack)),
{ event -> return InteractionResultHolder.fail(itemStack) },
{ event ->
world.addFreshEntity(bobberEntity)
var baitId = getBaitOnRod(itemStack)?.item ?: cobblemonResource("empty_bait")
CobblemonCriteria.CAST_POKE_ROD.trigger(user as ServerPlayer, CastPokeRodContext(baitId))
CobblemonEvents.POKEROD_CAST_POST.post(
PokerodCastEvent.Post(itemStack, bobberEntity, getBaitStackOnRod(itemStack))
)
}
)
}
user.awardStat(Stats.ITEM_USED.get(this))
user.gameEvent(GameEvent.ITEM_INTERACT_START)
}
return InteractionResultHolder.sidedSuccess(itemStack, world.isClientSide)
}*/
private fun playAttachSound(entity: Entity) {
entity.playSound(CobblemonSounds.FISHING_BAIT_ATTACH, 1F, 1F)
}
private fun playDetachSound(entity: Entity) {
entity.playSound(CobblemonSounds.FISHING_BAIT_DETACH, 1F, 1F)
}
}
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.item
import com.cobblemon.mod.common.block.SaccharineSaplingBlock
import net.minecraft.world.item.ItemNameBlockItem
import net.minecraft.world.item.context.BlockPlaceContext
import net.minecraft.world.level.block.state.BlockState
class SaccharineSaplingItem(block: SaccharineSaplingBlock) : ItemNameBlockItem(block, Properties()) {
override fun getPlacementState(context: BlockPlaceContext): BlockState? {
return super.getPlacementState(context)
}
}
@@ -0,0 +1,54 @@
/*
* 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
import com.cobblemon.mod.common.entity.throwable.ScatterBangEntity
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.entity.projectile.thrown.SnowballEntity
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.sound.SoundCategory
import net.minecraft.sound.SoundEvents
import net.minecraft.stat.Stats
import net.minecraft.util.Hand
import net.minecraft.util.TypedActionResult
import net.minecraft.world.World
class ScatterBangItem(settings: Settings?) : Item(settings) {
override fun use(world: World, user: PlayerEntity, hand: Hand): TypedActionResult<ItemStack> {
val itemStack = user.getStackInHand(hand)
world.playSound(
null as PlayerEntity?,
user.x,
user.y,
user.z,
SoundEvents.ENTITY_SNOWBALL_THROW,
SoundCategory.NEUTRAL,
0.5f,
0.4f / (world.getRandom().nextFloat() * 0.4f + 0.8f)
)
if (!world.isClient) {
val scatterBangEntity = ScatterBangEntity(world, user)
scatterBangEntity.setItem(itemStack)
scatterBangEntity.setVelocity(user, user.pitch, user.yaw, 0.0f, 1.5f, 1.0f)
world.spawnEntity(scatterBangEntity)
}
user.incrementStat(Stats.USED.getOrCreateStat(this))
if (!user.abilities.creativeMode) {
itemStack.decrement(1)
}
return TypedActionResult.success(itemStack, world.isClient())
}
}*/
@@ -0,0 +1,98 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.item.components
import com.cobblemon.mod.common.api.cooking.Seasoning
import com.cobblemon.mod.common.api.fishing.FishingBait
import com.cobblemon.mod.common.api.fishing.FishingBaits
import com.cobblemon.mod.common.api.cooking.Seasonings
import com.cobblemon.mod.common.client.pot.CookingQuality
import net.minecraft.network.codec.ByteBufCodecs
import net.minecraft.network.codec.StreamCodec
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import io.netty.buffer.ByteBuf
import net.minecraft.ChatFormatting
import net.minecraft.resources.ResourceLocation
class CookingComponent(
val bait1: FishingBait,
val bait2: FishingBait,
val bait3: FishingBait,
val seasoning1: Seasoning,
val seasoning2: Seasoning,
val seasoning3: Seasoning
) {
companion object {
val CODEC: Codec<CookingComponent> = RecordCodecBuilder.create { builder ->
builder.group(
ResourceLocation.CODEC.fieldOf("bait1").forGetter<CookingComponent> { it.bait1.item },
ResourceLocation.CODEC.fieldOf("bait2").forGetter<CookingComponent> { it.bait2.item },
ResourceLocation.CODEC.fieldOf("bait3").forGetter<CookingComponent> { it.bait3.item },
ResourceLocation.CODEC.fieldOf("seasoning1").forGetter<CookingComponent> { it.seasoning1.ingredient },
ResourceLocation.CODEC.fieldOf("seasoning2").forGetter<CookingComponent> { it.seasoning2.ingredient },
ResourceLocation.CODEC.fieldOf("seasoning3").forGetter<CookingComponent> { it.seasoning3.ingredient }
).apply(builder) { bait1Loc, bait2Loc, bait3Loc, seasoning1Loc, seasoning2Loc, seasoning3Loc ->
CookingComponent(
bait1 = FishingBaits.getFromIdentifier(bait1Loc) ?: FishingBait.BLANK_BAIT,
bait2 = FishingBaits.getFromIdentifier(bait2Loc) ?: FishingBait.BLANK_BAIT,
bait3 = FishingBaits.getFromIdentifier(bait3Loc) ?: FishingBait.BLANK_BAIT,
seasoning1 = Seasonings.getFromIdentifier(seasoning1Loc)
?: Seasoning(ResourceLocation("minecraft", "unknown"), mapOf(), "", 0),
seasoning2 = Seasonings.getFromIdentifier(seasoning2Loc)
?: Seasoning(ResourceLocation("minecraft", "unknown"), mapOf(), "", 0),
seasoning3 = Seasonings.getFromIdentifier(seasoning3Loc)
?: Seasoning(ResourceLocation("minecraft", "unknown"), mapOf(), "", 0)
)
}
}
val PACKET_CODEC: StreamCodec<ByteBuf, CookingComponent> = ByteBufCodecs.fromCodec(CODEC)
}
override fun equals(other: Any?): Boolean {
return other is CookingComponent &&
bait1 == other.bait1 &&
bait2 == other.bait2 &&
bait3 == other.bait3 &&
seasoning1 == other.seasoning1 &&
seasoning2 == other.seasoning2 &&
seasoning3 == other.seasoning3
}
override fun hashCode(): Int {
return listOf(bait1, bait2, bait3, seasoning1, seasoning2, seasoning3).hashCode()
}
fun getCookingQuality(): CookingQuality {
val qualityAverage = getQualityAverage()
return when {
qualityAverage < 10 -> CookingQuality.LOW
qualityAverage < 20 -> CookingQuality.MEDIUM
else -> CookingQuality.HIGH
}
}
private fun getQualityAverage() = getSeasonings().map { it.quality }.average()
fun getDominantFlavors(): List<String> {
val flavors = getFlavorsSum()
val maxFlavorValue = flavors.values.maxOrNull()
return flavors.filter { it.value == maxFlavorValue }.map { it.key }
}
fun getFlavorsSum(): Map<String, Int> =
getSeasonings()
.flatMap { it.flavors.entries }
.groupingBy { it.key }
.fold(0) { acc, entry -> acc + entry.value }
fun getSeasonings() = listOf(seasoning1, seasoning2, seasoning3).filter { it.quality > 0 }
}
@@ -0,0 +1,85 @@
/*
* 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.components
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.resources.ResourceLocation
import java.util.Optional
class SeasoningComponent(
val ingredient1: ResourceLocation?,
val flavor1: String?,
val color1: String?,
val quality1: Int?,
val ingredient2: ResourceLocation?,
val flavor2: String?,
val color2: String?,
val quality2: Int?,
val ingredient3: ResourceLocation?,
val flavor3: String?,
val color3: String?
) {
companion object {
val CODEC: Codec<SeasoningComponent> = RecordCodecBuilder.create { builder ->
builder.group(
ResourceLocation.CODEC.optionalFieldOf("ingredient1").forGetter { Optional.ofNullable(it.ingredient1) },
Codec.STRING.optionalFieldOf("flavor1").forGetter { Optional.ofNullable(it.flavor1) },
Codec.STRING.optionalFieldOf("color1").forGetter { Optional.ofNullable(it.color1) },
Codec.INT.optionalFieldOf("quality1").forGetter { Optional.ofNullable(it.quality1) },
ResourceLocation.CODEC.optionalFieldOf("ingredient2").forGetter { Optional.ofNullable(it.ingredient2) },
Codec.STRING.optionalFieldOf("flavor2").forGetter { Optional.ofNullable(it.flavor2) },
Codec.STRING.optionalFieldOf("color2").forGetter { Optional.ofNullable(it.color2) },
Codec.INT.optionalFieldOf("quality2").forGetter { Optional.ofNullable(it.quality2) },
ResourceLocation.CODEC.optionalFieldOf("ingredient3").forGetter { Optional.ofNullable(it.ingredient3) },
Codec.STRING.optionalFieldOf("flavor3").forGetter { Optional.ofNullable(it.flavor3) },
Codec.STRING.optionalFieldOf("color3").forGetter { Optional.ofNullable(it.color3) }
).apply(builder) { ingredient1Opt, flavor1Opt, color1Opt, quality1Opt,
ingredient2Opt, flavor2Opt, color2Opt, quality2Opt,
ingredient3Opt, flavor3Opt, color3Opt ->
SeasoningComponent(
ingredient1Opt.orElse(null),
flavor1Opt.orElse(null),
color1Opt.orElse(null),
quality1Opt.orElse(null),
ingredient2Opt.orElse(null),
flavor2Opt.orElse(null),
color2Opt.orElse(null),
quality2Opt.orElse(null),
ingredient3Opt.orElse(null),
flavor3Opt.orElse(null),
color3Opt.orElse(null)
)
}
}
}
override fun equals(other: Any?): Boolean {
return other is SeasoningComponent &&
ingredient1 == other.ingredient1 &&
flavor1 == other.flavor1 &&
color1 == other.color1 &&
quality1 == other.quality1 &&
ingredient2 == other.ingredient2 &&
flavor2 == other.flavor2 &&
color2 == other.color2 &&
quality2 == other.quality2 &&
ingredient3 == other.ingredient3 &&
flavor3 == other.flavor3 &&
color3 == other.color3
}
override fun hashCode(): Int {
return listOf(
ingredient1, flavor1, color1, quality1,
ingredient2, flavor2, color2, quality2,
ingredient3, flavor3, color3
).fold(0) { acc, value -> 31 * acc + (value?.hashCode() ?: 0) }
}
}
@@ -107,6 +107,10 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.VIVICHOKE)
entries.accept(CobblemonItems.VIVICHOKE_SEEDS)
entries.accept(CobblemonItems.GALARICA_NUTS)
entries.accept(CobblemonItems.Hearty_Grains)
entries.accept(CobblemonItems.BUGWORT)
entries.accept(CobblemonItems.SWEET_SAP)
entries.accept(CobblemonItems.RED_APRICORN)
entries.accept(CobblemonItems.YELLOW_APRICORN)
@@ -123,6 +127,8 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.BLACK_APRICORN_SEED)
entries.accept(CobblemonItems.WHITE_APRICORN_SEED)
entries.accept(CobblemonItems.SACCHARINE_SAPLING)
entries.accept(CobblemonItems.RED_MINT_SEEDS)
entries.accept(CobblemonItems.RED_MINT_LEAF)
entries.accept(CobblemonItems.BLUE_MINT_SEEDS)
@@ -250,6 +256,7 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.RELIC_COIN_POUCH)
entries.accept(CobblemonItems.RELIC_COIN_SACK)
entries.accept(CobblemonItems.INCENSE_SWEET)
entries.accept(CobblemonItems.DISPLAY_CASE)
entries.accept(CobblemonItems.APRICORN_LOG)
entries.accept(CobblemonItems.APRICORN_WOOD)
@@ -268,6 +275,25 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.APRICORN_HANGING_SIGN)
entries.accept(CobblemonItems.APRICORN_LEAVES)
entries.accept(CobblemonItems.SACCHARINE_LOG)
entries.accept(CobblemonItems.SACCHARINE_WOOD)
entries.accept(CobblemonItems.STRIPPED_SACCHARINE_LOG)
entries.accept(CobblemonItems.STRIPPED_SACCHARINE_WOOD)
entries.accept(CobblemonItems.SACCHARINE_PLANKS)
entries.accept(CobblemonItems.SACCHARINE_STAIRS)
entries.accept(CobblemonItems.SACCHARINE_SLAB)
entries.accept(CobblemonItems.SACCHARINE_FENCE)
entries.accept(CobblemonItems.SACCHARINE_FENCE_GATE)
entries.accept(CobblemonItems.SACCHARINE_DOOR)
entries.accept(CobblemonItems.SACCHARINE_TRAPDOOR)
entries.accept(CobblemonItems.SACCHARINE_BUTTON)
entries.accept(CobblemonItems.SACCHARINE_PRESSURE_PLATE)
entries.accept(CobblemonItems.SACCHARINE_SIGN)
entries.accept(CobblemonItems.SACCHARINE_HANGING_SIGN)
entries.accept(CobblemonItems.SACCHARINE_LEAVES)
//entries.accept(CobblemonItems.SACCHARINE_SAPLING)
entries.accept(CobblemonItems.TUMBLESTONE_BLOCK)
entries.accept(CobblemonItems.BLACK_TUMBLESTONE_BLOCK)
entries.accept(CobblemonItems.SKY_TUMBLESTONE_BLOCK)
@@ -346,6 +372,8 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.SUN_STONE_BLOCK)
entries.accept(CobblemonItems.THUNDER_STONE_BLOCK)
entries.accept(CobblemonItems.WATER_STONE_BLOCK)
CobblemonItems.campfire_pots.forEach(entries::accept)
}
private fun consumableEntries(displayContext: ItemDisplayParameters, entries: Output) {
@@ -405,6 +433,15 @@ object CobblemonItemGroups {
entries.accept(CobblemonItems.CALCIUM)
entries.accept(CobblemonItems.ZINC)
entries.accept(CobblemonItems.CARBOS)
entries.accept(CobblemonItems.HEALTH_MOCHI)
entries.accept(CobblemonItems.MUSCLE_MOCHI)
entries.accept(CobblemonItems.RESIST_MOCHI)
entries.accept(CobblemonItems.GENIUS_MOCHI)
entries.accept(CobblemonItems.CLEVER_MOCHI)
entries.accept(CobblemonItems.SWIFT_MOCHI)
entries.accept(CobblemonItems.FRESH_START_MOCHI)
entries.accept(CobblemonItems.PP_UP)
entries.accept(CobblemonItems.PP_MAX)
entries.accept(CobblemonItems.EXPERIENCE_CANDY_XS)
@@ -648,6 +685,9 @@ object CobblemonItemGroups {
injector.putAfter(CobblemonItems.BRAISED_VIVICHOKE, CobblemonItems.ROASTED_LEEK)
injector.putAfter(CobblemonItems.LEEK_AND_POTATO_STEW, Items.RABBIT_STEW)
injector.putAfter(CobblemonItems.VIVICHOKE_DIP, CobblemonItems.LEEK_AND_POTATO_STEW)
injector.putLast(CobblemonItems.POTATO_MOCHI)
injector.putLast(CobblemonItems.FRIED_RICE)
injector.putLast(CobblemonItems.CANDIED_APPLE)
}
private fun toolsAndUtilitiesInjections(injector: Injector) {
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.item.interactive
import com.cobblemon.mod.common.api.item.PokemonSelectingItem
import com.cobblemon.mod.common.api.pokemon.stats.ItemEvSource
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.item.CobblemonItem
import com.cobblemon.mod.common.pokemon.EVs
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundEvent
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResultHolder
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
abstract class EVIncreaseItem(
val stat: Stat,
val evIncreaseAmount: Int,
) : CobblemonItem(Properties()), PokemonSelectingItem {
override val bagItem = null
override fun canUseOnPokemon(pokemon: Pokemon) = pokemon.evs.getOrDefault(stat) < EVs.MAX_STAT_VALUE
abstract val sound: SoundEvent
override fun applyToPokemon(
player: ServerPlayer,
stack: ItemStack,
pokemon: Pokemon
): InteractionResultHolder<ItemStack> {
val evsGained = pokemon.evs.add(stat, evIncreaseAmount, ItemEvSource(player, stack, pokemon))
return if (evsGained > 0) {
pokemon.entity?.playSound(sound, 1F, 1F)
if (!player.isCreative) {
stack.shrink(1)
}
InteractionResultHolder.success(stack)
} else {
InteractionResultHolder.fail(stack)
}
}
override fun use(world: Level, user: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
if (user is ServerPlayer) {
return use(user, user.getItemInHand(hand))
}
return InteractionResultHolder.success(user.getItemInHand(hand))
}
}
@@ -9,49 +9,9 @@
package com.cobblemon.mod.common.item.interactive
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.item.PokemonSelectingItem
import com.cobblemon.mod.common.api.pokemon.stats.ItemEvSource
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.item.CobblemonItem
import com.cobblemon.mod.common.pokemon.EVs
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResultHolder
import net.minecraft.world.level.Level
import net.minecraft.sounds.SoundEvent
class FeatherItem(val stat: Stat) : CobblemonItem(Properties()), PokemonSelectingItem {
companion object {
const val EV_YIELD = 1
}
override val bagItem = null
override fun canUseOnPokemon(pokemon: Pokemon) = pokemon.evs.getOrDefault(stat) < EVs.MAX_STAT_VALUE
override fun applyToPokemon(
player: ServerPlayer,
stack: ItemStack,
pokemon: Pokemon
): InteractionResultHolder<ItemStack> {
val evsGained = pokemon.evs.add(stat, EV_YIELD, ItemEvSource(player, stack, pokemon))
return if (evsGained > 0) {
pokemon.entity?.playSound(CobblemonSounds.MEDICINE_FEATHER_USE, 1F, 1F)
if (!player.isCreative) {
stack.shrink(1)
}
InteractionResultHolder.success(stack)
} else {
InteractionResultHolder.fail(stack)
}
}
override fun use(world: Level, user: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
if (user is ServerPlayer) {
return use(user, user.getItemInHand(hand))
}
return InteractionResultHolder.success(user.getItemInHand(hand))
}
class FeatherItem(stat: Stat) : EVIncreaseItem(stat, 1) {
override val sound: SoundEvent = CobblemonSounds.MEDICINE_FEATHER_USE
}

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