From eb8eb76e984b2213765db32c47f67a8528188457 Mon Sep 17 00:00:00 2001 From: azzy Date: Sat, 17 Dec 2022 01:33:12 -0600 Subject: [PATCH] I said we DO A LIL PORTING --- .../java/net/id/incubus_core/IncubusCore.java | 87 +++ .../id/incubus_core/IncubusCoreClient.java | 20 + .../net/id/incubus_core/IncubusCoreInit.java | 85 +++ .../annotations/NonnullByDefault.java | 17 + .../net/id/incubus_core/be/IncubusBaseBE.java | 84 +++ .../be/IncubusLazyBlockEntity.java | 65 +++ .../incubus_core/be/InventoryBlockEntity.java | 92 ++++ .../id/incubus_core/block/TallCropBlock.java | 213 ++++++++ .../api/BlockLikeEntity.java | 447 +++++++++++++++ .../blocklikeentities/api/BlockLikeSet.java | 192 +++++++ .../api/client/BlockLikeEntityRenderer.java | 56 ++ .../util/PostTickEntity.java | 8 + .../condition/IncubusCondition.java | 31 ++ .../incubus_core/condition/api/Condition.java | 130 +++++ .../condition/api/ConditionAPI.java | 76 +++ .../condition/api/ConditionModifier.java | 24 + .../condition/api/Persistence.java | 44 ++ .../incubus_core/condition/api/Severity.java | 81 +++ .../condition/api/package-info.java | 2 + .../condition/base/ConditionCommand.java | 216 ++++++++ .../condition/base/ConditionManager.java | 304 +++++++++++ .../java/net/id/incubus_core/dev/DevInit.java | 92 ++++ .../dev/block/TestFurnaceBlock.java | 42 ++ .../dev/block/TestFurnaceBlockEntity.java | 26 + .../dev/item/EntityDeathMessageTestItem.java | 57 ++ .../dev/recipe/TestRecipeType.java | 58 ++ .../id/incubus_core/json/RecipeParser.java | 149 +++++ .../misc/CustomDeathMessageProvider.java | 83 +++ .../misc/IncubusDamageSources.java | 60 +++ .../incubus_core/misc/IncubusPlayerData.java | 44 ++ .../id/incubus_core/misc/IncubusSounds.java | 21 + .../misc/IncubusToolMaterials.java | 59 ++ .../net/id/incubus_core/misc/Players.java | 9 + .../incubus_core/misc/WorthinessChecker.java | 131 +++++ .../incubus_core/misc/item/AzzysFlagItem.java | 507 ++++++++++++++++++ .../misc/item/BerryBranchItem.java | 100 ++++ .../incubus_core/misc/item/FoxEffigyItem.java | 91 ++++ .../misc/item/IncubusCoreItems.java | 40 ++ .../misc/item/IncubusFoodComponents.java | 38 ++ .../misc/item/IncubusMusicDiscItem.java | 10 + .../misc/item/LongSpatulaItem.java | 159 ++++++ .../misc/item/LunarianSaberItem.java | 116 ++++ .../misc/item/PathStateAccessor.java | 19 + .../misc/playerdata/PlayerData.java | 100 ++++ .../misc/playerdata/SpawnItemRegistry.java | 27 + .../net/id/incubus_core/mixin/Plugin.java | 48 ++ .../blocklikeentities/ServerWorldMixin.java | 39 ++ .../client/ClientPlayerEntityMixin.java | 34 ++ .../client/ClientWorldMixin.java | 30 ++ .../mixin/client/AnimationAccessor.java | 14 + .../mixin/client/AnimationFrameAccessor.java | 14 + .../incubus_core/mixin/client/CapeMixin.java | 32 ++ .../mixin/client/InGameHudMixin.java | 39 ++ .../mixin/client/InterpFixMixin.java | 63 +++ .../mixin/client/PlayerRendererMixin.java | 21 + .../mixin/client/RenderLayerAccessor.java | 35 ++ .../mixin/client/SpriteContentsAccessor.java | 13 + .../mixin/entity/BlockEntityMixin.java | 13 + .../mixin/entity/EntityDamageSourceMixin.java | 69 +++ .../mixin/entity/FoxEditorMixin.java | 62 +++ .../mixin/player/PlayerDropMixin.java | 40 ++ .../mixin/player/PlayerEntityMixin.java | 30 ++ .../mixin/player/PlayerManagerMixin.java | 37 ++ .../id/incubus_core/networking/C2SPacket.java | 46 ++ .../id/incubus_core/networking/Packet.java | 15 + .../id/incubus_core/networking/S2CPacket.java | 55 ++ .../id/incubus_core/recipe/IncubusRecipe.java | 29 + .../recipe/IncubusRecipeType.java | 24 + .../recipe/IncubusRecipeTypes.java | 13 + .../incubus_core/recipe/IngredientStack.java | 168 ++++++ .../recipe/ItemDamagingRecipe.java | 54 ++ .../id/incubus_core/recipe/OptionalStack.java | 123 +++++ .../recipe/matchbook/BooleanMatch.java | 65 +++ .../recipe/matchbook/ByteMatch.java | 65 +++ .../recipe/matchbook/EnchantmentMatch.java | 103 ++++ .../recipe/matchbook/FloatMatch.java | 72 +++ .../recipe/matchbook/IncubusMatches.java | 16 + .../recipe/matchbook/IntMatch.java | 65 +++ .../recipe/matchbook/IntRangeMatch.java | 72 +++ .../recipe/matchbook/LongMatch.java | 65 +++ .../incubus_core/recipe/matchbook/Match.java | 38 ++ .../recipe/matchbook/MatchFactory.java | 28 + .../recipe/matchbook/MatchRegistry.java | 41 ++ .../recipe/matchbook/Matchbook.java | 91 ++++ .../recipe/matchbook/ShortMatch.java | 65 +++ .../recipe/matchbook/StringMatch.java | 65 +++ .../id/incubus_core/render/ColorUtils.java | 19 + .../render/IncubusRenderLayers.java | 36 ++ .../incubus_core/render/OverlayRegistrar.java | 27 + .../id/incubus_core/render/RenderHelper.java | 93 ++++ .../IncubusCoreResourceConditions.java | 24 + .../status_effects/ZonkedEffect.java | 11 + .../systems/DefaultMaterials.java | 28 + .../id/incubus_core/systems/HeatHelper.java | 76 +++ .../net/id/incubus_core/systems/HeatIo.java | 71 +++ .../id/incubus_core/systems/KineticIo.java | 34 ++ .../net/id/incubus_core/systems/Lookups.java | 58 ++ .../net/id/incubus_core/systems/Material.java | 32 ++ .../systems/MaterialProvider.java | 10 + .../id/incubus_core/systems/PressureIo.java | 35 ++ .../systems/RegistryRegistry.java | 16 + .../id/incubus_core/systems/Simulation.java | 14 + .../id/incubus_core/task/AbstractTask.java | 31 ++ .../net/id/incubus_core/task/AsyncTask.java | 43 ++ .../id/incubus_core/task/ScheduledTask.java | 29 + .../net/id/incubus_core/task/TaskInfo.java | 8 + .../java/net/id/incubus_core/task/Tasks.java | 56 ++ .../java/net/id/incubus_core/util/Config.java | 59 ++ .../id/incubus_core/util/EnumExtender.java | 59 ++ .../net/id/incubus_core/util/FoxDuck.java | 14 + .../id/incubus_core/util/IncubusHoliday.java | 108 ++++ .../incubus_core/util/InventoryWrapper.java | 125 +++++ .../net/id/incubus_core/util/RandomShim.java | 61 +++ .../id/incubus_core/util/RegistryHelper.java | 44 ++ .../id/incubus_core/util/RegistryQueue.java | 129 +++++ .../id/incubus_core/util/SeedSupplier.java | 9 + .../net/id/incubus_core/util/TagSuperset.java | 56 ++ .../net/id/incubus_core/util/TickCounter.java | 49 ++ .../net/id/incubus_core/util/UuidHelper.java | 67 +++ 119 files changed, 7854 insertions(+) create mode 100644 src/main/java/net/id/incubus_core/IncubusCore.java create mode 100644 src/main/java/net/id/incubus_core/IncubusCoreClient.java create mode 100644 src/main/java/net/id/incubus_core/IncubusCoreInit.java create mode 100644 src/main/java/net/id/incubus_core/annotations/NonnullByDefault.java create mode 100644 src/main/java/net/id/incubus_core/be/IncubusBaseBE.java create mode 100644 src/main/java/net/id/incubus_core/be/IncubusLazyBlockEntity.java create mode 100644 src/main/java/net/id/incubus_core/be/InventoryBlockEntity.java create mode 100644 src/main/java/net/id/incubus_core/block/TallCropBlock.java create mode 100644 src/main/java/net/id/incubus_core/blocklikeentities/api/BlockLikeEntity.java create mode 100644 src/main/java/net/id/incubus_core/blocklikeentities/api/BlockLikeSet.java create mode 100644 src/main/java/net/id/incubus_core/blocklikeentities/api/client/BlockLikeEntityRenderer.java create mode 100644 src/main/java/net/id/incubus_core/blocklikeentities/util/PostTickEntity.java create mode 100644 src/main/java/net/id/incubus_core/condition/IncubusCondition.java create mode 100644 src/main/java/net/id/incubus_core/condition/api/Condition.java create mode 100644 src/main/java/net/id/incubus_core/condition/api/ConditionAPI.java create mode 100644 src/main/java/net/id/incubus_core/condition/api/ConditionModifier.java create mode 100644 src/main/java/net/id/incubus_core/condition/api/Persistence.java create mode 100644 src/main/java/net/id/incubus_core/condition/api/Severity.java create mode 100644 src/main/java/net/id/incubus_core/condition/api/package-info.java create mode 100644 src/main/java/net/id/incubus_core/condition/base/ConditionCommand.java create mode 100644 src/main/java/net/id/incubus_core/condition/base/ConditionManager.java create mode 100644 src/main/java/net/id/incubus_core/dev/DevInit.java create mode 100644 src/main/java/net/id/incubus_core/dev/block/TestFurnaceBlock.java create mode 100644 src/main/java/net/id/incubus_core/dev/block/TestFurnaceBlockEntity.java create mode 100644 src/main/java/net/id/incubus_core/dev/item/EntityDeathMessageTestItem.java create mode 100644 src/main/java/net/id/incubus_core/dev/recipe/TestRecipeType.java create mode 100644 src/main/java/net/id/incubus_core/json/RecipeParser.java create mode 100644 src/main/java/net/id/incubus_core/misc/CustomDeathMessageProvider.java create mode 100644 src/main/java/net/id/incubus_core/misc/IncubusDamageSources.java create mode 100644 src/main/java/net/id/incubus_core/misc/IncubusPlayerData.java create mode 100644 src/main/java/net/id/incubus_core/misc/IncubusSounds.java create mode 100644 src/main/java/net/id/incubus_core/misc/IncubusToolMaterials.java create mode 100644 src/main/java/net/id/incubus_core/misc/Players.java create mode 100644 src/main/java/net/id/incubus_core/misc/WorthinessChecker.java create mode 100644 src/main/java/net/id/incubus_core/misc/item/AzzysFlagItem.java create mode 100644 src/main/java/net/id/incubus_core/misc/item/BerryBranchItem.java create mode 100644 src/main/java/net/id/incubus_core/misc/item/FoxEffigyItem.java create mode 100644 src/main/java/net/id/incubus_core/misc/item/IncubusCoreItems.java create mode 100644 src/main/java/net/id/incubus_core/misc/item/IncubusFoodComponents.java create mode 100644 src/main/java/net/id/incubus_core/misc/item/IncubusMusicDiscItem.java create mode 100644 src/main/java/net/id/incubus_core/misc/item/LongSpatulaItem.java create mode 100644 src/main/java/net/id/incubus_core/misc/item/LunarianSaberItem.java create mode 100644 src/main/java/net/id/incubus_core/misc/item/PathStateAccessor.java create mode 100644 src/main/java/net/id/incubus_core/misc/playerdata/PlayerData.java create mode 100644 src/main/java/net/id/incubus_core/misc/playerdata/SpawnItemRegistry.java create mode 100644 src/main/java/net/id/incubus_core/mixin/Plugin.java create mode 100644 src/main/java/net/id/incubus_core/mixin/blocklikeentities/ServerWorldMixin.java create mode 100644 src/main/java/net/id/incubus_core/mixin/blocklikeentities/client/ClientPlayerEntityMixin.java create mode 100644 src/main/java/net/id/incubus_core/mixin/blocklikeentities/client/ClientWorldMixin.java create mode 100644 src/main/java/net/id/incubus_core/mixin/client/AnimationAccessor.java create mode 100644 src/main/java/net/id/incubus_core/mixin/client/AnimationFrameAccessor.java create mode 100644 src/main/java/net/id/incubus_core/mixin/client/CapeMixin.java create mode 100644 src/main/java/net/id/incubus_core/mixin/client/InGameHudMixin.java create mode 100644 src/main/java/net/id/incubus_core/mixin/client/InterpFixMixin.java create mode 100644 src/main/java/net/id/incubus_core/mixin/client/PlayerRendererMixin.java create mode 100644 src/main/java/net/id/incubus_core/mixin/client/RenderLayerAccessor.java create mode 100644 src/main/java/net/id/incubus_core/mixin/client/SpriteContentsAccessor.java create mode 100644 src/main/java/net/id/incubus_core/mixin/entity/BlockEntityMixin.java create mode 100644 src/main/java/net/id/incubus_core/mixin/entity/EntityDamageSourceMixin.java create mode 100644 src/main/java/net/id/incubus_core/mixin/entity/FoxEditorMixin.java create mode 100644 src/main/java/net/id/incubus_core/mixin/player/PlayerDropMixin.java create mode 100644 src/main/java/net/id/incubus_core/mixin/player/PlayerEntityMixin.java create mode 100644 src/main/java/net/id/incubus_core/mixin/player/PlayerManagerMixin.java create mode 100644 src/main/java/net/id/incubus_core/networking/C2SPacket.java create mode 100644 src/main/java/net/id/incubus_core/networking/Packet.java create mode 100644 src/main/java/net/id/incubus_core/networking/S2CPacket.java create mode 100644 src/main/java/net/id/incubus_core/recipe/IncubusRecipe.java create mode 100644 src/main/java/net/id/incubus_core/recipe/IncubusRecipeType.java create mode 100644 src/main/java/net/id/incubus_core/recipe/IncubusRecipeTypes.java create mode 100644 src/main/java/net/id/incubus_core/recipe/IngredientStack.java create mode 100644 src/main/java/net/id/incubus_core/recipe/ItemDamagingRecipe.java create mode 100644 src/main/java/net/id/incubus_core/recipe/OptionalStack.java create mode 100644 src/main/java/net/id/incubus_core/recipe/matchbook/BooleanMatch.java create mode 100644 src/main/java/net/id/incubus_core/recipe/matchbook/ByteMatch.java create mode 100644 src/main/java/net/id/incubus_core/recipe/matchbook/EnchantmentMatch.java create mode 100644 src/main/java/net/id/incubus_core/recipe/matchbook/FloatMatch.java create mode 100644 src/main/java/net/id/incubus_core/recipe/matchbook/IncubusMatches.java create mode 100644 src/main/java/net/id/incubus_core/recipe/matchbook/IntMatch.java create mode 100644 src/main/java/net/id/incubus_core/recipe/matchbook/IntRangeMatch.java create mode 100644 src/main/java/net/id/incubus_core/recipe/matchbook/LongMatch.java create mode 100644 src/main/java/net/id/incubus_core/recipe/matchbook/Match.java create mode 100644 src/main/java/net/id/incubus_core/recipe/matchbook/MatchFactory.java create mode 100644 src/main/java/net/id/incubus_core/recipe/matchbook/MatchRegistry.java create mode 100644 src/main/java/net/id/incubus_core/recipe/matchbook/Matchbook.java create mode 100644 src/main/java/net/id/incubus_core/recipe/matchbook/ShortMatch.java create mode 100644 src/main/java/net/id/incubus_core/recipe/matchbook/StringMatch.java create mode 100644 src/main/java/net/id/incubus_core/render/ColorUtils.java create mode 100644 src/main/java/net/id/incubus_core/render/IncubusRenderLayers.java create mode 100644 src/main/java/net/id/incubus_core/render/OverlayRegistrar.java create mode 100644 src/main/java/net/id/incubus_core/render/RenderHelper.java create mode 100644 src/main/java/net/id/incubus_core/resource_conditions/IncubusCoreResourceConditions.java create mode 100644 src/main/java/net/id/incubus_core/status_effects/ZonkedEffect.java create mode 100644 src/main/java/net/id/incubus_core/systems/DefaultMaterials.java create mode 100644 src/main/java/net/id/incubus_core/systems/HeatHelper.java create mode 100644 src/main/java/net/id/incubus_core/systems/HeatIo.java create mode 100644 src/main/java/net/id/incubus_core/systems/KineticIo.java create mode 100644 src/main/java/net/id/incubus_core/systems/Lookups.java create mode 100644 src/main/java/net/id/incubus_core/systems/Material.java create mode 100644 src/main/java/net/id/incubus_core/systems/MaterialProvider.java create mode 100644 src/main/java/net/id/incubus_core/systems/PressureIo.java create mode 100644 src/main/java/net/id/incubus_core/systems/RegistryRegistry.java create mode 100644 src/main/java/net/id/incubus_core/systems/Simulation.java create mode 100644 src/main/java/net/id/incubus_core/task/AbstractTask.java create mode 100644 src/main/java/net/id/incubus_core/task/AsyncTask.java create mode 100644 src/main/java/net/id/incubus_core/task/ScheduledTask.java create mode 100644 src/main/java/net/id/incubus_core/task/TaskInfo.java create mode 100644 src/main/java/net/id/incubus_core/task/Tasks.java create mode 100644 src/main/java/net/id/incubus_core/util/Config.java create mode 100644 src/main/java/net/id/incubus_core/util/EnumExtender.java create mode 100644 src/main/java/net/id/incubus_core/util/FoxDuck.java create mode 100644 src/main/java/net/id/incubus_core/util/IncubusHoliday.java create mode 100644 src/main/java/net/id/incubus_core/util/InventoryWrapper.java create mode 100644 src/main/java/net/id/incubus_core/util/RandomShim.java create mode 100644 src/main/java/net/id/incubus_core/util/RegistryHelper.java create mode 100644 src/main/java/net/id/incubus_core/util/RegistryQueue.java create mode 100644 src/main/java/net/id/incubus_core/util/SeedSupplier.java create mode 100644 src/main/java/net/id/incubus_core/util/TagSuperset.java create mode 100644 src/main/java/net/id/incubus_core/util/TickCounter.java create mode 100644 src/main/java/net/id/incubus_core/util/UuidHelper.java diff --git a/src/main/java/net/id/incubus_core/IncubusCore.java b/src/main/java/net/id/incubus_core/IncubusCore.java new file mode 100644 index 0000000..5554748 --- /dev/null +++ b/src/main/java/net/id/incubus_core/IncubusCore.java @@ -0,0 +1,87 @@ +package net.id.incubus_core; + +import com.mojang.logging.LogUtils; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.loader.api.FabricLoader; +import net.id.incubus_core.condition.IncubusCondition; +import net.id.incubus_core.dev.DevInit; +import net.id.incubus_core.misc.IncubusPlayerData; +import net.id.incubus_core.misc.IncubusSounds; +import net.id.incubus_core.misc.WorthinessChecker; +import net.id.incubus_core.misc.item.IncubusCoreItems; +import net.id.incubus_core.recipe.IncubusRecipeTypes; +import net.id.incubus_core.recipe.matchbook.IncubusMatches; +import net.id.incubus_core.resource_conditions.IncubusCoreResourceConditions; +import net.id.incubus_core.status_effects.ZonkedEffect; +import net.id.incubus_core.systems.RegistryRegistry; +import net.id.incubus_core.util.Config; +import net.minecraft.block.Block; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.entity.effect.StatusEffect; +import net.minecraft.item.Item; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.sound.SoundEvent; +import net.minecraft.util.Identifier; +import org.slf4j.Logger; + +import java.util.Random; +import java.util.SplittableRandom; + +public class IncubusCore implements ModInitializer { + + public static final String MODID = "incubus_core"; + public static final Logger LOG = LogUtils.getLogger(); + + public static final SplittableRandom RANDOM = new SplittableRandom(System.currentTimeMillis()); + + @Override + public void onInitialize() { + var tempRandom = new Random(System.currentTimeMillis()); + if(!FabricLoader.getInstance().isDevelopmentEnvironment() && tempRandom.nextInt(100) == 0) + LOG.info(IncubusCoreInit.HOLY_CONST); + + WorthinessChecker.init(); + RegistryRegistry.init(); + IncubusSounds.init(); + IncubusCoreItems.init(); + IncubusMatches.init(); + IncubusCondition.init(); + IncubusRecipeTypes.init(); + IncubusPlayerData.init(); + IncubusCoreResourceConditions.init(); + + if(FabricLoader.getInstance().isDevelopmentEnvironment()) { + if (Config.getBoolean(locate("devtools"), true)) { + DevInit.commonInit(); + } + } + } + + public static final StatusEffect ZONKED = registerEffect("zonked", new ZonkedEffect()); + + public static Item registerItem(String name, Item item) { + return Registry.register(Registries.ITEM, locate(name), item); + } + + public static Block registerBlock(String name, Block item) { + return Registry.register(Registries.BLOCK, locate(name), item); + } + + public static BlockEntityType registerBE(String name, BlockEntityType item) { + return Registry.register(Registries.BLOCK_ENTITY_TYPE, locate(name), item); + } + + public static StatusEffect registerEffect(String name, StatusEffect item) { + return Registry.register(Registries.STATUS_EFFECT, locate(name), item); + } + + public static SoundEvent registerSoundEvent(String name) { + var id = locate(name); + return Registry.register(Registries.SOUND_EVENT, id, SoundEvent.of(id)); + } + + public static Identifier locate(String path) { + return new Identifier(MODID, path); + } +} diff --git a/src/main/java/net/id/incubus_core/IncubusCoreClient.java b/src/main/java/net/id/incubus_core/IncubusCoreClient.java new file mode 100644 index 0000000..ba69c42 --- /dev/null +++ b/src/main/java/net/id/incubus_core/IncubusCoreClient.java @@ -0,0 +1,20 @@ +package net.id.incubus_core; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.loader.api.FabricLoader; +import net.id.incubus_core.dev.DevInit; +import net.id.incubus_core.util.Config; + +import static net.id.incubus_core.IncubusCore.locate; + +public class IncubusCoreClient implements ClientModInitializer { + + @Override + public void onInitializeClient() { + if(FabricLoader.getInstance().isDevelopmentEnvironment()) { + if (Config.getBoolean(locate("devtools"), true)) + DevInit.clientInit(); + } + } + +} diff --git a/src/main/java/net/id/incubus_core/IncubusCoreInit.java b/src/main/java/net/id/incubus_core/IncubusCoreInit.java new file mode 100644 index 0000000..5435d83 --- /dev/null +++ b/src/main/java/net/id/incubus_core/IncubusCoreInit.java @@ -0,0 +1,85 @@ +package net.id.incubus_core; + +public class IncubusCoreInit { + // I'm not even going to check git blame for this because we all know who did this. + public static final String HOLY_CONST = "hhddmmmmNNNNNNmmmdddhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdMMmyo-::-:::::::////++++++++/////::----------------------------------------::::::::://///////++syddd\n" + + "MMMMMNNNmmmmmmNNNNMMMMNNddhhhhhhhhhhhhhhhhhhhhhhhdMMNhy+oyhdmmNNNNNNmmmmmmmmmmmmmNNNNNNNmmdhyyso+/:-------------------------::::::::::////////////+osh\n" + + "mdhhhyyyyyyyyyyyyyyhhdmNNddhhhyhyhhhhhhyhhhyhhhmNMMMMNmmddhhhyyyyyyyyyyyyyyyyyyyyyyyyyyhhhhhddmmmmNmmdhso+:-----------------:::::::::///////////////+o\n" + + "yyyyyyyyyyyyyyyyyyyyyymmNdhyyyyyyyyyyyyyyyhdmNMMMNmdhyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyhhhhhhddmmmmmhyo+:---------:::::::::://////////////+sh\n" + + "yyyhhhhhhhhhhhhhhhhhhdmNmdhyyyyyyyyyyyyhdNMMMNNdhyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyhhhhhhhhhhhhhdddmmmNmdys+/:-::::::::://///////////+oydNN\n" + + "dddddmmmNNNNNNNNNNNNNNMNdhhhhhhhhhhhhdmMMMNmdhhhhyhhhhhhyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhddddddddddddddmmmmNNNmdyo+/:::::///////////+shmNNdh\n" + + "mmNNMMMMMNNmmmmmmmNNNMMMMNmddhhhhhhdNMMNmdhhhyyyhhhhhyyyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhddddddddddddddmmmmmmmmmmNNNmdyo+//////////+shmNmdhhh\n" + + "MMNNmdhhyyyyyyyyyyyyyyhhdmNMNNdhhdNMMNmdyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyhhhhhhhhhhhhhddddddddddddmmmmmmmmmNNNNdyo+////+shNNmdhyyyy\n" + + "mdhyyyyyyyyyyyyyyyyyyyyyyyyhmNNNMMMNmhyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyhhhhhhhhhhhdddddddddddddmmmmmmmmmmmmNNNmhsoydNNmdhyyyyyy\n" + + "hyyyyyyyyyyyyyyyyyyyyyyyyyyyhmNMMNmdhyyyyyyyyyhhhhhhhhhddddddddddddddhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdddddddddddddmmmmmmmmmmmmmmmmmmmmmNNNMNNmdhyyyyyyyy\n" + + "hhhhhhhhhhhhhhhhhhhhhhhhhhhhmNMNmdhhhhddddmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmNNNNmmmmmmmmddddddddddddddddddddddmmmmmmmmmmmmmmmmmmmmmmmmmNMMMNdhhhhhhhhhhh\n" + + "yyyyyyyyyyyyyyyyyyyyyyyyyydmNMNmdddmmmmmmmmmmmmmmmmmmmmmmmmmmmdddddddddddddddddddddddddmmmmmmmmmmmmddddddddddddmmmmmmmmmmmmmmmmmmmmmNMMNmdhyyyyyyyyyyy\n" + + "yyyyyyyyyyyyyyyyyyyyyyyyydmNNNmmmmNmmNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMNNNNNNmmmddhhhhhhhhddmmmmmmmmmmdddddmmmmmmmmmmmmmmmmmNNMMmdyyyyyyyyyyyyy\n" + + "yyyyyyyyyyyyyyyyyyyyyyyhmNMMNNNMMMMNNNNNNmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmNNNNNNNNNNNNNMMMMMMMMMNNNmmddhhhhhddmmmmmmmmmmmmmmmmmmmmmmmmNNMNdhyyyyyyyyyyyyy\n" + + "yyyyyyyyyyyyyyyyyyhhdmNNMMMMMNNmdddhdddddddddddddddddddddmmmmmmmmmmmmmNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMNNmmmdddddmmmmmmmmmmmmmmmmmNMMMmdhyhyyyyyyyyyyy\n" + + "yyyyyyyyyyyyyyhhdmmNMMMNNmddhhyyyyyyyyyyyyyyyyhhhhhhhhhhhhhhhhhhhhhhdddddddddmmmmmmmmNNNNNNNMMMMMMMMMMMMMMMMMMMNNmmmmmmmmmmmmmmmmmNMMNddyyhyyyyyyyyyyy\n" + + "yyyyyyyyyyyyyhdNMMMNmdhyyyssssssssssssssssssyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyhhhdmNMMMMNNNNNNNNNNNMMMMMMMMMMMMMNNmmmmmmmmmmmNMMmdyyyyyyyyyyyyyyy\n" + + "yyyyyyyyyyydmNMMNmhysssssssssssssssssssssssssyyhhho+ssssssssssyyyyssssssyyyyssyyyyyyhmNMMNmdhyyyhhhddmmNNNNNMMMMMNNNMMMMMMNNmmmmmNNMMddyyyyyyyyyyyyyyy\n" + + "yyyyyyyyyydNNNmdysssssssssoosssssssssssssssyyhdmdd+/+osssysssyhhhhhyyyyyyyyyyyyyyydmNMNmdhyyyyyyyyyyyhhhdmmmNNNNMMMNNNNMMMMMMMMNNMMMNddyyyyyyyyyyyyyyy\n" + + "hhhhhhhhhhNNMmmyssssssso+/::+ossssssssssssyhdddmddo/:oosyyyyyyhhdddddhyyyyyyyyyyydmMNmhyyyyyyyyyyyyyyyyyyyhhddmmmNNNNNNMMMMMMMMMMMMMNdhhhhhhhhhhhhhhhh\n" + + "yyyyyyyyyhmNMmdssssso+/::::/oosssssssssssyyddssyhddo++ssyssssyyhddddmmddhyysssssyhhdhyssssssssssssssssssssssssyyyhddmmmNNNMMMMMNNNMMmdhyyyyyyyyyyyyyyy\n" + + "yyyyyyyyyhmNMmdsoo+/::::::/oooooooooooosyyhdso/shmNNNNmmmddyysyyhhhddddmmmdhyysssssssssssssssssssssssssssssssssssssyyhdmmNNMMMMNNNNMmdyyyyyyyyyyyyyyyy\n" + + "yyyyyyyyyhmNMmdsoo/::::::/+oooooooooossyhhdhshymNMMMNNmmNNNmdhyyhhhddmddhhmNNNmdhyyssssssssssssssssssssssssssssssyyhdmNNMMMMNNmmNNNMddyyyyyyyyyyyyyyyy\n" + + "hhhhhhhhhhmNMmdyss+/::::/+ossssssssssyyhdmmhNmNdyshdmmhyyyhddhhyyhdddmNmdhyyyhmmNNNNNmmddhhhyyyyyyyyyyyyyyhhddmNNNNNmdhhmmNNNNNNNNNNddhhhhhhhhhhhhhhhh\n" + + "hhhhhhhhhhdNNNdhsso/:-:/+oossoooosssyyhddmNMMmy+:-:oydmmdyssssssyyhdddmmNNdysooossyhddmmNNNNNNNNNNNNNNNMMMMNNNmddhysssoshdNNmmmNNNNNddhhhhhhhhhhhhhhhh\n" + + "yyyyyyyyyhhmNMmdsoo/:::++oooooooossyyyhhdmMMds+:----:+sdmNmdhysoossyhhhddmNNdyo+++++++++ooossyyhhdmmNNNNMMNNMMMNNNNmmdhhdmNNmmmmmmNNddyyyyyyyyyyyyyyyy\n" + + "yyyyyyyyyhhhmNNmhoo/::/+oooooooossyyyhhhdNNhs/:::::::--:+shmNNNmdhyyyyyyyhhdNNmho++++///++syhmNNMNNNNNMMMMMMMMMMMMMMNNmNNNNNddmmmmmNddyyyyyyyyyyyyyyyy\n" + + "hhhhhhhhhhhhmNNNmhso/:++ooooooosyyhhhdhmmNhs/::::::::::----:/oyhdmNNNNNmmmmNmmdyo++++oshmNNMNmdys/:.-oymNNNNNNNMMMMNhyoydmNmdmmmmNNNmdhhhhhhhhhhhhhhhh\n" + + "hhhhhhhhhhdddmNMNmdsooooooooooyyhhddddymmmyo////////:::::------::://++ooossoo++///+shmNNmdy+:.`` ``.+ydddddmmmmmNMdhoohdNNmdmmmmNNNmdhhhhhhhhhhhhhhhh\n" + + "hhhhhhhhhhhhhhdNNMNdyoooooooosyhhhdddyhdNdy+////////:::::::::::::::///////////////syhho/-.``` `-/shhddddddddddmmNmhsshdNNhhddmmmNNmmhhhhhhhhhhhhhhhh\n" + + "yyyyhhhhhhhhhhhdmNMMmdyooooosyhhhhhhhodhNhy/////////:::::::::::::://///////////////:-.```` .-+syhhhhhhddmNMNmhsshmNmhhhhdddmmmmdhhhhhhhhhhhhhhh\n" + + "hhhhhhhhhhhhhhhdddmNMMNmhysyyhdhddddysddNhs++//////////+oossyyys+//:////:::::::::////:.``` `-/oyhmmNMMNhso//yhmNmhhhhdddmNNmdhhhhhhhhhhhhhhh\n" + + "hhhhhhdddddddddddddmNNMMMNmmddddddddsddNmhs++++++oyhdNNMMMMMMMmhs/::::::::----..........--://///::-----:+shmmmho:..:/+ydNNdhhhddmmmNNmmddddddddddddddd\n" + + "dhhhhhdddddddddddddddmmNMMMMNNmmddddymdMdho+osydNNMMNmmNNNNNMMmyo::-----..............`````...--::/+++++++//-..````.:+hdNNdhhhddmmmNNmmddddddddddddddd\n" + + "hhhhhhhhhhhhhhddddddddddmmNNMMMMNNmdhmmNhhshmNMNmyyo:.`+smmmmmhs:..:oydhs+:.....````````````````````````````````````:ohdNmhyyhhhdddmNNmdhhhhhhhhhhhhhh\n" + + "dhhhhhhhhhhhhdddddddddddmmmmmNNNMMMMMMMMNMNMmho:.`---//shddddho/`.:yhdy+/++:...`````````````````````````````````````:shmNmyyyhhddddmmNmmdddddddddddddd\n" + + "ddddhhddddddddddddddmmmmmmmmmmmmmNMMNMNMMmy+:.`````:/yhhhhddy+:```+yys/----:..``````````````````````````````````````:yhNNdhhhdddmmmmNNmmdddddddddddddd\n" + + "mmddddddddddddddddmmmmmmmmmmmmmmmNMMMNNmo:-.`` `-/shdddo/.````.+yys+:/o/:.``````````````````````````````````````/ydNNdhhhddmmmmmNNNmmdmdmddddddddd\n" + + "ddddddhhdddddddddddddmmmmmmmmmmmmMMNMNd+:`.`` .:+ss+-.````````.-/++/-.````````````````````..``````````````````+ydNmhhhhhdddddmmNNmmddddddddddddd\n" + + "mdddddhhhhhdddddddddddmmmmmmmmmmNMMNMmy+.`.-::::://++/-.```````````````````````````````````````/y+/.````````````````symNmyyyhhhdddddmmNmmddddddddddddd\n" + + "NNmmdddddddddddddddddmmmmmmmmmmmNMMNNms+.``````....```````````````````````````````````````````/s//.````````````````.yhNNdyyhhhddddddmmNNmmmmmmmmmmmmdd\n" + + "mMNNmmmddddddmmmmmmmmmmmmmmmmmmNMMNNNNy+.``````````````````````````````````````````````````-+yy+:``````````````````+ymNmhhhhdddmmmmmmNNNmmmmmmmmmmmmmm\n" + + "dmMMNmmmmdddddmmmmmmmmmmmmmmmmmNMMNNmNy+-``````````````````````````````````-://::----::/oshhy+-.``````````````````+ymNmhyhhdddddmmmmmNNNNmmmmmmmmmmmmm\n" + + "ddmMMNNmmmmdddddddmmmmmmmmmmmmmNMMNNmmh+-```````````````````````````````./yys+///+++++//:-..````````````````````.ohmmdyyyhhhhhhddddddmmNNmmmmmmmmmmmmm\n" + + "dddmMMMNmmmmmddddmmmmmmmmmmmmmNNMNNddmd+:````````````````..`````````.-/sys/-```````````````````````````````````-shmmdyyyhhhhhhhddddddmmNNmmmmmmmmmmmmm\n" + + "dddddNMMNNmmmmmmmmmmmmmmmmmmmmNMMNNdmmN+/`````````````````.....--://+/:-.`````````````````````````````````````/ydNmdyyhhddddddddmmmmmmNNNNmmmmmmmmmmNN\n" + + "ddddddNMMMNNmmmmmmmmmmmmmmmmmNMMMNmdmmNo+```````````````````````````````````````````````````````````````````.ohmNmhyyhhdddddddmmmmmmmmNNNNmmmmmmmmmNNN\n" + + "hhhhhhhmMMMNNmmmmmmmmmmmmmmmmNMMNNdhdmNy+.`````````````````````````````````````````````````````````````````-shNmdyyyyhhhhhddddddddddddmNNNmmmmmmmmNNNN\n" + + "hhhhhhhhdNMMNNmmmNNNmmmmmmmmmNNMNmhhhdmm+:````````````````````````````````````````````````````````````````:ydNmhssyyyhhhhhhhhhddddddhddNNNmmmmmmmmNNNN\n" + + "hhhhhhhdhdNMMNNmmmNNNNNmmmmmmMMNNmhhddmNs/.``````````````````````````````````````````````````````````````+ymmmyssyyhhhhhhhhdddddddddyddNNNmmmmmmmNNNNN\n" + + "dddddddddddmMMMNmmmNNNNNNNNNNMMNNddddmmNmo/````````````````````````````````````````````````````````````.ohmmdyssyyhhddddddddddmmmmmdsmdNNNNmmmmNNNNNNN\n" + + "hhhhhhhhhhhhdNMMNmmdmNNNNNNNMMMNmhhddmmmNmo/``````````````````````````````````````````````````````````.ohmmhyssyyhhhhhhddddddddddmmhymmMNNNmmNNNNNNNNN\n" + + "hhhhhhhhhhhhhhmMMMmmddmNNNNNMMNNdhyhhddmmNmy/.```````````````````````````````````````````````````````.ohmmhssssyyyhhhhhhhhhhddddddhsddNNNmmmmmNNNNNNNN\n" + + "hhhhhhhhhhhhhhhdNMMNmdhdmNNMMMNmhyyhhddddmmNms:.```````````````````````````````````````````````````..oydmyssssyyyyyyyhhhhhhhdddddhshdNMNNmmmmmNNNNNNNN\n" + + "dhhhhhhhhhhhhhhhdmMMMNdhhdNMNNmhhhhddmmmmmmNNNds/.`````````````````````````````````````````````...--+yhmhssssyyhhyhhhhhddddddddddshdNMMNNNmNNNNNNNNNNN\n" + + "dddddddddddddddddddNMMNmmmNNmdhhhhddmmmmmmmmmNNNmy+:.`````````````````````````````````````...----::/yhddssssyyyhyhyhhhdddddddmmdshdNMMNNNNNNNNNNNNNNNN\n" + + "hhhhhhhhhhhhhhhhhhhhmMMMMMNmhyyyhhhddddddmmmmmmmmNNmyo:-.```````````````````````````....----:::::::oyhmyshyhyyyssyyhhhhhhdddddhshdNMMMNNNmNNNNNNNNNNNN\n" + + "hhhhhhhhhhhhhhhhhhhhhmMMNmdyyyyyyhhhhhdddddddddddhhdmNNdy+:-.```````````````......-----::::::://///yhddyhddhyssssyyyyhhhhhhhdyyhdNNMmNNNmmNNNNNNNNNNNN\n" + + "hhhhhhhhhhhhhhhhhhhdmMMNmhyyyyyhhhhhddddddddddddddshhmMNMNNmhs+:-.........----------::::::///////+ohhmmdmmdhssssyyhhhhhhdhhdyhddNmdddNNNNNNNNNNNNNNNNN\n" + + "dddddddddddddddddddNMNNmmddyyyhhhhhdddddmmmmmmmmmdsshdNhhsydmNNNmhyo+/:------------:::::///////+++shdNNNMmdhssssyyhhhdddhhmddNmNdyyymNNNNNNNNNNNNNNNNN\n" + + "dddddddddddddddddmMMNNNNmmhsyyyyyhhdddddddddmmmmmdssyhNyy///++syhdmNNNmdhso+/::---::::::///////+++yhmMNNNmdysoosyhhhhddhydmNMNNdyoyhNNNNNNNNNNNNNNNNNN\n" + + "hhhhhhhhhhhhhhhhmMMMMNMNmhssssssyyyhhhhhhhdddddddhsshdds+:::::::///+ooshdmNNNNhs/::::::////////+++yhNMNNmmdhooosyyhhhhyohhNMMNdyoshdNNNNNNNNNNNNNNNmmN\n" + + "hhhhhhhhhhhhhhhdMMMMNMMmdsssssssyyyhhhhhhhhhdddhdhshhmyo----::::::::::::/oydNMMNmy+/:::///+osyhdmmNMMNNNNNdhoooyyhhhhy+shmMMNdyooyhNNNNNNNNNNNNNNmdddd\n" + + "dddddddddddddddmMMMmNMNddsssssssyyhhhhhhddddddymdhddmhs/------::::://+oyhmmNNmmNNNmyo///shmNNNmdhyyddhy+yhddsosyyhhddsohhNMNhs+oohdNNNNNNNNNNNNmdhhdmN\n" + + "dddddddddddddddNMMmdMMNdhooooooosyhhhhhhdddddyhdmdNdds+---------:+oydNNNNmys+//+ydNNhs/+hdNhy/:---:osoo/syhhyosyhddddoyhmMmhs++oyhmNNNNNNNNNNNmhyydmMm\n" + + "hhhhhhhhhhhhhhhmMmddMMNdyooooooosyyyhhhhhhhhysddNNNmyo----------+ydNmho//o+//:::/shmNds+shmys:----:://+ossyososyyhhhyoddMmho/+ooyhNNNNNNNNNNmdysshmNNm\n" + + "hhhhhhhhhhhhhhhmmdddMMNdhoooooooosyyyyyyyyhy+shmMMMhs/----------/shNhs/---++//::::+ydNhs+yhds+---:::/oso/+sssysyyhhhohhNmhs/+ooshdNNNNNNNNNmdsoshdNNmd\n" + + "hhhhhhhhhhhhhhhhhhhhNMMmdsooooooosyyyyhhhhho+yhNMMNyo:-----------:oymmyo:--/+//::::/shmhs+ydys:-:::+o+////oyydyyyhhhydmNhs/++ooyhmNNNNNNNNmdhsshdNNmdh\n" + + "hhhhhhhhhhhhhhhhhhhhdMMMmdsoooooosyyhhhhhhyo+yhmMNms+--------------:ohmds+--:++/////+hddy++yhso:::/+//::///yyddhhdddmmNhy//+ooshdNNNNNNNNNddyshdNNmddh\n" + + "hhhhhhhhhhhhhhhhhhhhhdMMMmdyoooooosyyhhhhhoo/hdhmhss/:::-------------:oydhs/--/++/+oydNNmyoyhsy/:/:::::////+shmNddddmNdy+/+oooyhmNNNNNNNNmdyshdNNmddhh\n" + + "hhhhhhhhyhhhhhhhhhhhhhhNMMNmdsooooossyyyyy++/hhyy/:::::::::::::::------:oydhs/:/sydmNNNmNNNmNdys:::::////////symNNdmmmho//+ooshhNNNNNNNNNdhsydmNmdhhhh\n" + + "hhhhhhhhhhhhhhhhhhhhhhhhdNMMMNdysoooosyyyyo+ohdhs:::::::::::::::::::::::::oydddddNNhyssssyydmMdy+::://////////+shNMMNdy//+oooyhmNNNNNNNNmdsshmMmdhhhhh\n" + + "hhhhhhhhhhhhhhhhhhhhhhhhhhdmNMMNNdysoosyyho+shmMdy+::::::::::::::::::::::::/oydNNNhssssssssyhmNys://////////////+shmdh+/+oooshdNNNNNNNNmdhydmMmddhhhdh\n" + + "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhdmNMMMNdhsssysoyhmMMNdy+::::::::::::::::::::::::oydNmyssssssyyyhdNhy://///////////////+/+//+oooyhNNNNNNNNNddyhmMmdhhhhhhh\n" + + "hhhhhhhhhyhyyhhhhhhhhhhhhhhhhhhhhdmNMMNNddhsddmdmMMNmy+/:::::::::::::::::::::/yhNNhyyyyyyyyhddNhs//////////////////////+oooshdMNNNNNNNmdyhdNmdhyhhhhhh\n" + + "hhyyyyyyyyyyyyyhhhhhhhyhhhhhhhhhhhhhhddmNMMMMMmdhdNMMNmho/::::::::::::::::::::oydNNmhhyyyyhhdNmh+/////////////////////+ooooyhNNNNNNNNNdhhdNNdhyyhhyyhy\n" + + "hhhhhyhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhdddhhhhhdNMMNmhs+/:::::::::::://++oshdNMMMNmmmmNNMMMNmhyso+++++++++++ooooooooooyhmNNNNNNNNmdddNNdhhyyhhyhhy\n" + + "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhmMMMNmdddhhhhhhhddmmmNNMMMMMMMNNNNMMMMMNNNMMMMMMNmdhhysoooooooooooosshdNNNNNNNNmdmdmNddhhhhhhhhhh\n" + + "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhmMMMNNmmmNNNNNNNNNNMMMNhhmmyshdmNmmMMNNNNNNNNNMMMMMMMMMNNNNmmmNNNMMMMNNNNNNNNdmmmNddhhhhhhhhhhh"; + +} diff --git a/src/main/java/net/id/incubus_core/annotations/NonnullByDefault.java b/src/main/java/net/id/incubus_core/annotations/NonnullByDefault.java new file mode 100644 index 0000000..aa72cf1 --- /dev/null +++ b/src/main/java/net/id/incubus_core/annotations/NonnullByDefault.java @@ -0,0 +1,17 @@ +package net.id.incubus_core.annotations; + +import javax.annotation.Nonnull; +import javax.annotation.meta.TypeQualifierDefault; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Nonnull +@TypeQualifierDefault({ + ElementType.FIELD, + ElementType.METHOD, + ElementType.PARAMETER}) +@Retention(RetentionPolicy.CLASS) +public @interface NonnullByDefault { + +} \ No newline at end of file diff --git a/src/main/java/net/id/incubus_core/be/IncubusBaseBE.java b/src/main/java/net/id/incubus_core/be/IncubusBaseBE.java new file mode 100644 index 0000000..dde5fb0 --- /dev/null +++ b/src/main/java/net/id/incubus_core/be/IncubusBaseBE.java @@ -0,0 +1,84 @@ +package net.id.incubus_core.be; + +import com.google.common.base.Preconditions; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; + +public abstract class IncubusBaseBE extends BlockEntity { + + private boolean shouldClientRemesh = true; + + public IncubusBaseBE(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); + } + + @Override + public final NbtCompound toInitialChunkDataNbt() { + NbtCompound nbt = super.toInitialChunkDataNbt(); + saveClient(nbt); + nbt.putBoolean("#c", shouldClientRemesh); // mark client tag + shouldClientRemesh = false; + return nbt; + } + + @Override + public final BlockEntityUpdateS2CPacket toUpdatePacket() { + return BlockEntityUpdateS2CPacket.create(this); + } + + public void sync() { + sync(true); + } + + public void sync(boolean shouldRemesh) { + Preconditions.checkNotNull(world); // Maintain distinct failure case from below + if (!(world instanceof ServerWorld)) + throw new IllegalStateException("Cannot call sync() on the logical client! Did you check world.isClient first?"); + + shouldClientRemesh = shouldRemesh | shouldClientRemesh; + ((ServerWorld) world).getChunkManager().markForUpdate(getPos()); + } + + @Override + protected final void writeNbt(NbtCompound nbt) { + save(nbt); + } + + @Override + public final void readNbt(NbtCompound nbt) { + if (nbt.contains("#c")) { + loadClient(nbt); + if (nbt.getBoolean("#c")) { + remesh(); + } + } else { + load(nbt); + } + } + + public void save(NbtCompound nbt) { + super.writeNbt(nbt); + } + + public void load(NbtCompound nbt) { + super.readNbt(nbt); + } + + public abstract void saveClient(NbtCompound nbt); + + public abstract void loadClient(NbtCompound nbt); + + public final void remesh() { + Preconditions.checkNotNull(world); + if (!(world instanceof ClientWorld)) + throw new IllegalStateException("Cannot call remesh() on the server!"); + + world.updateListeners(pos, null, null, 0); + } +} diff --git a/src/main/java/net/id/incubus_core/be/IncubusLazyBlockEntity.java b/src/main/java/net/id/incubus_core/be/IncubusLazyBlockEntity.java new file mode 100644 index 0000000..3078fb3 --- /dev/null +++ b/src/main/java/net/id/incubus_core/be/IncubusLazyBlockEntity.java @@ -0,0 +1,65 @@ +package net.id.incubus_core.be; + +import net.id.incubus_core.IncubusCore; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +@SuppressWarnings("unused") +public abstract class IncubusLazyBlockEntity extends IncubusBaseBE { + + protected boolean initialized; + private final int tickSpacing, tickOffset; + + public IncubusLazyBlockEntity(BlockEntityType type, BlockPos pos, BlockState state, int tickSpacing) { + super(type, pos, state); + this.tickSpacing = tickSpacing; + this.tickOffset = tickSpacing > 1 ? IncubusCore.RANDOM.nextInt(tickSpacing) : 0; + } + + public IncubusLazyBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + this(type, pos, state, 0); + } + + public static void tick(World world, BlockPos pos, BlockState state, T be) { + var entity = (IncubusLazyBlockEntity) be; + if(!entity.initialized) { + entity.initialized = entity.initialize(world, pos, state); + } + if(entity.hasInitialized()) { + entity.tick(pos, state); + if(!world.isClient()) { + entity.tickServer(pos, state); + } + } + } + + protected abstract void tick(BlockPos pos, BlockState state); + + public void tickServer(BlockPos pos, BlockState state) {} + + public boolean allowTick() { + return tickSpacing == 0 || (world.getTime() + tickOffset) % tickSpacing == 0; + } + + protected boolean initialize(World world, BlockPos pos, BlockState state) { + return true; + } + + public boolean hasInitialized() { + return initialized; + } + + public void save(NbtCompound nbt) { + super.save(nbt); + nbt.putBoolean("initialized", initialized); + } + + public void load(NbtCompound nbt) { + super.load(nbt); + initialized = nbt.getBoolean("initialized"); + } +} diff --git a/src/main/java/net/id/incubus_core/be/InventoryBlockEntity.java b/src/main/java/net/id/incubus_core/be/InventoryBlockEntity.java new file mode 100644 index 0000000..ec5addc --- /dev/null +++ b/src/main/java/net/id/incubus_core/be/InventoryBlockEntity.java @@ -0,0 +1,92 @@ +package net.id.incubus_core.be; + +import net.id.incubus_core.util.InventoryWrapper; +import net.minecraft.inventory.SidedInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.Direction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Predicate; + +import static com.google.common.base.Predicates.alwaysFalse; +import static com.google.common.base.Predicates.alwaysTrue; + +/** + * A block entity that has an inventory and a strategy for hoppers. + * @see HopperStrategy + */ +@SuppressWarnings("unused") +public interface InventoryBlockEntity extends InventoryWrapper, SidedInventory { + + @Override + default int[] getAvailableSlots(Direction side) { + return new int[this.getItems().size()]; + } + + @NotNull + HopperStrategy getHopperStrategy(); + + @Override + default boolean canInsert(int slot, ItemStack stack, @Nullable Direction dir) { + return this.getHopperStrategy().canInsert(dir); + } + + @Override + default boolean canExtract(int slot, ItemStack stack, Direction dir) { + return this.getHopperStrategy().canExtract(dir); + } + + /** + * A strategy for how to deal with hoppers and the items that they may want to pass + */ + @SuppressWarnings("Guava") + enum HopperStrategy { + /** + * Hopper items can enter through the top and leave through the bottom + */ + IN_TOP_OUT_BOTTOM(dir -> dir == Direction.UP, dir -> dir == Direction.DOWN), + /** + * Hopper items can enter from any side and leave through the bottom + */ + IN_ANY_OUT_BOTTOM(alwaysTrue(), dir -> dir == Direction.DOWN), + /** + * Hopper items can enter from any side, but cannot leave + */ + IN_ANY(alwaysTrue(), alwaysFalse()), + /** + * Hopper items can leave from any side, but cannot enter + */ + OUT_ANY(alwaysFalse(), alwaysTrue()), + /** + * Hopper items can enter and leave from any side + */ + ALL_PASS(alwaysTrue(), alwaysTrue()), + /** + * Hopper items cannot enter or leave this from any side + */ + NO_PASS(alwaysFalse(), alwaysFalse()); + + private final Predicate<@Nullable Direction> canInsert; + private final Predicate canExtract; + + HopperStrategy(Predicate<@Nullable Direction> canInsert, Predicate canExtract) { + this.canInsert = canInsert; + this.canExtract = canExtract; + } + + /** + * @return Whether this strategy can receive items from the given direction + */ + public final boolean canInsert(@Nullable Direction dir) { + return this.canInsert.test(dir); + } + + /** + * @return Whether this strategy can extract items from the given direction + */ + public final boolean canExtract(Direction dir) { + return this.canExtract.test(dir); + } + } +} diff --git a/src/main/java/net/id/incubus_core/block/TallCropBlock.java b/src/main/java/net/id/incubus_core/block/TallCropBlock.java new file mode 100644 index 0000000..a09ba29 --- /dev/null +++ b/src/main/java/net/id/incubus_core/block/TallCropBlock.java @@ -0,0 +1,213 @@ +package net.id.incubus_core.block; + +import net.minecraft.block.*; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.enums.DoubleBlockHalf; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.ItemStack; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.EnumProperty; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.random.Random; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.world.*; +import org.jetbrains.annotations.Nullable; + +/** + * A crop block that is two blocks tall. + * This class is fully usable on its own, but it is recommended to extend it. + */ +@SuppressWarnings("unused") +public class TallCropBlock extends CropBlock { + public static final EnumProperty HALF = Properties.DOUBLE_BLOCK_HALF; + public final int lastSingleBlockAge; + + /** + * @param lastSingleBlockAge The highest age for which this block is one block tall. + */ + // For PL flax, lastSingleBlockAge is 3. + public TallCropBlock(Settings settings, int lastSingleBlockAge) { + super(settings); + this.lastSingleBlockAge = lastSingleBlockAge; + } + + @Override + public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random){ + this.tryGrow(state, world, pos, random, 25F); + } + + @Override + public void applyGrowth(World world, BlockPos pos, BlockState state) { + if (state.get(HALF) == DoubleBlockHalf.UPPER) { + pos = pos.down(); + state = world.getBlockState(pos); + } + if (!state.isOf(this)){ + return; + } + int newAge = this.getAge(state) + this.getGrowthAmount(world); + int maxAge = this.getMaxAge(); + if (newAge > maxAge) { + newAge = maxAge; + } + + if (newAge > this.lastSingleBlockAge && canGrowUp(world, pos, state, newAge)) { + world.setBlockState(pos, this.withAge(newAge), Block.NOTIFY_LISTENERS); + world.setBlockState(pos.up(), this.withAgeAndHalf(newAge, DoubleBlockHalf.UPPER), Block.NOTIFY_LISTENERS); + } else { + world.setBlockState(pos, this.withAge(Math.min(newAge, lastSingleBlockAge)), Block.NOTIFY_LISTENERS); + } + } + + private boolean canGrowUp(World world, BlockPos pos, BlockState state, int age) { + return world.getBlockState(pos.up()).isOf(this) || world.getBlockState(pos.up()).getMaterial().isReplaceable(); + } + + /** + * Tries to grow the crop. Call me in randomTick(). + * Will not do anything if the block state is upper. + * + * @param upperBound The inverse of the probability of the crop growing with zero moisture. + *
E.g. If upperBound is 25F, the crop with no moisture will grow about 1 in every 25 attempts. + * The more moisture, the more likely. + */ + @SuppressWarnings("SameParameterValue") + protected void tryGrow(BlockState state, ServerWorld world, BlockPos pos, Random random, float upperBound) { + if (state.get(HALF) == DoubleBlockHalf.UPPER) return; + + if (world.getBaseLightLevel(pos, 0) >= 9) { + int age = this.getAge(state); + if (age < this.getMaxAge()) { + float moisture = getAvailableMoisture(this, world, pos); + // More likely if there's more moisture + if (random.nextInt((int) (upperBound / moisture) + 1) == 0) { + if (age >= Block.NOTIFY_LISTENERS) { + if (world.getBlockState(pos.up()).isOf(this) || world.getBlockState(pos.up()).getMaterial().isReplaceable()) { + world.setBlockState(pos, this.withAge(age + 1), Block.NOTIFY_LISTENERS); + world.setBlockState(pos.up(), this.withAgeAndHalf(age + 1, DoubleBlockHalf.UPPER), Block.NOTIFY_LISTENERS); + } + } else { + world.setBlockState(pos, this.withAge(age + 1), Block.NOTIFY_LISTENERS); + } + } + } + } + } + + /** + * Returns the bottom block state for the given age. + */ + @Override + public BlockState withAge(int age) { + return this.withAgeAndHalf(age, DoubleBlockHalf.LOWER); + } + + public BlockState withAgeAndHalf(int age, DoubleBlockHalf half) { + return this.getDefaultState().with(this.getAgeProperty(), age).with(HALF, half); + } + + @Override + protected void appendProperties(StateManager.Builder builder) { + builder.add(HALF).add(AGE); + } + + @Override + public boolean canPlaceAt(BlockState state, WorldView world, BlockPos pos) { + if (state.get(HALF) != DoubleBlockHalf.UPPER) { + BlockPos blockPos = pos.down(); + return this.canPlantOnTop(world.getBlockState(blockPos), world, blockPos); + } else { + BlockState blockState = world.getBlockState(pos.down()); + return blockState.isOf(this) && blockState.get(HALF) == DoubleBlockHalf.LOWER && blockState.get(AGE) > this.lastSingleBlockAge; + } + } + + protected DoubleBlockHalf getHalf(BlockState state) { + return state.get(HALF); + } + + @Override + public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { + if (state.get(HALF) == DoubleBlockHalf.LOWER) { + if (state.get(AGE) <= this.lastSingleBlockAge) { + return super.getOutlineShape(state, world, pos, context); + } else { + // Fill in the bottom block if the plant is two-tall + return Block.createCuboidShape(0, 0, 0,16, 16, 16); + } + } else { + return super.getOutlineShape(this.withAge(state.get(AGE) - this.lastSingleBlockAge - 1), world, pos, context); + } + } + + // below code is (mostly) copied from TallPlantBlock + + public static BlockState withWaterloggedState(WorldView world, BlockPos pos, BlockState state) { + return state.contains(Properties.WATERLOGGED) ? state.with(Properties.WATERLOGGED, world.isWater(pos)) : state; + } + + public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) { + DoubleBlockHalf doubleBlockHalf = state.get(HALF); + if (direction.getAxis() == Direction.Axis.Y && doubleBlockHalf == DoubleBlockHalf.LOWER == (direction == Direction.UP)) { + return (state.get(AGE) <= lastSingleBlockAge || neighborState.isOf(this) && neighborState.get(HALF) != doubleBlockHalf) ? state : Blocks.AIR.getDefaultState(); + } else { + return doubleBlockHalf == DoubleBlockHalf.LOWER && direction == Direction.DOWN && !state.canPlaceAt(world, pos) ? Blocks.AIR.getDefaultState() : super.getStateForNeighborUpdate(state, direction, neighborState, world, pos, neighborPos); + } + } + + @Override + @Nullable + public BlockState getPlacementState(ItemPlacementContext ctx) { + BlockPos blockPos = ctx.getBlockPos(); + World world = ctx.getWorld(); + return blockPos.getY() < world.getTopY() - 1 && world.getBlockState(blockPos.up()).canReplace(ctx) ? this.withAgeAndHalf(0, DoubleBlockHalf.LOWER) : null; + } + + @Override + public void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) { + // Place the other half + if (state.get(HALF) == DoubleBlockHalf.UPPER) { + world.setBlockState(pos.down(), this.withAgeAndHalf(state.get(AGE), DoubleBlockHalf.LOWER), Block.NOTIFY_ALL); + } else { + if (state.get(AGE) > this.lastSingleBlockAge) { + world.setBlockState(pos.up(), this.withAgeAndHalf(state.get(AGE), DoubleBlockHalf.UPPER), Block.NOTIFY_ALL); + } + } + } + + protected static void onBreakInCreative(World world, BlockPos pos, BlockState state, PlayerEntity player) { + if (state.get(HALF) == DoubleBlockHalf.UPPER) { + // Break the other half + BlockPos blockPos = pos.down(); + BlockState blockState = world.getBlockState(blockPos); + if (blockState.isOf(state.getBlock()) && blockState.get(HALF) == DoubleBlockHalf.LOWER) { + BlockState blockState2 = blockState.contains(Properties.WATERLOGGED) && blockState.get(Properties.WATERLOGGED) ? Blocks.WATER.getDefaultState() : Blocks.AIR.getDefaultState(); + world.setBlockState(blockPos, blockState2, Block.SKIP_DROPS | Block.NOTIFY_ALL); + world.syncWorldEvent(player, WorldEvents.BLOCK_BROKEN, blockPos, Block.getRawIdFromState(blockState)); + } + } + } + + @Override + public void onBreak(World world, BlockPos pos, BlockState state, PlayerEntity player) { + if (!world.isClient) { + if (player.isCreative()) { + onBreakInCreative(world, pos, state, player); + } else { + dropStacks(state, world, pos, null, player, player.getMainHandStack()); + } + } + + super.onBreak(world, pos, state, player); + } + + @Override + public void afterBreak(World world, PlayerEntity player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack stack) { + super.afterBreak(world, player, pos, Blocks.AIR.getDefaultState(), blockEntity, stack); + } +} diff --git a/src/main/java/net/id/incubus_core/blocklikeentities/api/BlockLikeEntity.java b/src/main/java/net/id/incubus_core/blocklikeentities/api/BlockLikeEntity.java new file mode 100644 index 0000000..2d92b93 --- /dev/null +++ b/src/main/java/net/id/incubus_core/blocklikeentities/api/BlockLikeEntity.java @@ -0,0 +1,447 @@ +package net.id.incubus_core.blocklikeentities.api; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.id.incubus_core.blocklikeentities.util.PostTickEntity; +import net.minecraft.block.*; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.MovementType; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.data.DataTracker; +import net.minecraft.entity.data.TrackedData; +import net.minecraft.entity.data.TrackedDataHandlerRegistry; +import net.minecraft.fluid.Fluids; +import net.minecraft.item.AutomaticItemPlacementContext; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.nbt.NbtHelper; +import net.minecraft.network.Packet; +import net.minecraft.network.listener.ClientPlayPacketListener; +import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.tag.BlockTags; +import net.minecraft.registry.tag.FluidTags; +import net.minecraft.state.property.Properties; +import net.minecraft.util.crash.CrashReportSection; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.*; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.world.GameRules; +import net.minecraft.world.RaycastContext; +import net.minecraft.world.World; +import net.minecraft.world.WorldEvents; + +import java.util.List; + +/** + * An entity that resembles a block. + */ +public abstract class BlockLikeEntity extends Entity implements PostTickEntity { + private static final TrackedData ORIGIN = DataTracker.registerData(BlockLikeEntity.class, TrackedDataHandlerRegistry.BLOCK_POS); + public int moveTime; + public boolean dropItem = true; + protected NbtCompound blockEntityData; + protected BlockState blockState = Blocks.STONE.getDefaultState(); + protected boolean canSetBlock = true; + protected boolean hurtEntities = false; + protected int fallHurtMax = 40; + protected float fallHurtAmount = 2.0f; + protected boolean collides; + protected boolean partOfSet = false; + + public BlockLikeEntity(EntityType entityType, World world) { + super(entityType, world); + this.moveTime = 0; + } + + public BlockLikeEntity(EntityType entityType, World world, double x, double y, double z, BlockState blockState) { + this(entityType, world); + this.blockState = blockState; + this.intersectionChecked = true; + this.setPosition(x, y, z); + this.setVelocity(Vec3d.ZERO); + this.prevX = x; + this.prevY = y; + this.prevZ = z; + this.setOrigin(new BlockPos(this.getPos())); + } + + public BlockLikeEntity(EntityType entityType, World world, BlockPos pos, BlockState blockState, boolean partOfSet) { + this(entityType, world, pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, blockState); + this.partOfSet = partOfSet; + } + + /** + * Calculates the bounding box based on the blockstate's collision shape. + * If the blockstate doesn't have collision, this method turns collision + * off for this entity and sets the bounding box to the outline shape instead. + * Note: Complex bounding boxes are not supported. These are all rectangular prisms. + * @return The bounding box of this entity + */ + @Override + protected Box calculateBoundingBox() { + if (this.dataTracker == null || this.blockState == null) { + return super.calculateBoundingBox(); + } + BlockPos origin = this.dataTracker.get(ORIGIN); + VoxelShape shape = this.blockState.getCollisionShape(world, origin); + if (shape.isEmpty()) { + this.collides = false; + shape = this.blockState.getOutlineShape(world, origin); + if (shape.isEmpty()) { + return super.calculateBoundingBox(); + } + } else { + this.collides = true; + } + Box box = shape.getBoundingBox(); + return box.offset(getPos().subtract(new Vec3d(0.5, 0, 0.5))); + } + + @Override + public void tick() { + // recalculate fall damage + if (this.hurtEntities) { + double verticalSpeed = Math.abs(this.getVelocity().getY()); + this.fallHurtAmount = this.blockState.getBlock().getHardness() * (float)verticalSpeed; + this.fallHurtMax = Math.max(Math.round(this.fallHurtAmount), this.fallHurtMax); + } + } + + /** + * Override me! Calculate movement. + */ + public abstract void postTickMovement(); + + /** + * Take actions on entities on "collision". + * By default, it replicates the blockstate's behavior on collision. + */ + public void postTickEntityCollision(Entity entity) { + if (!(entity instanceof BlockLikeEntity ble && ble.partOfSet)) { + this.blockState.onEntityCollision(world, this.getBlockPos(), entity); + } + } + + /** + * @return Whether this entity should cease and return to being a block in the world. + */ + public boolean shouldCease() { + if (this.world.isClient) return false; + + BlockPos blockPos = this.getBlockPos(); + boolean isConcrete = this.blockState.getBlock() instanceof ConcretePowderBlock; + + if (isConcrete && this.world.getFluidState(blockPos).isIn(FluidTags.WATER)) { + return true; + } + + double speed = this.getVelocity().lengthSquared(); + + if (isConcrete && speed > 1.0D) { + BlockHitResult blockHitResult = this.world.raycast(new RaycastContext( + new Vec3d(this.prevX, this.prevY, this.prevZ), + new Vec3d(this.getX(), this.getY(), this.getZ()), + RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.SOURCE_ONLY, this) + ); + + if (blockHitResult.getType() != HitResult.Type.MISS + && this.world.getFluidState(blockHitResult.getBlockPos()).isIn(FluidTags.WATER)) { + return true; + } + } + + // Check if it is outside of the world + return this.moveTime > 100 && (blockPos.getY() < this.world.getBottomY() || blockPos.getY() > this.world.getTopY()); + } + + /** + * The big kahuna. You likely don't need to override this method. + * Instead, override the methods that it calls. + */ + public void postTick() { + if (this.blockState.isAir()) { + this.discard(); + return; + } + + this.prevX = this.getX(); + this.prevY = this.getY(); + this.prevZ = this.getZ(); + + // Destroy the block in the world that this is spawned from + // If no block exists, remove this entity (unless part of a set) + if (this.moveTime++ == 0) { + BlockPos blockPos = this.getBlockPos(); + Block block = this.blockState.getBlock(); + if (this.world.getBlockState(blockPos).isOf(block)) { + this.world.removeBlock(blockPos, false); + } else if (!this.world.isClient && !this.partOfSet) { + this.discard(); + return; + } + } + + this.postTickMovement(); + + this.postTickMoveEntities(); + + if (this.shouldCease()) this.cease(); + } + + /** + * You likely won't need to override this method, but it imparts this block's + * momentum onto other entities. + */ + public void postTickMoveEntities() { + if (FallingBlock.canFallThrough(this.blockState)) return; + + List otherEntities = this.world.getOtherEntities(this, getBoundingBox().union(getBoundingBox().offset(0, 0.5, 0))); + for (var entity : otherEntities) { + if (!(entity instanceof BlockLikeEntity) && !entity.noClip && collides) { + entity.move(MovementType.SHULKER_BOX, this.getVelocity()); + entity.setOnGround(true); + + // If we're about to stop touching, give the entity momentum. + if (!entity.getBoundingBox().offset(entity.getVelocity().multiply(2)).intersects( + this.getBoundingBox().offset(this.getVelocity().multiply(2)))) { + entity.setVelocity(entity.getVelocity().add(this.getVelocity())); + } + } + this.postTickEntityCollision(entity); + } + } + + @Override + public boolean handleFallDamage(float distance, float multiplier, DamageSource damageSource) { + int i = MathHelper.ceil(distance - 1.0F); + + if (!this.hurtEntities || i <= 0) { + return false; + } + + boolean flag = this.blockState.isIn(BlockTags.ANVIL); + DamageSource damageSource2 = flag ? DamageSource.anvil(damageSource.getAttacker()) : DamageSource.fallingBlock(this); + float f = Math.min(MathHelper.floor((float)i * this.fallHurtAmount), this.fallHurtMax); + + this.world.getOtherEntities(this, getBoundingBox().union(getBoundingBox().offset(0, 1 + -2 * this.getVelocity().getY(), 0))).forEach(entity -> entity.damage(damageSource2, f)); + + if (flag && f > 0.0F && this.random.nextFloat() < 0.05F + i * 0.05F) { + BlockState blockstate = AnvilBlock.getLandingState(this.blockState); + if (blockstate == null) { + this.canSetBlock = false; + } else this.blockState = blockstate; + } + return false; + } + + @Override + protected void writeCustomDataToNbt(NbtCompound compound) { + compound.put("BlockState", NbtHelper.fromBlockState(this.blockState)); + compound.putInt("Time", this.moveTime); + compound.putBoolean("DropItem", this.dropItem); + compound.putBoolean("HurtEntities", this.hurtEntities); + compound.putFloat("FallHurtAmount", this.fallHurtAmount); + compound.putInt("FallHurtMax", this.fallHurtMax); + if (this.blockEntityData != null) { + compound.put("TileEntityData", this.blockEntityData); + } + } + + @Override + protected void readCustomDataFromNbt(NbtCompound compound) { + this.blockState = NbtHelper.toBlockState(this.world.createCommandRegistryWrapper(RegistryKeys.BLOCK), compound.getCompound("BlockState")); + this.moveTime = compound.getInt("Time"); + if (compound.contains("HurtEntities", 99)) { + this.hurtEntities = compound.getBoolean("HurtEntities"); + this.fallHurtAmount = compound.getFloat("FallHurtAmount"); + this.fallHurtMax = compound.getInt("FallHurtMax"); + } else if (this.blockState.isIn(BlockTags.ANVIL)) { + this.hurtEntities = true; + } + + if (compound.contains("DropItem", 99)) this.dropItem = compound.getBoolean("DropItem"); + + if (compound.contains("TileEntityData", 10)) this.blockEntityData = compound.getCompound("TileEntityData"); + + if (this.blockState.isAir()) this.blockState = Blocks.STONE.getDefaultState(); + } + + @Environment(EnvType.CLIENT) + public World getWorldObj() { + return this.world; + } + + @Override + public boolean doesRenderOnFire() { + return false; + } + + @Override + public void populateCrashReport(CrashReportSection section) { + super.populateCrashReport(section); + section.add("Imitating BlockState", this.blockState.toString()); + } + + public BlockState getBlockState() { + return this.blockState; + } + + public void setHurtEntities(boolean hurtEntities) { + this.hurtEntities = hurtEntities; + } + + /** + * End entity movement and become a block in the world (Removes this entity). + */ + public void cease() { + if (this.isRemoved()) { + return; + } + BlockPos pos = this.getBlockPos(); + BlockState state = this.world.getBlockState(pos); + // I don't like this + if (state.isOf(Blocks.MOVING_PISTON)) { + this.setVelocity(this.getVelocity().multiply(0.7, 0.5, 0.7)); + return; + } + if (!this.trySetBlock()) { + this.breakApart(); + } + } + + /** + * Tries to set the block + * @return {@code true} if the block can be set + */ + public boolean trySetBlock() { + BlockPos blockPos = this.getBlockPos(); + BlockState blockState = this.world.getBlockState(blockPos); + boolean canReplace = blockState.canReplace(new AutomaticItemPlacementContext(this.world, blockPos, Direction.UP, ItemStack.EMPTY, Direction.DOWN)); + boolean canPlace = this.blockState.canPlaceAt(this.world, blockPos); + + if (!this.canSetBlock || !canPlace || !canReplace) + return false; + + if (this.blockState.contains(Properties.WATERLOGGED) && this.world.getFluidState(blockPos).getFluid() == Fluids.WATER) { + this.blockState = this.blockState.with(Properties.WATERLOGGED, true); + } + + if (this.world.setBlockState(blockPos, this.blockState, Block.NOTIFY_ALL)) { + this.discard(); + if (this.blockEntityData != null && this.blockState.hasBlockEntity()) { + BlockEntity blockEntity = this.world.getBlockEntity(blockPos); + if (blockEntity != null) { + NbtCompound compoundTag = blockEntity.createNbt(); + for (String keyName : this.blockEntityData.getKeys()) { + NbtElement tag = this.blockEntityData.get(keyName); + if (tag != null && !"x".equals(keyName) && !"y".equals(keyName) && !"z".equals(keyName)) { + compoundTag.put(keyName, tag.copy()); + } + } + + blockEntity.readNbt(compoundTag); + blockEntity.markDirty(); + } + } + // Stop entities from clipping through the block when it's set + this.postTickMoveEntities(); + return true; + } + return false; + } + + /** + * Break the block, spawn break particles, and drop stacks if it can. + */ + public void breakApart() { + if (this.isRemoved()) return; + + this.discard(); + if (this.dropItem && this.world.getGameRules().getBoolean(GameRules.DO_ENTITY_DROPS)) { + Block.dropStacks(this.blockState, this.world, this.getBlockPos()); + } + // spawn break particles + world.syncWorldEvent(null, WorldEvents.BLOCK_BROKEN, this.getBlockPos(), Block.getRawIdFromState(blockState)); + } + + @Override + public boolean entityDataRequiresOperator() { + return true; + } + + @Override + public Packet createSpawnPacket() { + return new EntitySpawnS2CPacket(this, Block.getRawIdFromState(this.getBlockState()) * (this.partOfSet ? -1 : 1)); + } + + @Override + public void onSpawnPacket(EntitySpawnS2CPacket packet) { + super.onSpawnPacket(packet); + int data = packet.getEntityData(); + this.partOfSet = data < 0; + this.blockState = Block.getStateFromRawId(packet.getEntityData() * (this.partOfSet ? -1 : 1)); + this.intersectionChecked = true; + double d = packet.getX(); + double e = packet.getY(); + double f = packet.getZ(); + this.setPosition(d, e + (double) ((1.0F - this.getHeight()) / 2.0F), f); + this.setOrigin(this.getBlockPos()); + } + + /** + * Aligns this block to another. + * @param other The other block to align with + * @param offset The offset from the other block. this pos - other pos. + * @see BlockLikeSet + */ + public void alignWith(BlockLikeEntity other, Vec3i offset) { + if (this == other) return; + Vec3d newPos = other.getPos().add(Vec3d.of(offset)); + this.setPos(newPos.x, newPos.y, newPos.z); + this.setVelocity(other.getVelocity()); + } + + @Override + public boolean isAttackable() { + return false; + } + + @Environment(EnvType.CLIENT) + public BlockPos getOrigin() { + return this.dataTracker.get(ORIGIN); + } + + public void setOrigin(BlockPos origin) { + this.dataTracker.set(ORIGIN, origin); + this.setPosition(getX(), getY(), getZ()); + } + + public void markPartOfSet() { + this.partOfSet = true; + } + + @Override + protected void initDataTracker() { + this.dataTracker.startTracking(ORIGIN, BlockPos.ORIGIN); + } + + //@Override + //public boolean collides() { + // return !this.isRemoved() && this.collides; + //} + + @Override + public boolean isCollidable() { + return collides; + } + + @Override + public boolean collidesWith(Entity other) { + return !(other instanceof BlockLikeEntity) && super.collidesWith(other); + } +} diff --git a/src/main/java/net/id/incubus_core/blocklikeentities/api/BlockLikeSet.java b/src/main/java/net/id/incubus_core/blocklikeentities/api/BlockLikeSet.java new file mode 100644 index 0000000..feec89d --- /dev/null +++ b/src/main/java/net/id/incubus_core/blocklikeentities/api/BlockLikeSet.java @@ -0,0 +1,192 @@ +package net.id.incubus_core.blocklikeentities.api; + +import net.id.incubus_core.blocklikeentities.util.PostTickEntity; +import net.minecraft.block.BlockState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.World; + +import java.util.*; +import java.util.function.Predicate; + +/** + * An object designed to hold {@link BlockLikeEntity}s together. + *
These are ticked every post-tick until destroyed. (Similar to {@link PostTickEntity}) + * @author Jack Papel + */ +@SuppressWarnings("unused") +public class BlockLikeSet { + private static final Set structures = new HashSet<>(); + private final Map entries; + + /** + * This constructor is useful for doors and other two-block {@link BlockLikeSet}s. + * @param offset entity2's position - entity1's position + */ + public BlockLikeSet(BlockLikeEntity entity1, BlockLikeEntity entity2, Vec3i offset) { + this.entries = Map.of( + Vec3i.ZERO, entity1, + offset, entity2 + ); + } + + /** + * One of the entries in this map must have an offset of {@link Vec3i#ZERO}. + *
"Must" is a strong word. It's recommended. If it isn't the case, the physics won't be consistent, probably. + */ + public BlockLikeSet(Map map) { + this.entries = map; + } + + /** + * @return An unordered iterator of all active {@link BlockLikeSet}s. Active means the set has not landed. + */ + public static Iterator getAllSets() { + return Set.copyOf(structures).iterator(); + } + + /** + * @return An immutable copy of this {@link BlockLikeSet}'s entries. + */ + public Map getEntries(){ + return Map.copyOf(entries); + } + + public void spawn(World world) { + entries.forEach((offset, block) -> { + block.markPartOfSet(); + world.removeBlock(block.getBlockPos(), false); + world.spawnEntity(block); + }); + init(); + } + + // Aligns all BLEs to the master block + protected void synchronize() { + BlockLikeEntity master = getMasterBlock(); + entries.forEach((offset, block) -> block.alignWith(master, offset)); + } + + public void postTick() { + this.synchronize(); + + for (BlockLikeEntity block : entries.values()) { + if (block.isRemoved()) { + // If one block ceases, the rest must as well. + World world = block.world; + BlockState state = block.getBlockState(); + boolean success = world.getBlockState(block.getBlockPos()).isOf(state.getBlock()); + this.land(block, success); + break; + } + } + } + + public void land(BlockLikeEntity lander, boolean success) { + this.synchronize(); + + for (BlockLikeEntity block : entries.values()) { + if (block != lander) { + if (success) { + block.cease(); + } else { + World world = block.world; + BlockState state = block.getBlockState(); + BlockPos pos = block.getBlockPos(); + + // If the block has been set already, remove it. We want this BLE to break. + // This is imperfect - if the block lands on a grass plant, say, then + // the grass plant is already gone. But this should prevent duplications. + if (world.getBlockState(pos).isOf(state.getBlock())) { + world.removeBlock(pos, false); + } + block.breakApart(); + } + } + block.dropItem = false; + } + this.entries.clear(); + this.remove(); + } + + public BlockLikeEntity getMasterBlock() { + if (entries.containsKey(Vec3i.ZERO)) { + return entries.get(Vec3i.ZERO); + } else { + return entries.values().iterator().next(); + } + } + + private void init() { + structures.add(this); + } + + public void remove() { + structures.remove(this); + } + + /** + * A builder intended to aid the creation of {@link BlockLikeSet}s. + */ + @SuppressWarnings("unused") + public static class Builder { + protected final Map entries; + protected final BlockPos origin; + + /** + * @param origin The position of the first block in the {@link BlockLikeSet}. + */ + public Builder(BlockPos origin) { + this.origin = origin; + this.entries = new HashMap<>(2); + } + + /** + * @param entity The BlockLikeEntity to add to the {@link BlockLikeSet}. + * If there has already been an entity added at that location, + * this entity will be ignored and not added. + */ + public Builder add(BlockLikeEntity entity){ + BlockPos pos = entity.getBlockPos(); + if (!isAlreadyInSet(pos)) { + this.entries.put(pos.subtract(origin), entity); + } + return this; + } + + /** + * Allows one to add to a {@link BlockLikeSet} only if a certain condition is met. + * The predicate acts on an immutable copy of the entries so far. + * @param entity The entity that should be added to the {@link BlockLikeSet}. + * @param predicate A {@link Predicate} to test whether the block should be added. + */ + public Builder addIf(BlockLikeEntity entity, Predicate> predicate){ + if (predicate.test(Map.copyOf(entries))){ + return this.add(entity); + } + return this; + } + + /** + * @return The size of the {@link BlockLikeSet} so far. + */ + public int size(){ + return entries.size(); + } + + /** + * @return The {@link BlockLikeSet} that has been built. + */ + public BlockLikeSet build(){ + return new BlockLikeSet(entries); + } + + /** + * @param pos The position of the block to test + * @return Whether the given block is already in the set. + */ + public boolean isAlreadyInSet(BlockPos pos) { + return this.entries.containsKey(pos.subtract(origin)); + } + } +} diff --git a/src/main/java/net/id/incubus_core/blocklikeentities/api/client/BlockLikeEntityRenderer.java b/src/main/java/net/id/incubus_core/blocklikeentities/api/client/BlockLikeEntityRenderer.java new file mode 100644 index 0000000..8e1ef59 --- /dev/null +++ b/src/main/java/net/id/incubus_core/blocklikeentities/api/client/BlockLikeEntityRenderer.java @@ -0,0 +1,56 @@ +package net.id.incubus_core.blocklikeentities.api.client; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.id.incubus_core.blocklikeentities.api.BlockLikeEntity; +import net.minecraft.block.BlockRenderType; +import net.minecraft.block.BlockState; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.OverlayTexture; +import net.minecraft.client.render.RenderLayers; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.block.BlockRenderManager; +import net.minecraft.client.render.entity.EntityRenderer; +import net.minecraft.client.render.entity.EntityRendererFactory; +import net.minecraft.client.texture.SpriteAtlasTexture; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.random.Random; +import net.minecraft.world.World; + +@Environment(EnvType.CLIENT) +public class BlockLikeEntityRenderer extends EntityRenderer { + private final Random random = Random.create(); + + public BlockLikeEntityRenderer(EntityRendererFactory.Context renderManager) { + super(renderManager); + this.shadowRadius = 0.5F; + } + + @Override + public void render(BlockLikeEntity entity, float yaw, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light) { + BlockState blockState = entity.getBlockState(); + + if (blockState.getRenderType() == BlockRenderType.MODEL) { + World world = entity.getWorldObj(); + + if (blockState != world.getBlockState(new BlockPos(entity.getPos())) && blockState.getRenderType() != BlockRenderType.INVISIBLE) { + matrices.push(); + + BlockPos blockpos = new BlockPos(entity.getX(), entity.getBoundingBox().maxY, entity.getZ()); + matrices.translate(-0.5, 0.0, -0.5); + BlockRenderManager blockRenderManager = MinecraftClient.getInstance().getBlockRenderManager(); + blockRenderManager.getModelRenderer().render(world, blockRenderManager.getModel(blockState), blockState, blockpos, matrices, vertexConsumers.getBuffer(RenderLayers.getMovingBlockLayer(blockState)), false, random, blockState.getRenderingSeed(entity.getOrigin()), OverlayTexture.DEFAULT_UV); + matrices.pop(); + super.render(entity, yaw, tickDelta, matrices, vertexConsumers, light); + } + } + } + + @SuppressWarnings("deprecation") + @Override + public Identifier getTexture(BlockLikeEntity entityIn) { + return SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE; + } +} diff --git a/src/main/java/net/id/incubus_core/blocklikeentities/util/PostTickEntity.java b/src/main/java/net/id/incubus_core/blocklikeentities/util/PostTickEntity.java new file mode 100644 index 0000000..b6b1b9a --- /dev/null +++ b/src/main/java/net/id/incubus_core/blocklikeentities/util/PostTickEntity.java @@ -0,0 +1,8 @@ +package net.id.incubus_core.blocklikeentities.util; + +/** + * An interface for entities that need to be ticked after the main tick. + */ +public interface PostTickEntity { + void postTick(); +} \ No newline at end of file diff --git a/src/main/java/net/id/incubus_core/condition/IncubusCondition.java b/src/main/java/net/id/incubus_core/condition/IncubusCondition.java new file mode 100644 index 0000000..2bab3e3 --- /dev/null +++ b/src/main/java/net/id/incubus_core/condition/IncubusCondition.java @@ -0,0 +1,31 @@ +package net.id.incubus_core.condition; + +import dev.onyxstudios.cca.api.v3.component.ComponentKey; +import dev.onyxstudios.cca.api.v3.component.ComponentRegistry; +import dev.onyxstudios.cca.api.v3.entity.EntityComponentFactoryRegistry; +import dev.onyxstudios.cca.api.v3.entity.EntityComponentInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder; +import net.id.incubus_core.IncubusCore; +import net.id.incubus_core.condition.api.Condition; +import net.id.incubus_core.condition.base.ConditionCommand; +import net.id.incubus_core.condition.base.ConditionManager; +import net.minecraft.entity.LivingEntity; +import net.minecraft.registry.Registry; + +public class IncubusCondition implements EntityComponentInitializer { + + public static final ComponentKey CONDITION_MANAGER_KEY = ComponentRegistry.getOrCreate(IncubusCore.locate("condition_manager"), ConditionManager.class); + + public static final Registry CONDITION_REGISTRY = FabricRegistryBuilder.createSimple(Condition.class, IncubusCore.locate("condition")).buildAndRegister(); + + // Called by Cardinal Components + @Override + public void registerEntityComponentFactories(EntityComponentFactoryRegistry registry) { + registry.registerFor(LivingEntity.class, CONDITION_MANAGER_KEY, ConditionManager::new); + } + + public static void init() { + CommandRegistrationCallback.EVENT.register(((dispatcher, dedicated, environment) -> ConditionCommand.register(dispatcher))); + } +} diff --git a/src/main/java/net/id/incubus_core/condition/api/Condition.java b/src/main/java/net/id/incubus_core/condition/api/Condition.java new file mode 100644 index 0000000..8894aad --- /dev/null +++ b/src/main/java/net/id/incubus_core/condition/api/Condition.java @@ -0,0 +1,130 @@ +package net.id.incubus_core.condition.api; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.id.incubus_core.condition.IncubusCondition; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; +import net.minecraft.world.World; + +/** + *

These are supposed to be flywheel objects ya nut. + *
No you don't get any further documentation, go nag jack. + *
[ - insert gigachad image - ] + *
~ Azzy + *
+ *
Jack here, these are conditions. + *
{@link Condition#tick} is ticked for every not-exempt {@code LivingEntity} once every tick, as provided by {@link dev.onyxstudios.cca.api.v3.component.tick.CommonTickingComponent} + *
{@link Condition#tickPlayer} is similarly ticked, but only if the entity is a {@code PlayerEntity} + *
{@link Condition#clientTick} is also similarly ticked, but only if the game is the client + *
All conditions, which extend this class, override these methods with their own effects. + * + *
See the {@link Persistence} class for a description of {@link Persistence#TEMPORARY}, + * {@link Persistence#CHRONIC}, and {@link Persistence#CONSTANT} {@code persistences} are. + *
+ *
Hope this helps! + *
~ Jack.

+ * + * @author AzazelTheDemonLord + * @see Persistence + */ +public abstract class Condition { + + /** + * A tag containing all {@code EntityType}s which cannot get this condition. + */ + public final TagKey> exempt; + /** + * The maximum value for the {@code Temporary} {@link Persistence}. + */ + public final float maxTemp; + /** + * The maximum value for the {@code Chronic} {@link Persistence}. + */ + public final float maxChron; + /** + * The decay rate of the {@code Temporary} {@link Persistence}. + */ + public final float tempDecay; + /** + * The decay rate of the {@code Chronic} {@link Persistence}. + */ + public final float chronDecay; + /** + * The value {@link Condition#maxTemp} and {@link Condition#maxChron} are normalized to. + */ + public final float scalingValue; + /** + * The severity threshold after which the effects of the condition become visible. + */ + public final float visThreshold; + + /** + * @param exempt See {@link Condition#exempt} + * @param maxTemp See {@link Condition#maxTemp} + * @param maxChron See {@link Condition#maxChron} + * @param tempDecay See {@link Condition#tempDecay} + * @param chronDecay See {@link Condition#chronDecay} + * @param scalingValue See {@link Condition#scalingValue} + * @param visThreshold See {@link Condition#visThreshold} + * @see Persistence + */ + public Condition(TagKey> exempt, float maxTemp, float maxChron, float tempDecay, float chronDecay, float scalingValue, float visThreshold) { + this.exempt = exempt; + this.maxTemp = maxTemp; + this.maxChron = maxChron; + this.tempDecay = tempDecay; + this.chronDecay = chronDecay; + this.scalingValue = scalingValue; + this.visThreshold = visThreshold; + } + + /** + * @return The {@link Identifier} for the condition, as registered in {@link IncubusCondition#CONDITION_REGISTRY}. + */ + public final Identifier getId(){ + return IncubusCondition.CONDITION_REGISTRY.getId(this); + } + + /** + * @param entity A {@code LivingEntity} to be tested. + * @return Whether the provided {@code LivingEntity} is exempt from the condition + */ + public final boolean isExempt(LivingEntity entity) { + return entity.getType().isIn(exempt); + } + + /** + * Processes the given entity, and applies any effects from the condition. + * @param world The current {@code World} + * @param entity The {@code LivingEntity} to process + * @param severity The {@link Severity} of the {@code Condition} + * @param rawSeverity The raw {@code float} value of the severity + */ + public abstract void tick(World world, LivingEntity entity, Severity severity, float rawSeverity); + + /** + * Processes the given player, and applies any effects from the condition. + * @param world The current {@code World} + * @param player The {@code PlayerEntity} to process + * @param severity The {@link Severity} of the {@code Condition} + * @param rawSeverity The raw {@code float} value of the severity + * @see Condition#tick + */ + public abstract void tickPlayer(World world, PlayerEntity player, Severity severity, float rawSeverity); + + /** + * Processes the given entity, and applies any effects from the condition. + * @param world The current {@code ClientWorld} + * @param entity The {@code LivingEntity} to process + * @param severity The {@link Severity} of the {@code Condition} + * @param rawSeverity The raw {@code float} value of the severity + * @see Condition#tick + */ + @Environment(EnvType.CLIENT) + public abstract void clientTick(ClientWorld world, LivingEntity entity, Severity severity, float rawSeverity); +} diff --git a/src/main/java/net/id/incubus_core/condition/api/ConditionAPI.java b/src/main/java/net/id/incubus_core/condition/api/ConditionAPI.java new file mode 100644 index 0000000..5aa7f5f --- /dev/null +++ b/src/main/java/net/id/incubus_core/condition/api/ConditionAPI.java @@ -0,0 +1,76 @@ +package net.id.incubus_core.condition.api; + +import net.id.incubus_core.condition.IncubusCondition; +import net.id.incubus_core.condition.base.ConditionManager; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.util.Identifier; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; + +/** + * An API intended to aid use of {@code Condition}s.
+ *
+ * ~ Jack + * @author AzazelTheDemonLord + */ +@SuppressWarnings("unused") +public class ConditionAPI { + /** + * @param type The {@code EntityType} to test + * @return A list of all conditions the given entity is not immune to. + */ + public static List getValidConditions(EntityType type) { + return IncubusCondition.CONDITION_REGISTRY + .stream() + .filter(condition -> !type.isIn(condition.exempt)) + .collect(Collectors.toList()); + } + + /** + * @param id The unique {@code Identifier} of the {@code Condition}. + * @return The {@code Condition} corresponding to the given {@code Identifier} + */ + public static Condition getOrThrow(Identifier id) { + return IncubusCondition.CONDITION_REGISTRY.getOrEmpty(id).orElseThrow((() -> new NoSuchElementException("No ConditionManager found registered for entry: " + id.toString()))); + } + + /** + * @param condition The {@code Condition} to test + * @param entity The entity to test + * @return Whether the given {@code Condition} is currently outwardly + * visible on the given entity. + */ + public static boolean isVisible(Condition condition, LivingEntity entity) { + if(!condition.isExempt(entity)) { + return IncubusCondition.CONDITION_MANAGER_KEY.get(entity).getScaledSeverity(condition) >= condition.visThreshold; + } + return false; + } + + /** + * @param entity The entity you want to get the {@code ConditionManager} of. + * @return The {@code ConditionManager} of the given entity. + */ + public static ConditionManager getConditionManager(LivingEntity entity) { + return IncubusCondition.CONDITION_MANAGER_KEY.get(entity); + } + + /** + * Syncs a given entity's {@code ConditionManager}. + * @param entity The entity whose {@code ConditionManager} you wish to sync. + */ + public static void trySync(LivingEntity entity) { + IncubusCondition.CONDITION_MANAGER_KEY.sync(entity); + } + + /** + * @param condition The {@code Condition} you want the translation string of + * @return The translation string of the given {@code Condition} + */ + public static String getTranslationString(Condition condition) { + return "condition." + condition.getId().getNamespace() + ".condition." + condition.getId().getPath(); + } +} diff --git a/src/main/java/net/id/incubus_core/condition/api/ConditionModifier.java b/src/main/java/net/id/incubus_core/condition/api/ConditionModifier.java new file mode 100644 index 0000000..ea1c39a --- /dev/null +++ b/src/main/java/net/id/incubus_core/condition/api/ConditionModifier.java @@ -0,0 +1,24 @@ +package net.id.incubus_core.condition.api; + +public interface ConditionModifier { + + default float getDecayMultiplier(Condition condition) { + return 1; + } + + default float getScalingMultiplier(Condition condition) { + return 1; + } + + default float getScalingOffset(Condition condition) { + return 0; + } + + default float getSeverityMultiplier(Condition condition) { + return 1; + } + + default float getConstantCondition(Condition condition) { + return 0; + } +} \ No newline at end of file diff --git a/src/main/java/net/id/incubus_core/condition/api/Persistence.java b/src/main/java/net/id/incubus_core/condition/api/Persistence.java new file mode 100644 index 0000000..195c94b --- /dev/null +++ b/src/main/java/net/id/incubus_core/condition/api/Persistence.java @@ -0,0 +1,44 @@ +package net.id.incubus_core.condition.api; + +/** + * {@code Persistence} is how much a {@code Condition} persists. + * There are three main types: {@link Persistence#TEMPORARY}, + * {@link Persistence#CHRONIC}, and {@link Persistence#CONSTANT} + *

+ * ~ Jack + * @author AzazelTheDemonLord + * @see Persistence#TEMPORARY + * @see Persistence#CHRONIC + * @see Persistence#CONSTANT + */ +public enum Persistence { + /** + * This {@code Persistence} is what you'd expect. + * It's given by things like projectiles, for example, and goes away with time. + */ + TEMPORARY("condition.persistence.temporary"), + /** + * This is similar to the {@link Persistence#TEMPORARY} {@code Persistence}. + * Depending on the {@code Condition}, it may be reduced only through + * consumables, or it may just go down slower. + */ + CHRONIC("condition.persistence.chronic"), + /** + * The {@code CONSTANT} {@code Persistence} is given by things like + * armors and trinkets that apply conditions. It goes away when the + * armor or trinket is removed. + */ + CONSTANT("condition.persistence.constant"); + + /** + * The translation key.
e.g. {@code "condition.persistence.temporary"}. + */ + public final String translation; + + /** + * @param translation {@link Persistence#translation} + */ + Persistence(String translation) { + this.translation = translation; + } +} diff --git a/src/main/java/net/id/incubus_core/condition/api/Severity.java b/src/main/java/net/id/incubus_core/condition/api/Severity.java new file mode 100644 index 0000000..d828009 --- /dev/null +++ b/src/main/java/net/id/incubus_core/condition/api/Severity.java @@ -0,0 +1,81 @@ +package net.id.incubus_core.condition.api; + +/** + * The severity intervals at which a {@code Condition} is processed. + *
{@code Condition}s may use these to apply certain affects when + * the {@code Severity} is greater than {@code Severity.EXTREME}, rather + * than a lower {@code Severity}, for example. + *

+ * ~ Jack + * @author AzazelTheDemonLord + * @see Severity#NEGLIGIBLE + * @see Severity#MILD + * @see Severity#ACUTE + * @see Severity#DIRE + * @see Severity#EXTREME + */ +public enum Severity { + /** + * The lowest {@code Severity}. The intent of this is to describe when + * a {@code Condition} is practically zero - hence the name {@code NEGLIGIBLE}. + */ + NEGLIGIBLE(0.0F), + /** + * The intent of this is to be the lowest {@code Severity} that + * makes a {@code Condition} do things. + */ + MILD(0.2F), + /** + * The intent of this is to be the {@code Severity} at which the + * {@code Condition} has "gotten pretty bad". + */ + ACUTE(0.525F), + /** + * The {@code Severity} at which the {@code Condition} has "gotten + * really, really, bad". + */ + DIRE(0.8F), + /** + * The last {@code Severity} level. This is as bad as the + * {@code Condition} gets. + */ + EXTREME(0.95F); + + /** + * The percent at which the {@code Severity} becomes this {@code Severity}. + * @see Severity#getSeverity + */ + public final float triggerPercent; + + /** + * @param triggerPercent {@link Severity#triggerPercent} + */ + Severity(float triggerPercent) { + this.triggerPercent = triggerPercent; + } + /** + * Converts a raw severity percent to a {@code Severity}. + * @param rawSeverity The raw percent of how strong the {@code Condition} is + * @return The greatest {@code Severity} less than {@code rawSeverity} + */ + public static Severity getSeverity(float rawSeverity) { + var severity = NEGLIGIBLE; + for (Severity value : values()) { + if(rawSeverity > value.triggerPercent) + severity = value; + } + return severity; + } + /** + * Returns whether a given {@code Severity} is equal or more severe that this {@code Severity} + * @param severity The {@code Severity} to test + * @return Whether {@code severity} is equal or more severe than this {@code Severity} + */ + public boolean isAsOrMoreSevere(Severity severity) { + return this.triggerPercent >= severity.triggerPercent; + } + + public String getTranslationKey(){ + return "condition.severity." + this.name().toLowerCase(); + } +} diff --git a/src/main/java/net/id/incubus_core/condition/api/package-info.java b/src/main/java/net/id/incubus_core/condition/api/package-info.java new file mode 100644 index 0000000..2bad82d --- /dev/null +++ b/src/main/java/net/id/incubus_core/condition/api/package-info.java @@ -0,0 +1,2 @@ +@javax.annotation.ParametersAreNonnullByDefault +package net.id.incubus_core.condition.api; \ No newline at end of file diff --git a/src/main/java/net/id/incubus_core/condition/base/ConditionCommand.java b/src/main/java/net/id/incubus_core/condition/base/ConditionCommand.java new file mode 100644 index 0000000..1f855f4 --- /dev/null +++ b/src/main/java/net/id/incubus_core/condition/base/ConditionCommand.java @@ -0,0 +1,216 @@ +package net.id.incubus_core.condition.base; + + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.FloatArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.id.incubus_core.IncubusCore; +import net.id.incubus_core.condition.IncubusCondition; +import net.id.incubus_core.condition.api.Condition; +import net.id.incubus_core.condition.api.ConditionAPI; +import net.id.incubus_core.condition.api.Persistence; +import net.id.incubus_core.condition.api.Severity; +import net.minecraft.command.argument.EntityArgumentType; +import net.minecraft.command.argument.IdentifierArgumentType; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.CompletableFuture; + +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; + +public class ConditionCommand { + + public static final ConditionSuggester CONDITION_SUGGESTER = new ConditionSuggester(); + public static final SeveritySuggester SEVERITY_SUGGESTER = new SeveritySuggester(); + public static final PersistenceSuggester PERSISTENCE_SUGGESTER = new PersistenceSuggester(); + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register( + literal(IncubusCore.MODID + ":condition") + .requires((source) -> source.hasPermissionLevel(2)) + .then(literal("query") + .executes(context -> printCondition(context.getSource(), null, null)) + .then(argument("target", EntityArgumentType.entities()) + .executes(context -> printCondition(context.getSource(), EntityArgumentType.getEntities(context, "target"), null)) + .then(argument("condition", IdentifierArgumentType.identifier()).suggests(CONDITION_SUGGESTER) + .executes((context -> printCondition(context.getSource(), EntityArgumentType.getEntities(context, "target"), IdentifierArgumentType.getIdentifier(context, "condition"))))))) + .then(literal("assign") + .then(argument("target", EntityArgumentType.entities()) + .then(argument("condition", IdentifierArgumentType.identifier()).suggests(CONDITION_SUGGESTER) + .then(argument("persistence", StringArgumentType.word()).suggests(PERSISTENCE_SUGGESTER) + .then(argument("value", FloatArgumentType.floatArg()).suggests(SEVERITY_SUGGESTER) + .executes(context -> setCondition(context.getSource(), EntityArgumentType.getEntity(context, "target"), IdentifierArgumentType.getIdentifier(context, "condition"), FloatArgumentType.getFloat(context, "value"), StringArgumentType.getString(context, "persistence")))))))) + .then(literal("clear") + .executes(context -> clearCondition(context.getSource(), null, null)) + .then(argument("target", EntityArgumentType.entities()) + .executes(context -> clearCondition(context.getSource(), EntityArgumentType.getEntities(context, "target"), null)) + .then(argument("condition", IdentifierArgumentType.identifier()).suggests(CONDITION_SUGGESTER) + .executes(context -> clearCondition(context.getSource(), EntityArgumentType.getEntities(context, "target"), IdentifierArgumentType.getIdentifier(context, "condition")))))) + ); + } + + private static int clearCondition(ServerCommandSource source, Collection entities, Identifier attributeId) { + entities = handleNullEntity(source, entities); + entities.forEach(entity -> { + if(entity instanceof LivingEntity target) { + var conditions = handleNullCondition(source, attributeId, target); + if (!conditions.isEmpty()) { + conditions.forEach(condition -> { + ConditionManager manager = ConditionAPI.getConditionManager(target); + manager.set(condition, Persistence.TEMPORARY, 0); + manager.set(condition, Persistence.CHRONIC, 0); + + ConditionAPI.trySync(target); + }); + + source.sendFeedback( + Text.translatable( + "commands.incubus_core.condition.success.clear.individual", + conditions.size(), entity.getDisplayName() + ), + true + ); + } + } + }); + source.sendFeedback(Text.translatable("commands.incubus_core.condition.success.clear"), true); + return 1; + } + + private static int printCondition(ServerCommandSource source, @Nullable Collection entities, Identifier attributeId) { + entities = handleNullEntity(source, entities); + entities.forEach(entity -> { + if(entity instanceof LivingEntity target) { + var conditions = handleNullCondition(source, attributeId, target); + conditions.forEach(condition -> { + var rawSeverity = ConditionAPI.getConditionManager(target).getScaledSeverity(condition); + var severity = Severity.getSeverity(rawSeverity); + + if (!condition.isExempt(target)) { + // todo: also print who is being queried + source.sendFeedback(Text.translatable("commands.incubus_core.condition.success.query", Text.translatable(ConditionAPI.getTranslationString(condition)), Text.translatable(severity.getTranslationKey()), rawSeverity), false); + } else { + source.sendError(Text.translatable("commands.incubus_core.condition.failure.query", Text.translatable(ConditionAPI.getTranslationString(condition)))); + } + }); + } + }); + return 1; + } + + private static int setCondition(ServerCommandSource source, Entity entity, Identifier attributeId, float value, String persistenceString) { + if(entity instanceof LivingEntity target) { + Condition condition; + Persistence persistence; + + try { + condition = ConditionAPI.getOrThrow(attributeId); + } catch (NoSuchElementException e) { + source.sendError(Text.translatable("commands.incubus_core.condition.failure.get_condition", attributeId)); + return 1; + } + try { + persistence = Persistence.valueOf(persistenceString); + } catch (NoSuchElementException e) { + source.sendError(Text.translatable("commands.incubus_core.condition.failure.get_persistence", persistenceString)); + return 1; + } + + var manager = ConditionAPI.getConditionManager(target); + + if(!condition.isExempt(target)) { + if(manager.set(condition, persistence, value)) { + var rawSeverity = ConditionAPI.getConditionManager(target).getScaledSeverity(condition); + var severity = Severity.getSeverity(rawSeverity); + + // todo: also print who the condition is being assigned to + source.sendFeedback(Text.translatable("commands.incubus_core.condition.success.assign", Text.translatable(ConditionAPI.getTranslationString(condition)), Text.translatable(severity.getTranslationKey()), rawSeverity), false); + ConditionAPI.trySync(target); + } + else { + source.sendError(Text.translatable("commands.incubus_core.condition.failure.assign")); + } + } + } + return 1; + } + + private static Collection handleNullEntity(ServerCommandSource source, Collection entities){ + if (entities == null) { + try { + entities = List.of(source.getEntityOrThrow()); + } catch (Exception e) { + source.sendError(Text.translatable("commands.incubus_core.condition.failure.get_entity")); + entities = List.of(); + } + } + return entities; + } + + private static Collection handleNullCondition(ServerCommandSource source, Identifier attributeId, LivingEntity target){ + Collection conditions; + if (attributeId != null) { + try { + conditions = List.of(ConditionAPI.getOrThrow(attributeId)); + } catch (NoSuchElementException e) { + source.sendError(Text.translatable("commands.incubus_core.condition.failure.get_condition", attributeId)); + conditions = List.of(); + } + } else { + conditions = ConditionAPI.getValidConditions(target.getType()); + } + return conditions; + } + + public static class ConditionSuggester implements SuggestionProvider { + @Override + public CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException { + IncubusCondition.CONDITION_REGISTRY.getIds().forEach(id -> builder.suggest(id.toString())); + return builder.buildFuture(); + } + } + + public static class SeveritySuggester implements SuggestionProvider { + @Override + public CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException { + Condition condition; + + try { + condition = ConditionAPI.getOrThrow(IdentifierArgumentType.getIdentifier(context, "condition")); + } catch (Exception e){ + return builder.suggest(0).buildFuture(); + } + + float max = condition.scalingValue; + + Arrays.stream(Severity.values()).sorted().forEach((severity) -> builder.suggest(Float.toString(Math.round(max * severity.triggerPercent)))); + builder.suggest(Float.toString(max)); + + return builder.buildFuture(); + } + } + + public static class PersistenceSuggester implements SuggestionProvider { + @Override + public CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException { + builder.suggest(Persistence.TEMPORARY.name()); + builder.suggest(Persistence.CHRONIC.name()); + return builder.buildFuture(); + } + } +} diff --git a/src/main/java/net/id/incubus_core/condition/base/ConditionManager.java b/src/main/java/net/id/incubus_core/condition/base/ConditionManager.java new file mode 100644 index 0000000..d4c5b86 --- /dev/null +++ b/src/main/java/net/id/incubus_core/condition/base/ConditionManager.java @@ -0,0 +1,304 @@ +package net.id.incubus_core.condition.base; + +import dev.emi.trinkets.api.TrinketsApi; +import dev.onyxstudios.cca.api.v3.component.sync.AutoSyncedComponent; +import dev.onyxstudios.cca.api.v3.component.tick.CommonTickingComponent; +import dev.onyxstudios.cca.api.v3.entity.PlayerComponent; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.id.incubus_core.condition.api.*; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.util.Pair; +import net.minecraft.util.math.MathHelper; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +// TODO finish docs +@SuppressWarnings("unused") +public class ConditionManager implements AutoSyncedComponent, CommonTickingComponent, PlayerComponent { + + private final LivingEntity target; + private final List conditionTrackers = new ArrayList<>(); + + public ConditionManager(LivingEntity target) { + this.target = target; + var conditions = ConditionAPI.getValidConditions(target.getType()); + conditions.forEach(condition -> conditionTrackers.add(new ConditionTracker(condition))); + } + + @Override + public void tick() { + conditionTrackers.forEach(tracker -> { + var condition = tracker.getCondition(); + + float rawSeverity = getScaledSeverity(condition); + var severity = Severity.getSeverity(rawSeverity); + + if(target instanceof PlayerEntity player) { + condition.tickPlayer(player.world, player, severity, rawSeverity); + } + else { + condition.tick(target.world, target, severity, rawSeverity); + } + + tracker.remove(Persistence.TEMPORARY, getScaledDecay(Persistence.TEMPORARY, condition)); + tracker.remove(Persistence.CHRONIC, getScaledDecay(Persistence.CHRONIC, condition)); + }); + } + + @Override + @Environment(EnvType.CLIENT) + public void clientTick() { + CommonTickingComponent.super.clientTick(); + conditionTrackers.forEach(tracker -> { + var condition = tracker.getCondition(); + + float rawSeverity = getScaledSeverity(condition); + var severity = Severity.getSeverity(rawSeverity); + + condition.clientTick((ClientWorld) target.world, target, severity, rawSeverity); + }); + } + + public boolean set(Condition condition, Persistence persistence, float value) { + return Optional.ofNullable(this.getConditionTracker(condition)).map(tracker -> { + switch (persistence) { + case TEMPORARY -> tracker.tempVal = value; + case CHRONIC -> tracker.chronVal = value; + case CONSTANT -> throw new IllegalArgumentException("Constant condition values may not be directly edited"); + } + ConditionAPI.trySync(this.target); + return true; + }).orElse(false); + } + + /** + * Adds the specified amount to the specified persistence. + */ + public void add(Condition condition, Persistence persistence, float amount) { + Optional.ofNullable(this.getConditionTracker(condition)).ifPresent(tracker -> tracker.add(persistence, amount)); + ConditionAPI.trySync(this.target); + } + + /** + * Removes the specified amount from the specified persistence + */ + public void remove(Condition condition, Persistence persistence, float amount) { + Optional.ofNullable(this.getConditionTracker(condition)).ifPresent(tracker -> tracker.remove(persistence, amount)); + ConditionAPI.trySync(this.target); + } + + /** + * Clears all conditions for this entity + */ + public boolean removeAll(){ + return conditionTrackers.stream().allMatch((tracker) -> + set(tracker.parent, Persistence.TEMPORARY, 0) + && set(tracker.parent, Persistence.CHRONIC, 0)); + } + + /** + * Removes a specified percentage of the specified condition. + * @param amount The percentage of the condition to remove. 0.00 means no change, 1.00 means remove all. + */ + public void removeScaled(Condition condition, float amount) { + Optional.ofNullable(this.getConditionTracker(condition)).ifPresent(tracker -> { + float partial = tracker.getPartialCondition(); + float tempPart = tracker.tempVal / partial; + float chronPart = tracker.chronVal / partial; + tracker.remove(Persistence.TEMPORARY, amount * tempPart); + tracker.remove(Persistence.CHRONIC, amount * chronPart); + }); + } + + /** + * @return Whether this entity is immune to the specified condition. + */ + public boolean isImmuneTo(Condition condition) { + return conditionTrackers.stream().noneMatch(tracker -> tracker.getCondition() == condition); + } + + private ConditionTracker getConditionTracker(Condition condition){ + for (var tracker : conditionTrackers) { + if (tracker.getCondition() == condition){ + return tracker; + } + } + return null; + } + + /** + * Tries to apply the specified condition with the specified persistence and amount. + * @return Whether the condition could be applied. + */ + public boolean tryApply(Condition condition, Persistence persistence, float amount) { + var tracker = this.getConditionTracker(condition); + if(tracker != null && persistence != Persistence.CONSTANT && !isImmuneTo(condition)) { + tracker.add(persistence, amount); + return true; + } + return false; + } + + public float getScaledDecay(Persistence persistence, @NotNull Condition condition) { + return switch (persistence) { + case TEMPORARY -> condition.tempDecay; + case CHRONIC -> condition.chronDecay; + default -> 0; + } * getDecayMultiplier(condition); + } + + public float getDecayMultiplier(@NotNull Condition condition) { + var modifiers = getActiveModifiers(); + if(!modifiers.isEmpty()) { + return (float) modifiers.stream() + .mapToDouble(mod -> mod.getDecayMultiplier(condition)) + .average().orElse(1); + } + return 1; + } + + public float getScaledSeverity(@NotNull Condition condition) { + return MathHelper.clamp((getRawCondition(condition) / getScalingValueForCondition(condition)) * getSeverityMultiplier(condition), 0, 1); + } + + public float getSeverityMultiplier(@NotNull Condition condition) { + return (float) getActiveModifiers().stream().mapToDouble(mod -> mod.getSeverityMultiplier(condition)).average().orElse(1); + } + + public float getScalingValueForCondition(@NotNull Condition condition) { + var modifiers = getActiveModifiers(); + float scalingValue = condition.scalingValue; + scalingValue *= modifiers.stream().mapToDouble(mod -> mod.getScalingMultiplier(condition)).average().orElse(1); + scalingValue += modifiers.stream().mapToDouble(mod -> mod.getScalingOffset(condition)).sum(); + return scalingValue; + } + + public float getRawCondition(@NotNull Condition condition) { + return Optional.ofNullable(this.getConditionTracker(condition)).map(tracker -> { + float partial = tracker.getPartialCondition(); + partial += getActiveModifiers().stream().mapToDouble(mod -> mod.getConstantCondition(condition)).sum(); + return partial; + }).orElseThrow(() -> new IllegalStateException("HOW in the FUCK do you get an invalid condition here: " + condition.getId())); + } + + public List getActiveModifiers() { + List modifiers = new ArrayList<>(); + if(target instanceof PlayerEntity player) { + // TODO: Make this only use trinkets if it's installed, otherwise + // start with an empty list. + var stacks = + TrinketsApi.TRINKET_COMPONENT.get(player) + .getEquipped(stack -> stack.getItem() instanceof ConditionModifier) + .stream().map(Pair::getRight).collect(Collectors.toList()); + + player.getArmorItems().forEach(stack -> { + if(stack.getItem() instanceof ConditionModifier) + stacks.add(stack); + }); + + if(player.getMainHandStack().getItem() instanceof ConditionModifier) + stacks.add(player.getMainHandStack()); + + if(player.getOffHandStack().getItem() instanceof ConditionModifier) + stacks.add(player.getOffHandStack()); + + stacks.forEach(stack -> modifiers.add((ConditionModifier) stack.getItem())); + } + + target.getActiveStatusEffects().forEach((statusEffect, statusEffectInstance) -> { + if(statusEffect instanceof ConditionModifier modifier) { + modifiers.add(modifier); + } + }); + + return modifiers; + } + + @Override + public void readFromNbt(NbtCompound tag) { + conditionTrackers.forEach(tracker -> { + var condition = tracker.getCondition(); + if(tag.contains(condition.getId().toString())) { + tracker.fromNbt((NbtCompound) tag.get(condition.getId().toString())); + } + }); + } + + @Override + public void writeToNbt(NbtCompound tag) { + conditionTrackers.forEach(tracker -> { + var nbt = new NbtCompound(); + tracker.writeToNbt(nbt); + tag.put(tracker.getCondition().getId().toString(), nbt); + }); + } + + @Override + public void copyFrom(ConditionManager other) { + PlayerComponent.super.copyFrom(other); + } + + @Override + public void copyForRespawn(ConditionManager original, boolean lossless, boolean keepInventory, boolean sameCharacter) { + if(sameCharacter) { + PlayerComponent.super.copyForRespawn(original, lossless, keepInventory, sameCharacter); + } + } + + @Override + public boolean shouldCopyForRespawn(boolean lossless, boolean keepInventory, boolean sameCharacter) { + return false; + } + + private static class ConditionTracker { + + private final Condition parent; + + private float tempVal; + private float chronVal; + + public ConditionTracker(Condition parent) { + this.parent = parent; + } + + public Condition getCondition(){ + return parent; + } + + public void add(Persistence persistence, float amount) { + switch (persistence) { + case TEMPORARY -> tempVal = Math.min(parent.maxTemp, tempVal + amount); + case CHRONIC -> chronVal = Math.min(parent.maxChron, chronVal + amount); + } + } + + public void remove(Persistence persistence, float amount) { + switch (persistence) { + case TEMPORARY -> tempVal = Math.max(0, tempVal - amount); + case CHRONIC -> chronVal = Math.max(0, chronVal - amount); + } + } + + public float getPartialCondition() { + return tempVal + chronVal; + } + + public void fromNbt(NbtCompound nbt) { + tempVal = nbt.getFloat("temporary"); + chronVal = nbt.getFloat("chronic"); + } + + public void writeToNbt(NbtCompound nbt) { + nbt.putFloat("temporary", tempVal); + nbt.putFloat("chronic", chronVal); + } + } +} diff --git a/src/main/java/net/id/incubus_core/dev/DevInit.java b/src/main/java/net/id/incubus_core/dev/DevInit.java new file mode 100644 index 0000000..49a9009 --- /dev/null +++ b/src/main/java/net/id/incubus_core/dev/DevInit.java @@ -0,0 +1,92 @@ +package net.id.incubus_core.dev; + +import net.fabricmc.fabric.api.item.v1.FabricItemSettings; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; +import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; +import net.fabricmc.loader.api.FabricLoader; +import net.id.incubus_core.IncubusCore; +import net.id.incubus_core.condition.IncubusCondition; +import net.id.incubus_core.condition.api.Condition; +import net.id.incubus_core.condition.api.Severity; +import net.id.incubus_core.dev.block.TestFurnaceBlock; +import net.id.incubus_core.dev.block.TestFurnaceBlockEntity; +import net.id.incubus_core.dev.item.EntityDeathMessageTestItem; +import net.id.incubus_core.dev.recipe.TestRecipeType; +import net.id.incubus_core.util.Config; +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.BlockItem; +import net.minecraft.item.Item; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.recipe.RecipeType; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; +import net.minecraft.world.World; +import org.spongepowered.asm.mixin.MixinEnvironment; + +import static net.id.incubus_core.IncubusCore.*; + +/** + * Development only stuff. + *

+ * Used for testing internal things without littering production environments. + */ +public final class DevInit { + static { + if (!FabricLoader.getInstance().isDevelopmentEnvironment()) { + throw new RuntimeException("Incubus Core development classes loaded in production, this should not happen!"); + } + } + + private static final Item ENTITY_DEATH_MESSAGE_ITEM = new EntityDeathMessageTestItem(new FabricItemSettings()); + + private static final Block TEST_FURNACE_BLOCK = new TestFurnaceBlock(FabricBlockSettings.copyOf(Blocks.OBSIDIAN)); + private static final Item TEST_FURNACE_BLOCKITEM = new BlockItem(TEST_FURNACE_BLOCK, new FabricItemSettings()); + public static final BlockEntityType TEST_FURNACE_BLOCK_ENTITY_TYPE = FabricBlockEntityTypeBuilder.create(TestFurnaceBlockEntity::new, TEST_FURNACE_BLOCK).build(); + public static final RecipeType TEST_RECIPE_TYPE = RecipeType.register("incubus_core:test_recipe_type"); + public static final RecipeSerializer TEST_RECIPE_SERIALIZER = new TestRecipeType.Serializer(); + + private static final Condition TEST_CONDITION = new Condition(TagKey.of(RegistryKeys.ENTITY_TYPE, new Identifier("a")), 100, 100, 5, 5, 1, 50) { + @Override + public void tick(World world, LivingEntity entity, Severity severity, float rawSeverity) { + } + + @Override + public void tickPlayer(World world, PlayerEntity player, Severity severity, float rawSeverity) { + if (severity.isAsOrMoreSevere(Severity.MILD)) { + player.addStatusEffect(new StatusEffectInstance(ZONKED, 10)); + } + } + + @Override + public void clientTick(ClientWorld world, LivingEntity entity, Severity severity, float rawSeverity) { + } + }; + + public static void commonInit() { + // This should catch any mixin errors that occur later in runtime. + // This also requires incubus_core.devtools=true + if (Config.getBoolean(locate("mixin_audit"), false)) { + LOG.info("Running mixin audit because we are in debug mode..."); + MixinEnvironment.getCurrentEnvironment().audit(); + } + + registerItem("entity_death_message_item", ENTITY_DEATH_MESSAGE_ITEM); + registerBlock("test_furnace", TEST_FURNACE_BLOCK); + registerItem("test_furnace", TEST_FURNACE_BLOCKITEM); + registerBE("test_furnace", TEST_FURNACE_BLOCK_ENTITY_TYPE); + Registry.register(Registries.RECIPE_SERIALIZER, IncubusCore.locate("test_recipe_type"), TEST_RECIPE_SERIALIZER); + } + + public static void clientInit() { + Registry.register(IncubusCondition.CONDITION_REGISTRY, IncubusCore.locate("test_condition"), TEST_CONDITION); + } +} diff --git a/src/main/java/net/id/incubus_core/dev/block/TestFurnaceBlock.java b/src/main/java/net/id/incubus_core/dev/block/TestFurnaceBlock.java new file mode 100644 index 0000000..9c37a6b --- /dev/null +++ b/src/main/java/net/id/incubus_core/dev/block/TestFurnaceBlock.java @@ -0,0 +1,42 @@ +package net.id.incubus_core.dev.block; + +import net.id.incubus_core.dev.DevInit; +import net.minecraft.block.AbstractFurnaceBlock; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityTicker; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.screen.NamedScreenHandlerFactory; +import net.minecraft.stat.Stats; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +public class TestFurnaceBlock extends AbstractFurnaceBlock { + + public TestFurnaceBlock(Settings settings) { + super(settings); + } + + protected void openScreen(World world, BlockPos pos, PlayerEntity player) { + BlockEntity blockEntity = world.getBlockEntity(pos); + if (blockEntity instanceof TestFurnaceBlockEntity) { + player.openHandledScreen((NamedScreenHandlerFactory)blockEntity); + player.incrementStat(Stats.INTERACT_WITH_FURNACE); + } + + } + + @Nullable + @Override + public BlockEntityTicker getTicker(World world, BlockState state, BlockEntityType type) { + return checkType(world, type, DevInit.TEST_FURNACE_BLOCK_ENTITY_TYPE); + } + + @Nullable + @Override + public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { + return new TestFurnaceBlockEntity(pos, state); + } +} diff --git a/src/main/java/net/id/incubus_core/dev/block/TestFurnaceBlockEntity.java b/src/main/java/net/id/incubus_core/dev/block/TestFurnaceBlockEntity.java new file mode 100644 index 0000000..52c3046 --- /dev/null +++ b/src/main/java/net/id/incubus_core/dev/block/TestFurnaceBlockEntity.java @@ -0,0 +1,26 @@ +package net.id.incubus_core.dev.block; + +import net.id.incubus_core.dev.DevInit; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.AbstractFurnaceBlockEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.screen.FurnaceScreenHandler; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.text.Text; +import net.minecraft.util.math.BlockPos; + +public class TestFurnaceBlockEntity extends AbstractFurnaceBlockEntity { + + public TestFurnaceBlockEntity(BlockPos pos, BlockState state) { + super(DevInit.TEST_FURNACE_BLOCK_ENTITY_TYPE, pos, state, DevInit.TEST_RECIPE_TYPE); + } + + @Override + protected Text getContainerName() { + return Text.translatable("container.furnace"); + } + + protected ScreenHandler createScreenHandler(int syncId, PlayerInventory playerInventory) { + return new FurnaceScreenHandler(syncId, playerInventory, this, this.propertyDelegate); + } +} diff --git a/src/main/java/net/id/incubus_core/dev/item/EntityDeathMessageTestItem.java b/src/main/java/net/id/incubus_core/dev/item/EntityDeathMessageTestItem.java new file mode 100644 index 0000000..f549cc9 --- /dev/null +++ b/src/main/java/net/id/incubus_core/dev/item/EntityDeathMessageTestItem.java @@ -0,0 +1,57 @@ +package net.id.incubus_core.dev.item; + +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; +import net.id.incubus_core.misc.CustomDeathMessageProvider; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.attribute.EntityAttribute; +import net.minecraft.entity.attribute.EntityAttributeModifier; +import net.minecraft.entity.attribute.EntityAttributes; +import net.minecraft.entity.damage.EntityDamageSource; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import net.minecraft.world.World; + +import java.util.Optional; + +/** + * Destroyer of worlds. + *

+ * Simple test thing for custom death messages. + *

+ * Does unlimited damage. + */ +public final class EntityDeathMessageTestItem extends Item implements CustomDeathMessageProvider.EntityDamage { + private final Multimap attributeModifiers; + + public EntityDeathMessageTestItem(Settings settings) { + super(settings); + + ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); + builder.put(EntityAttributes.GENERIC_ATTACK_DAMAGE, new EntityAttributeModifier(ATTACK_DAMAGE_MODIFIER_ID, "Weapon modifier", Double.POSITIVE_INFINITY, EntityAttributeModifier.Operation.ADDITION)); + attributeModifiers = builder.build(); + } + + @Override + public void inventoryTick(ItemStack stack, World world, Entity entity, int slot, boolean selected) { + if (!stack.hasCustomName()) { + stack.setCustomName(Text.of("Destroyer of Worlds")); + } + } + + @Override + public Optional getDeathMessage(EntityDamageSource damageSource, LivingEntity target, ItemStack stack, EntityDamageType type) { + return Optional.of(Text.translatable("I hope this works out!")); + } + + @Override + public Multimap getAttributeModifiers(EquipmentSlot slot) { + if (slot == EquipmentSlot.MAINHAND) { + return this.attributeModifiers; + } + return super.getAttributeModifiers(slot); + } +} diff --git a/src/main/java/net/id/incubus_core/dev/recipe/TestRecipeType.java b/src/main/java/net/id/incubus_core/dev/recipe/TestRecipeType.java new file mode 100644 index 0000000..c33efad --- /dev/null +++ b/src/main/java/net/id/incubus_core/dev/recipe/TestRecipeType.java @@ -0,0 +1,58 @@ +package net.id.incubus_core.dev.recipe; + +import com.google.gson.JsonObject; +import net.id.incubus_core.dev.DevInit; +import net.id.incubus_core.json.RecipeParser; +import net.id.incubus_core.recipe.IngredientStack; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.recipe.AbstractCookingRecipe; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.recipe.book.CookingRecipeCategory; +import net.minecraft.util.Identifier; +import net.minecraft.world.World; + +public class TestRecipeType extends AbstractCookingRecipe { + + private final IngredientStack input; + + public TestRecipeType(Identifier id, IngredientStack input, ItemStack output) { + super(DevInit.TEST_RECIPE_TYPE, id, "dev", CookingRecipeCategory.MISC, input.getIngredient(), output, 0, 5); + this.input = input; + } + + @Override + public RecipeSerializer getSerializer() { + return DevInit.TEST_RECIPE_SERIALIZER; + } + + @Override + public boolean matches(Inventory inventory, World world) { + return input.test(inventory.getStack(0)); + } + + public static class Serializer implements RecipeSerializer { + + @Override + public TestRecipeType read(Identifier id, JsonObject json) { + var input = RecipeParser.ingredientStackFromJson(json.getAsJsonObject("input")); + var output = RecipeParser.stackFromJson(json.getAsJsonObject(), "output"); + + return new TestRecipeType(id, input, output); + } + + @Override + public TestRecipeType read(Identifier id, PacketByteBuf buf) { + var input = IngredientStack.fromByteBuf(buf); + var output = buf.readItemStack(); + return new TestRecipeType(id, input, output); + } + + @Override + public void write(PacketByteBuf buf, TestRecipeType recipe) { + recipe.input.write(buf); + buf.writeItemStack(recipe.output); + } + } +} diff --git a/src/main/java/net/id/incubus_core/json/RecipeParser.java b/src/main/java/net/id/incubus_core/json/RecipeParser.java new file mode 100644 index 0000000..aeda242 --- /dev/null +++ b/src/main/java/net/id/incubus_core/json/RecipeParser.java @@ -0,0 +1,149 @@ +package net.id.incubus_core.json; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.MalformedJsonException; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.id.incubus_core.IncubusCore; +import net.id.incubus_core.recipe.IngredientStack; +import net.id.incubus_core.recipe.OptionalStack; +import net.id.incubus_core.recipe.matchbook.MatchFactory; +import net.id.incubus_core.recipe.matchbook.MatchRegistry; +import net.id.incubus_core.recipe.matchbook.Matchbook; +import net.id.incubus_core.recipe.matchbook.Matchbook.Builder; +import net.id.incubus_core.recipe.matchbook.Matchbook.Mode; +import net.id.incubus_core.util.RegistryHelper; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtHelper; +import net.minecraft.recipe.Ingredient; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@SuppressWarnings("unused") +public class RecipeParser { + + private static final JsonParser PARSER = new JsonParser(); + + public static JsonObject fromInputStream(InputStream in) { + return PARSER.parse(new JsonReader(new InputStreamReader(in, StandardCharsets.UTF_8))).getAsJsonObject(); + } + + public static ItemStack stackFromJson(JsonObject json, String elementName) { + Item item = Registries.ITEM.get(Identifier.tryParse(json.get(elementName).getAsString())); + int count = json.has("count") ? json.get("count").getAsInt() : 1; + return item != Items.AIR ? new ItemStack(item, count) : ItemStack.EMPTY; + } + + public static ItemStack stackFromJson(JsonObject json) { + return stackFromJson(json, "item"); + } + + public static IngredientStack ingredientStackFromJson(JsonObject json) { + Ingredient ingredient = json.has("ingredient") ? Ingredient.fromJson(json.getAsJsonObject("ingredient")) : Ingredient.fromJson(json); + var matchbook = Matchbook.empty(); + NbtCompound recipeViewNbt = null; + int count = json.has("count") ? json.get("count").getAsInt() : 1; + + if (json.has("matchbook")) { + try { + matchbook = matchbookFromJson(json.getAsJsonObject("matchbook")); + } catch (MalformedJsonException e) { + IncubusCore.LOG.error("RELAYED EXCEPTION. " + e); + } + } + + if (json.has("recipeViewNbt")) { + try { + recipeViewNbt = NbtHelper.fromNbtProviderString(json.get("recipeViewNbt").getAsString()); + } catch (CommandSyntaxException e) { + IncubusCore.LOG.error("RELAYED EXCEPTION. " + e); + } + } + + return IngredientStack.of(ingredient, matchbook, recipeViewNbt, count); + } + + public static List ingredientStacksFromJson(JsonArray array, int size) { + List ingredients = new ArrayList<>(size); + int dif = size - array.size(); + for (int i = 0; i < array.size() && i < size; i++) { + JsonObject object = array.get(i).getAsJsonObject(); + ingredients.add(ingredientStackFromJson(object)); + } + if(dif > 0) { + for (int i = 0; i < dif; i++) { + ingredients.add(IngredientStack.EMPTY); + } + } + return ingredients; + } + + public static OptionalStack optionalStackFromJson(JsonObject json) throws MalformedJsonException { + int count = json.has("count") ? json.get("count").getAsInt() : 1; + if(json.has("item")) { + Item item = Registries.ITEM.get(Identifier.tryParse(json.get("item").getAsString())); + return item != Items.AIR ? new OptionalStack(new ItemStack(item, count), count) : OptionalStack.EMPTY; + } + else if(json.has("tag")) { + var tagId = Identifier.tryParse(json.get("tag").getAsString()); + var tag = TagKey.of(Registries.ITEM.getKey(), tagId); + return !RegistryHelper.isTagEmpty(tag) ? new OptionalStack(tag, count) : OptionalStack.EMPTY; + } + else { + throw new MalformedJsonException("OptionalStacks must have an item or tag!"); + } + } + + public static List optionalStacksFromJson(JsonArray array, int size) throws MalformedJsonException { + List stacks = new ArrayList<>(size); + int dif = size - array.size(); + for (int i = 0; i < array.size() && i < size; i++) { + JsonObject object = array.get(i).getAsJsonObject(); + stacks.add(optionalStackFromJson(object)); + } + if(dif > 0) { + for (int i = 0; i < dif; i++) { + stacks.add(OptionalStack.EMPTY); + } + } + return stacks; + } + + public static Matchbook matchbookFromJson(JsonObject json) throws MalformedJsonException { + var builder = new Matchbook.Builder(); + var matchArray = json.getAsJsonArray("matches"); + + var mode = Matchbook.Mode.valueOf(json.get("mode").getAsString()); + + for (int i = 0; i < matchArray.size(); i++) { + var entry = matchArray.get(i).getAsJsonObject(); + var id = entry.get("type").getAsString(); + var key = entry.get("key").getAsString(); + + var optional = MatchRegistry.getOptional(id); + if(optional.isEmpty()) { + throw new MalformedJsonException("Invalid Match Type at index " + i); + } + + var factory = optional.get(); + + builder.add(factory.create(key, entry)); + } + + return builder.build(mode); + } + +} diff --git a/src/main/java/net/id/incubus_core/misc/CustomDeathMessageProvider.java b/src/main/java/net/id/incubus_core/misc/CustomDeathMessageProvider.java new file mode 100644 index 0000000..e16283b --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/CustomDeathMessageProvider.java @@ -0,0 +1,83 @@ +package net.id.incubus_core.misc; + +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.damage.EntityDamageSource; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +/** + * Allows an easy way to provide custom death messages without custom {@link DamageSource}s. + * + * @since 1.7.0 + */ +public interface CustomDeathMessageProvider { + /** + * Gets a custom death message for a damage source. + * + * @param damageSource The actual damage source + * @param target The target of the damage + * @param data Context specific data + * @return The text to use, or empty for default + */ + Optional getDeathMessage(S damageSource, LivingEntity target, D data); + + /** + * A damage type specific to {@link EntityDamageSource}. + */ + interface EntityDamage extends CustomDeathMessageProvider { + @Override + default Optional getDeathMessage(EntityDamageSource damageSource, LivingEntity target, Data data) { + return getDeathMessage(damageSource, target, data.stack(), data.type()); + } + + /** + * Gets a custom death message for a damage source. + * + * @param damageSource The actual damage source + * @param target The target of the damage + * @param stack The {@link ItemStack} that caused the damage + * @param type The type of entity damage + * @return The text to use, or empty for default + */ + Optional getDeathMessage(EntityDamageSource damageSource, LivingEntity target, ItemStack stack, EntityDamageType type); + + record Data( + @NotNull ItemStack stack, + @NotNull EntityDamageType type + ) { + } + } + + enum EntityDamageType { + /** + * Caused by a mob. + */ + MOB, + /** + * Caused directly by a player. + */ + PLAYER, + /** + * Caused by explosions the player created. + */ + PLAYER_EXPLOSION, + /** + * Caused by bees. + */ + STING, + /** + * Caused by the thorns enchantment. + */ + THORNS, + /** + * Unknown damage type. + *

+ * Should remain the last enum value. + */ + OTHER, + } +} diff --git a/src/main/java/net/id/incubus_core/misc/IncubusDamageSources.java b/src/main/java/net/id/incubus_core/misc/IncubusDamageSources.java new file mode 100644 index 0000000..b9ef0a5 --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/IncubusDamageSources.java @@ -0,0 +1,60 @@ +package net.id.incubus_core.misc; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.damage.EntityDamageSource; + +public class IncubusDamageSources { + + public static final DamageSource UNWORTHY = new UnworthyDamageSource("ic_unworthy"); + + public static DamageSource grillin(Entity source) { + return new GrillinDamageSource("ic_grillin", source); + } + + + private static class UnworthyDamageSource extends DamageSource { + + + protected UnworthyDamageSource(String name) { + super(name); + } + + @Override + public boolean bypassesArmor() { + return true; + } + + @Override + public boolean isOutOfWorld() { + return true; + } + + @Override + public boolean isNeutral() { + return true; + } + + @Override + public boolean isUnblockable() { + return true; + } + } + + private static class GrillinDamageSource extends EntityDamageSource { + + public GrillinDamageSource(String name, Entity source) { + super(name, source); + } + + @Override + public boolean bypassesArmor() { + return true; + } + + @Override + public boolean isUnblockable() { + return true; + } + } +} diff --git a/src/main/java/net/id/incubus_core/misc/IncubusPlayerData.java b/src/main/java/net/id/incubus_core/misc/IncubusPlayerData.java new file mode 100644 index 0000000..6314940 --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/IncubusPlayerData.java @@ -0,0 +1,44 @@ +package net.id.incubus_core.misc; + +import dev.onyxstudios.cca.api.v3.component.ComponentKey; +import dev.onyxstudios.cca.api.v3.component.ComponentRegistry; +import dev.onyxstudios.cca.api.v3.entity.EntityComponentFactoryRegistry; +import dev.onyxstudios.cca.api.v3.entity.EntityComponentInitializer; +import net.id.incubus_core.IncubusCore; +import net.id.incubus_core.misc.item.IncubusCoreItems; +import net.id.incubus_core.misc.playerdata.PlayerData; +import net.id.incubus_core.misc.playerdata.SpawnItemRegistry; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +import static net.id.incubus_core.misc.Players.*; + +@ApiStatus.Internal +public final class IncubusPlayerData implements EntityComponentInitializer { + + public static final ComponentKey PLAYER_DATA_KEY = ComponentRegistry.getOrCreate(IncubusCore.locate("player_data"), PlayerData.class); + + // Tomfoolery + @Override + public void registerEntityComponentFactories(EntityComponentFactoryRegistry registry) { + registry.registerFor(PlayerEntity.class, PLAYER_DATA_KEY, PlayerData::new); + } + + public static void init() { + SpawnItemRegistry.add(new ItemStack(IncubusCoreItems.AZZYS_ELEMENTAL_FLAG_ITEM), false, playerEntity -> playerEntity.getUuid().equals(AZZY)); + SpawnItemRegistry.add(new ItemStack(IncubusCoreItems.LUNARIAN_SABER_ITEM), false, playerEntity -> playerEntity.getUuid().equals(AZZY)); + SpawnItemRegistry.add(new ItemStack(IncubusCoreItems.LONG_SPATULA), false, playerEntity -> playerEntity.getUuid().equals(PIE)); + SpawnItemRegistry.add(new ItemStack(IncubusCoreItems.FOX_EFFIGY), false, playerEntity -> playerEntity.getUuid().equals(DAF)); + SpawnItemRegistry.add(new ItemStack(IncubusCoreItems.LEAN), false, playerEntity -> WorthinessChecker.isPlayerWorthy(playerEntity.getUuid(), Optional.of(playerEntity))); + SpawnItemRegistry.add(new ItemStack(Items.SWEET_BERRIES), true, playerEntity -> WorthinessChecker.isPlayerWorthy(playerEntity.getUuid(), Optional.of(playerEntity))); + } + + public static PlayerData get(@NotNull PlayerEntity player) { + return PLAYER_DATA_KEY.get(player); + } +} diff --git a/src/main/java/net/id/incubus_core/misc/IncubusSounds.java b/src/main/java/net/id/incubus_core/misc/IncubusSounds.java new file mode 100644 index 0000000..3fee9ce --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/IncubusSounds.java @@ -0,0 +1,21 @@ +package net.id.incubus_core.misc; + +import net.minecraft.sound.SoundEvent; + +import static net.id.incubus_core.IncubusCore.registerSoundEvent; + +public class IncubusSounds { + + public static final SoundEvent WEAK = registerSoundEvent("weak"); + public static final SoundEvent BLAST = registerSoundEvent("blast"); + public static final SoundEvent PARRY = registerSoundEvent("parry"); + public static final SoundEvent SLAM = registerSoundEvent("slam"); + + public static final SoundEvent AHH = registerSoundEvent("ahh"); + public static final SoundEvent APYR = registerSoundEvent("apyr"); + public static final SoundEvent BAD_TO_THE_BONE = registerSoundEvent("bone"); + public static final SoundEvent DRIP = registerSoundEvent("drip"); + public static final SoundEvent DRIP_LONG = registerSoundEvent("drip_long"); + + public static void init() {} +} diff --git a/src/main/java/net/id/incubus_core/misc/IncubusToolMaterials.java b/src/main/java/net/id/incubus_core/misc/IncubusToolMaterials.java new file mode 100644 index 0000000..e851e15 --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/IncubusToolMaterials.java @@ -0,0 +1,59 @@ +package net.id.incubus_core.misc; + +import net.minecraft.item.Items; +import net.minecraft.item.ToolMaterial; +import net.minecraft.recipe.Ingredient; +import net.minecraft.util.Lazy; + +import java.util.function.Supplier; + +public enum IncubusToolMaterials implements ToolMaterial { + LUNARIAN(12, 2031, 12.0F, 4.0F, 50, () -> Ingredient.ofItems(Items.GLASS)), + MILD_STEEL(12, 4096, 11.0F, 5.0F, 10, () -> Ingredient.ofItems(Items.IRON_INGOT)); + + private final int miningLevel; + private final int itemDurability; + private final float miningSpeed; + private final float attackDamage; + private final int enchantability; + private final Lazy repairIngredient; + + IncubusToolMaterials(int miningLevel, int itemDurability, float miningSpeed, float attackDamage, int enchantability, Supplier repairIngredient) { + this.miningLevel = miningLevel; + this.itemDurability = itemDurability; + this.miningSpeed = miningSpeed; + this.attackDamage = attackDamage; + this.enchantability = enchantability; + this.repairIngredient = new Lazy<>(repairIngredient); + } + + @Override + public int getDurability() { + return itemDurability; + } + + @Override + public float getMiningSpeedMultiplier() { + return miningSpeed; + } + + @Override + public float getAttackDamage() { + return attackDamage; + } + + @Override + public int getMiningLevel() { + return miningLevel; + } + + @Override + public int getEnchantability() { + return enchantability; + } + + @Override + public Ingredient getRepairIngredient() { + return repairIngredient.get(); + } +} diff --git a/src/main/java/net/id/incubus_core/misc/Players.java b/src/main/java/net/id/incubus_core/misc/Players.java new file mode 100644 index 0000000..26d4f1a --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/Players.java @@ -0,0 +1,9 @@ +package net.id.incubus_core.misc; + +import java.util.UUID; + +public class Players { + public static final UUID AZZY = UUID.fromString("f7957087-549e-4ca3-878e-48f36569dd3e"); + public static final UUID PIE = UUID.fromString("a250dea2-a0ec-4aa4-bfa9-858a44466241"); + public static final UUID DAF = UUID.fromString("5010ad09-0229-4d70-8a2c-bc254821dcb3"); +} diff --git a/src/main/java/net/id/incubus_core/misc/WorthinessChecker.java b/src/main/java/net/id/incubus_core/misc/WorthinessChecker.java new file mode 100644 index 0000000..3c25957 --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/WorthinessChecker.java @@ -0,0 +1,131 @@ +package net.id.incubus_core.misc; + +import net.fabricmc.loader.api.FabricLoader; +import net.id.incubus_core.IncubusCore; +import net.id.incubus_core.misc.playerdata.PlayerData; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.random.Random; +import net.minecraft.world.World; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Optional; +import java.util.UUID; + +import static net.id.incubus_core.IncubusCore.locate; + +public class WorthinessChecker { + private static boolean bypassWorthiness; + + private static final HashMap PLAYER_MAP = new HashMap<>(); + + public static boolean isPlayerWorthy(UUID uuid, Optional player) { + return player.map(IncubusPlayerData::get).map(PlayerData::isDeemedWorthy).orElse(false) + || Optional.ofNullable(PLAYER_MAP.get(uuid)).map(entry -> entry.worthy).orElse(bypassWorthiness); + } + + public static CapeType getCapeType(UUID uuid) { + return Optional.ofNullable(PLAYER_MAP.get(uuid)).map(entry -> entry.capeType).orElse(bypassWorthiness ? CapeType.IMMORTAL : CapeType.NONE); + } + + private static void putPlayer(UUID id) { + putPlayer(id, CapeType.IMMORTAL, false); + } + + private static void putPlayer(UUID id, CapeType cape, boolean worthy) { + PLAYER_MAP.put(id, new Entry(id, cape, worthy)); + } + + public static void smite(LivingEntity entity) { + var world = entity.getWorld(); + var random = entity.getRandom(); + var stack = entity.getStackInHand(entity.getActiveHand()); + + entity.addStatusEffect(new StatusEffectInstance(StatusEffects.WEAKNESS, 60, 1), entity); + entity.addStatusEffect(new StatusEffectInstance(StatusEffects.SLOWNESS, 60, 2), entity); + + entity.setStackInHand(entity.getActiveHand(), ItemStack.EMPTY); + + if (entity instanceof PlayerEntity) { + ((PlayerEntity) entity).sendMessage(Text.translatable("You have no right!"), true); + entity.dropStack(stack); + } + + world.playSoundFromEntity(null, entity, IncubusSounds.DRIP, SoundCategory.PLAYERS, 2F, 2F); + + if (entity.getHealth() < 5F) { + entity.damage(IncubusDamageSources.UNWORTHY, 10000F); + } + else { + entity.damage(IncubusDamageSources.UNWORTHY, 0.1F); + entity.setHealth(0.01F); + } + + if (!world.isClient()) { + Box bounds = entity.getBoundingBox(entity.getPose()); + for (int i = 0; i < Math.pow(bounds.getAverageSideLength() * 4, 2); i++) { + ((ServerWorld) world).spawnParticles(ParticleTypes.SOUL_FIRE_FLAME, entity.getX() + (random.nextDouble() * bounds.getXLength() - bounds.getXLength() / 2), entity.getY() + (random.nextDouble() * bounds.getYLength()), entity.getZ() + (random.nextDouble() * bounds.getZLength() - bounds.getZLength() / 2), random.nextInt(4), 0, 0, 0, 0.9); + } + } + } + + public record Entry(UUID playerId, CapeType capeType, boolean worthy) {} + + public static void init(){ + String[] args = FabricLoader.getInstance().getLaunchArguments(false); + bypassWorthiness = Arrays.asList(args).contains("WORTHY"); + if (bypassWorthiness) { + IncubusCore.LOG.info("Bypassed worthiness check."); + } + } + + public enum CapeType { + IMMORTAL(locate("textures/capes/immortal.png"), true), + LUNAR(locate("textures/capes/inaba_of_the_moon.png"), true), + V1(locate("textures/capes/v1.png"), true), + UNDERGROUND_ASTRONOMY(locate("textures/capes/underground_astronomy.png"), true), + LUCKY_STARS(locate("textures/capes/lucky_stars.png"), true), + PALE_ASTRONOMY(locate("textures/capes/pale_astronomy.png"), true), + GUDY(locate("textures/capes/gudy.png"), true), + CHROMED(locate("textures/capes/chromed.png"), true), + LEAD(locate("textures/capes/leads.png"), true), + NONE(null, false); + + public final Identifier capePath; + public final boolean render; + + CapeType(Identifier capePath, boolean render) { + this.capePath = capePath; + this.render = render; + } + } + + static { + putPlayer(UUID.fromString("f7957087-549e-4ca3-878e-48f36569dd3e"), CapeType.LUNAR, true); //azzy + putPlayer(UUID.fromString("a250dea2-a0ec-4aa4-bfa9-858a44466241"), CapeType.V1, true); //pie + putPlayer(UUID.fromString("32e3b46b-2d54-47c7-886e-8e53889592d6"), CapeType.LEAD, true); //kal + putPlayer(UUID.fromString("935bdd48-be5a-4537-95e4-e2274b2a9792"), CapeType.LEAD, true); //jack + putPlayer(UUID.fromString("904bc7cc-c99d-40c8-9297-2efc3e08205c"), CapeType.LEAD, true); //sun + putPlayer(UUID.fromString("510d0e83-67ef-49c6-83b4-d83ed34efeee"), CapeType.GUDY, false); //gud + putPlayer(UUID.fromString("9bab9ead-385d-421e-812f-b8cac440d183"), CapeType.IMMORTAL, true); //24 + putPlayer(UUID.fromString("5c868fb2-7727-4cb8-a7d6-3083fa175063"), CapeType.IMMORTAL, false); //cda + putPlayer(UUID.fromString("5010ad09-0229-4d70-8a2c-bc254821dcb3"), CapeType.UNDERGROUND_ASTRONOMY, true); // daf + putPlayer(UUID.fromString("6105cb83-5d33-4e45-8adb-f24ee0085bf5"), CapeType.LUCKY_STARS, false); // krak + putPlayer(UUID.fromString("f962000a-ee12-40ea-abd5-e15f7492f039"), CapeType.PALE_ASTRONOMY, false); // dra + putPlayer(UUID.fromString("f791d11d-5415-4c28-99e7-ac6a0b2fec28"), CapeType.PALE_ASTRONOMY, false); // opl + putPlayer(UUID.fromString("a1732122-e22e-4edf-883c-09673eb55de8"), CapeType.PALE_ASTRONOMY, false); // maya + putPlayer(UUID.fromString("5a4c901c-2477-436b-a5b3-3b753fad43a5")); //reo + putPlayer(UUID.fromString("c31a8cfa-ecd7-4ec2-8976-cb86c8c651e2")); //prof + putPlayer(UUID.fromString("004679d7-3163-4e06-a36f-8c6c531d7681")); //solly + putPlayer(UUID.fromString("73c30c75-e6d7-4141-9c14-06019b6888c1")); //ash + } +} diff --git a/src/main/java/net/id/incubus_core/misc/item/AzzysFlagItem.java b/src/main/java/net/id/incubus_core/misc/item/AzzysFlagItem.java new file mode 100644 index 0000000..204d830 --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/item/AzzysFlagItem.java @@ -0,0 +1,507 @@ +package net.id.incubus_core.misc.item; + +import net.id.incubus_core.misc.IncubusDamageSources; +import net.id.incubus_core.misc.IncubusPlayerData; +import net.id.incubus_core.misc.IncubusSounds; +import net.id.incubus_core.misc.WorthinessChecker; +import net.id.incubus_core.misc.playerdata.PlayerData; +import net.minecraft.block.*; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LightningEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.entity.passive.*; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.*; +import net.minecraft.particle.DustParticleEffect; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.TypedActionResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.random.Random; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class AzzysFlagItem extends HoeItem { + + private static final ArrayList SAPLINGS; + + public AzzysFlagItem(ToolMaterial toolMaterial, int attackDamage, float attackSpeed, Settings settings) { + super(toolMaterial, attackDamage, attackSpeed, settings); + } + + @Override + public TypedActionResult use(World world, PlayerEntity user, Hand hand) { + if (!WorthinessChecker.isPlayerWorthy(user.getUuid(), Optional.of(user))) { + WorthinessChecker.smite(user); + } + + var random = world.getRandom(); + var flagStaff = user.getStackInHand(hand); + + if (user.isSneaking() && hand == Hand.OFF_HAND) { + cycleMode(flagStaff); + user.sendMessage(getMode(flagStaff).name, true); + world.playSoundFromEntity(null, user, IncubusSounds.AHH, SoundCategory.PLAYERS, 1F, 1.4F + random.nextFloat() * 0.5F); + return TypedActionResult.success(flagStaff, world.isClient()); + } + + var mode = getMode(flagStaff); + var sneaking = user.isSneaking(); + + switch (mode) { + case MUNDANE -> { + + return TypedActionResult.pass(flagStaff); + + } + case GIFT -> { + + var players = new ArrayList<>(world.getPlayers()); + players.remove(user); + + if (players.isEmpty()) { + handleGifting(null, random, user, sneaking, true); + return TypedActionResult.success(flagStaff, world.isClient()); + } + + Collections.shuffle(players); + var target = players.get(0); + + handleGifting(target, random, user, sneaking, false); + world.playSoundFromEntity(null, sneaking ? user : target, random.nextFloat() < 0.015F ? IncubusSounds.APYR : IncubusSounds.BAD_TO_THE_BONE, SoundCategory.PLAYERS, 1, random.nextFloat() * 2); + + return TypedActionResult.success(flagStaff, world.isClient()); + + } + case VANISH -> { + + var playerData = IncubusPlayerData.get(user); + var invisible = playerData.shouldSkipRender(); + + playerData.setBlockRendering(!invisible); + world.playSoundFromEntity(null, user, IncubusSounds.DRIP, SoundCategory.PLAYERS, 1, 1); + + if (!world.isClient()) { + IncubusPlayerData.PLAYER_DATA_KEY.sync(user); + } + + return TypedActionResult.success(flagStaff, world.isClient()); + + } + case LIFE -> { + + if (sneaking) { + handleSpawning(world, random, user); + world.playSoundFromEntity(null, user, IncubusSounds.AHH, SoundCategory.PLAYERS, 1, 1); + + return TypedActionResult.success(flagStaff, world.isClient()); + } + + } + case SANS -> { + var players = new ArrayList<>(world.getPlayers()); + players.remove(user); + + if (players.isEmpty()) + return TypedActionResult.fail(flagStaff); + + Collections.shuffle(players); + var target = players.get(0); + + world.playSound(null, user.getBlockPos(), IncubusSounds.DRIP, SoundCategory.PLAYERS, 0.9F, 1); + if (!world.isClient()) { + Box bounds = user.getBoundingBox(); + for (int j = 0; j < Math.pow(bounds.getAverageSideLength() * 3, 2); j++) { + ((ServerWorld) world).spawnParticles(ParticleTypes.END_ROD, user.getX() + (random.nextDouble() * bounds.getXLength() - bounds.getXLength() / 2), user.getY() + (random.nextDouble() * bounds.getYLength()), user.getZ() + (random.nextDouble() * bounds.getZLength() - bounds.getZLength() / 2), random.nextInt(4), 0, 0, 0, 0.9); + } + } + + user.teleport(target.getX(), target.getY(), target.getZ()); + world.playSoundFromEntity(null, target, IncubusSounds.APYR, SoundCategory.PLAYERS, 0.65F, 1); + + return TypedActionResult.success(flagStaff, world.isClient()); + } + } + + + return TypedActionResult.fail(flagStaff); + } + + private void handleGifting(PlayerEntity target, Random random, PlayerEntity user, boolean sneaking, boolean hardFail) { + if (user.world.isClient()) + return; + + if (sneaking) { + int roll = random.nextInt(7); + switch (roll) { + case 0: + user.giveItemStack(new ItemStack(Items.SWEET_BERRIES)); + case 1: + user.giveItemStack(new ItemStack(Items.ROSE_BUSH)); + case 2: + user.giveItemStack(new ItemStack(Items.VINE)); + case 3: + user.giveItemStack(new ItemStack(Items.DARK_OAK_SAPLING)); + case 4: + user.giveItemStack(new ItemStack(Items.SPRUCE_SAPLING)); + case 5: + user.giveItemStack(new ItemStack(IncubusCoreItems.LONG_SPATULA)); + case 6: + user.giveItemStack(new ItemStack(IncubusCoreItems.FOX_EFFIGY)); + } + + } else if(!hardFail) { + var cap = lowRollRandom(random, 5) + 1; + target.sendMessage(Text.translatable("You have been graced with gifts!").styled(style -> style.withColor(0xffb0b3)), true); + for (int i = 0; i < cap; i++) { + float roll = random.nextInt(100); + + if (roll > 98) { + grantStack(target, IncubusCoreItems.DEBUG_FLAME_ITEM, random.nextInt(65)); + } else if (roll > 95) { + grantStack(target, IncubusCoreItems.FOX_EFFIGY, 1); + } else if (roll > 90) { + grantStack(target, IncubusCoreItems.LONG_SPATULA, 1); + } else if (roll > 80) { + grantStack(target, IncubusCoreItems.MOBILK_1, lowRollRandom(random, 9)); + } else if (roll > 70) { + grantStack(target, IncubusCoreItems.LEAN, lowRollRandom(random, 9)); + } else if (roll > 60) { + grantStack(target, IncubusCoreItems.RAT_POISON, lowRollRandom(random, 9)); + } else if (roll > 50) { + grantStack(target, Items.CAKE, 1); + } else if (roll > 30) { + grantStack(target, Items.FURNACE_MINECART, 1); + } else { + int subRoll = random.nextInt(7); + int count = lowRollRandom(random, 65); + + Item item = switch (subRoll) { + case 0 -> Items.SWEET_BERRIES; + case 1 -> Items.ROOTED_DIRT; + case 2 -> Items.POPPY; + case 3 -> Items.BIG_DRIPLEAF; + case 4 -> Items.SMALL_DRIPLEAF; + case 5 -> Items.COOKIE; + case 6 -> Items.POISONOUS_POTATO; + default -> throw new IllegalStateException("Unexpected value: " + subRoll); + }; + + grantStack(target, item, count); + } + } + } + } + + private void handleSpawning(World world, Random random, PlayerEntity user) { + var centerPos = user.getBlockPos(); + var tries = random.nextInt(19); + + for (int i = 0; i < tries; i++) { + var spawnPos = centerPos.add(random.nextInt(11) - 5, random.nextInt(7) - 3, random.nextInt(11) - 5); + var variant = random.nextFloat(); + + Entity entity = null; + + if (world.isWater(spawnPos)) { + if (variant > 0.9) { + entity = new AxolotlEntity(EntityType.AXOLOTL, world); + } else if (variant > 0.6) { + entity = new TropicalFishEntity(EntityType.TROPICAL_FISH, world); + } else if (variant > 0.3) { + entity = new SalmonEntity(EntityType.SALMON, world); + } else { + entity = new CodEntity(EntityType.COD, world); + } + } else if (world.isAir(spawnPos)) { + if (world.isAir(spawnPos.down())) { + if (variant > 0.9) { + entity = new BatEntity(EntityType.BAT, world); + } else if (variant > 0.75) { + entity = new ChickenEntity(EntityType.CHICKEN, world); + } else { + entity = new BeeEntity(EntityType.BEE, world); + } + } else { + + if (variant > 0.98) { + entity = new MooshroomEntity(EntityType.MOOSHROOM, world); + } else if (variant > 0.92) { + entity = new WolfEntity(EntityType.WOLF, world); + } else if (variant > 0.725) { + entity = new FoxEntity(EntityType.FOX, world); + } else { + entity = new RabbitEntity(EntityType.RABBIT, world); + } + } + } + + if (entity != null) { + entity.setPosition(Vec3d.ofCenter(spawnPos)); + world.spawnEntity(entity); + if (!world.isClient()) { + Box bounds = entity.getBoundingBox(); + for (int j = 0; j < Math.pow(bounds.getAverageSideLength() * 3, 2); j++) { + ((ServerWorld) world).spawnParticles(DustParticleEffect.DEFAULT, entity.getX() + (random.nextDouble() * bounds.getXLength() - bounds.getXLength() / 2), entity.getY() + (random.nextDouble() * bounds.getYLength()), entity.getZ() + (random.nextDouble() * bounds.getZLength() - bounds.getZLength() / 2), random.nextInt(4), 0, 0, 0, 0.9); + } + } + } + } + + + } + + private void grantStack(PlayerEntity target, Item item, int count) { + target.giveItemStack(new ItemStack(item, count)); + } + + private int lowRollRandom(Random random, int cap) { + return Math.min(random.nextInt(cap), random.nextInt(cap)); + } + + @Override + public ActionResult useOnEntity(ItemStack stack, PlayerEntity user, LivingEntity target, Hand hand) { + var mode = getMode(stack); + var world = user.getWorld(); + var random = user.getRandom(); + + if (target instanceof PlayerEntity player) { + switch (mode) { + case GIFT: { + handleGifting(player, random, user, false, false); + return ActionResult.success(world.isClient()); + } + case BLESS: { + target.addStatusEffect(new StatusEffectInstance(StatusEffects.REGENERATION, 200, 3)); + target.addStatusEffect(new StatusEffectInstance(StatusEffects.HEALTH_BOOST, 6000, 4)); + target.addStatusEffect(new StatusEffectInstance(StatusEffects.ABSORPTION, 6000, 4)); + + world.playSoundFromEntity(null, target, IncubusSounds.DRIP, SoundCategory.PLAYERS, 1, 1); + return ActionResult.success(world.isClient()); + } + case CURSE: { + if (!user.isSneaking()) { + target.damage(IncubusDamageSources.UNWORTHY, 10F); + world.playSoundFromEntity(null, target, IncubusSounds.WEAK, SoundCategory.PLAYERS, 1, 0.9F + random.nextFloat() * 0.2F); + } else { + if (!world.isClient()) { + var lightning = new LightningEntity(EntityType.LIGHTNING_BOLT, world); + lightning.setChanneler((ServerPlayerEntity) user); + lightning.setPosition(target.getPos()); + + target.addStatusEffect(new StatusEffectInstance(StatusEffects.HUNGER, 200, 3)); + target.addStatusEffect(new StatusEffectInstance(StatusEffects.WEAKNESS, 6000, 2)); + target.addStatusEffect(new StatusEffectInstance(StatusEffects.SLOWNESS, 6000, 2)); + target.addStatusEffect(new StatusEffectInstance(StatusEffects.MINING_FATIGUE, 6000, 2)); + + world.spawnEntity(lightning); + world.playSoundFromEntity(null, target, IncubusSounds.WEAK, SoundCategory.PLAYERS, 1, 1F); + } + } + return ActionResult.success(world.isClient()); + + } + case LIFE: { + target.clearStatusEffects(); + target.heal(500f); + target.addStatusEffect(new StatusEffectInstance(StatusEffects.SATURATION, 20, 0)); + target.addStatusEffect(new StatusEffectInstance(StatusEffects.RESISTANCE, 200, 3)); + + world.playSoundFromEntity(null, target, IncubusSounds.AHH, SoundCategory.PLAYERS, 0.5F, 1F); + return ActionResult.success(world.isClient()); + } + case WORTHY: { + var playerData = IncubusPlayerData.get(player); + var worthy = playerData.isDeemedWorthy(); + + playerData.deemWorthy(!playerData.isDeemedWorthy()); + + if (!worthy) { + player.sendMessage(Text.translatable("You have been deemed worthy"), true); + world.playSoundFromEntity(null, target, IncubusSounds.DRIP_LONG, SoundCategory.PLAYERS, 0.5F, 1F); + } else { + player.sendMessage(Text.translatable("You have no right!"), true); + world.playSoundFromEntity(null, target, IncubusSounds.WEAK, SoundCategory.PLAYERS, 0.5F, 1F); + } + + if (!world.isClient()) { + IncubusPlayerData.PLAYER_DATA_KEY.sync(player); + } + + return ActionResult.success(world.isClient()); + + } + default: + return ActionResult.PASS; + } + + } + + return ActionResult.PASS; + } + + @Override + public ActionResult useOnBlock(ItemUsageContext context) { + var pos = context.getBlockPos(); + var world = context.getWorld(); + var mode = getMode(context.getStack()); + var sneaking = Optional.ofNullable(context.getPlayer()).map(PlayerEntity::isSneaking).orElse(false); + var state = world.getBlockState(pos); + var block = state.getBlock(); + + if (mode == Mode.MUNDANE) { + if (sneaking) { + if (context.getSide() == Direction.DOWN) { + return ActionResult.PASS; + } else { + PlayerEntity playerEntity = context.getPlayer(); + BlockState blockState2 = PathStateAccessor.getPathStates().get(state.getBlock()); + BlockState blockState3 = null; + if (blockState2 != null && world.getBlockState(pos.up()).isAir()) { + world.playSound(playerEntity, pos, SoundEvents.ITEM_SHOVEL_FLATTEN, SoundCategory.BLOCKS, 1.0F, 1.0F); + blockState3 = blockState2; + } else if (state.getBlock() instanceof CampfireBlock && (Boolean) state.get(CampfireBlock.LIT)) { + if (!world.isClient()) { + world.syncWorldEvent(null, 1009, pos, 0); + } + + CampfireBlock.extinguish(context.getPlayer(), world, pos, state); + blockState3 = state.with(CampfireBlock.LIT, false); + } + + if (blockState3 != null) { + if (!world.isClient) { + world.setBlockState(pos, blockState3, 11); + if (playerEntity != null) { + context.getStack().damage(1, playerEntity, (p) -> { + p.sendToolBreakStatus(context.getHand()); + }); + } + } + + return ActionResult.success(world.isClient); + } else { + return ActionResult.PASS; + } + } + + } + + return super.useOnBlock(context); + } else if (mode == Mode.LIFE) { + BoneMealItem.createParticles(world, pos, 0); + + if (SAPLINGS.contains(block)) { + if (block == Blocks.FLOWERING_AZALEA) { + world.setBlockState(pos, Blocks.OAK_SAPLING.getDefaultState()); + } else { + world.setBlockState(pos, SAPLINGS.get(SAPLINGS.indexOf(block) + 1).getDefaultState()); + } + + world.playSound(null, pos, SoundEvents.BLOCK_SHROOMLIGHT_PLACE, SoundCategory.BLOCKS, 1, 1); + } + + if (block instanceof Fertilizable fertilizable && world.getRandom().nextFloat() < 0.1F) { + if (fertilizable.isFertilizable(world, pos, state, world.isClient()) && !world.isClient()) + fertilizable.grow((ServerWorld) world, world.getRandom(), pos, state); + } + } + + return ActionResult.FAIL; + } + + + public static Mode getMode(ItemStack stack) { + var id = stack.getOrCreateNbt().getString("mode"); + if (id.isEmpty()) { + stack.getOrCreateNbt().putString("mode", "MUNDANE"); + return Mode.MUNDANE; + } + return Mode.valueOf(id); + } + + public static void setMode(ItemStack stack, Mode mode) { + stack.getOrCreateNbt().putString("mode", mode.name()); + } + + public static void cycleMode(ItemStack stack) { + setMode(stack, getMode(stack).next()); + } + + @Override + public Text getName() { + var name = super.getName(); + return name.getWithStyle(name.getStyle().withColor(0xffb0b3)).get(0); + } + + @Override + public Text getName(ItemStack stack) { + var name = super.getName(stack); + return name.getWithStyle(name.getStyle().withColor(0xffb0b3)).get(0); + } + + @Override + public void appendTooltip(ItemStack stack, @Nullable World world, List tooltip, TooltipContext context) { + tooltip.add(Text.translatable("§f§oFor The Worthy")); + tooltip.add(Text.translatable("§olady azzy's blushing staff").styled(style -> style.withColor(0xffb0b3))); + tooltip.add(getMode(stack).name); + super.appendTooltip(stack, world, tooltip, context); + } + + @Override + public boolean hasGlint(ItemStack stack) { + return false; + } + + static { + SAPLINGS = new ArrayList<>(); + SAPLINGS.add(Blocks.OAK_SAPLING); + SAPLINGS.add(Blocks.BIRCH_SAPLING); + SAPLINGS.add(Blocks.SPRUCE_SAPLING); + SAPLINGS.add(Blocks.DARK_OAK_SAPLING); + SAPLINGS.add(Blocks.ACACIA_SAPLING); + SAPLINGS.add(Blocks.JUNGLE_SAPLING); + SAPLINGS.add(Blocks.AZALEA); + SAPLINGS.add(Blocks.FLOWERING_AZALEA); + } + + private enum Mode { + MUNDANE("mundane", 0x9faebd), + GIFT("charitable", 0xcf91ff), + BLESS("anointing", 0x70ffcd), + CURSE("emnity", 0xd83a71), + VANISH("illusionary", 0xbce5de), + LIFE("vitalizing", 0xffb5c3), + SANS("sans", 0xffc552), + WORTHY("righteous", 0xedfcff); + + public final Text name; + + Mode(String name, int color) { + this.name = Text.literal(name).styled(style -> style.withColor(color).withItalic(true)); + + } + + public Mode next() { + var nextOrdinal = this == WORTHY ? 0 : ordinal() + 1; + return values()[nextOrdinal]; + } + } +} diff --git a/src/main/java/net/id/incubus_core/misc/item/BerryBranchItem.java b/src/main/java/net/id/incubus_core/misc/item/BerryBranchItem.java new file mode 100644 index 0000000..19564c8 --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/item/BerryBranchItem.java @@ -0,0 +1,100 @@ +package net.id.incubus_core.misc.item; + +import net.id.incubus_core.misc.IncubusSounds; +import net.id.incubus_core.misc.WorthinessChecker; +import net.id.incubus_core.util.FoxDuck; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.entity.passive.FoxEntity; +import net.minecraft.entity.passive.FoxEntity.Type; +import net.minecraft.entity.passive.WolfEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.sound.SoundEvents; +import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Optional; + +public class BerryBranchItem extends Item { + + public BerryBranchItem(Settings settings) { + super(settings); + } + + @Override + public ActionResult useOnEntity(ItemStack stack, PlayerEntity user, LivingEntity entity, Hand hand) { + if (!WorthinessChecker.isPlayerWorthy(user.getUuid(), Optional.of(user))) { + user.sendMessage(Text.translatable("the sacred grove's magic heeds not your will").styled(style -> style.withColor(0xff6b97)), true); + return ActionResult.PASS; + } + + if (entity instanceof FoxEntity fox) { + var duck = (FoxDuck) fox; + duck.addTrustedUUID(user.getUuid()); + + fox.addStatusEffect(new StatusEffectInstance(StatusEffects.HEALTH_BOOST, Integer.MAX_VALUE, 9, false, false, false)); + fox.addStatusEffect(new StatusEffectInstance(StatusEffects.RESISTANCE, Integer.MAX_VALUE, 2, false, false, false)); + fox.addStatusEffect(new StatusEffectInstance(StatusEffects.STRENGTH, Integer.MAX_VALUE, 1, false, false, false)); + fox.addStatusEffect(new StatusEffectInstance(StatusEffects.REGENERATION, Integer.MAX_VALUE, 1, false, false, false)); + + fox.heal(300F); + + fox.setCustomName(Text.translatable("Sagacious Fox").styled(style -> style.withColor(0xedf6ff))); + + if (user.isSneaking()) { + var color = duck.getFoxColor(); + duck.setFoxColor(color == FoxEntity.Type.RED ? FoxEntity.Type.SNOW : FoxEntity.Type.RED); + fox.playSound(SoundEvents.BLOCK_AMETHYST_BLOCK_CHIME, 0.5F, 1); + } + + fox.playSound(IncubusSounds.AHH, 0.5F, 2); + } + else if (entity instanceof WolfEntity wolf) { + if (!wolf.isTamed()) { + wolf.setOwner(user); + } + + wolf.heal(50); + } + else { + entity.addStatusEffect(new StatusEffectInstance(StatusEffects.REGENERATION, 200, 2, false, true, true)); + entity.playSound(SoundEvents.BLOCK_AMETHYST_BLOCK_CHIME, 0.5F, 1); + + } + + return ActionResult.success(user.getWorld().isClient()); + } + + public Text getName() { + var name = super.getName(); + return name.getWithStyle(name.getStyle().withColor(0xff6b97)).get(0); + } + + @Override + public Text getName(ItemStack stack) { + var name = super.getName(stack); + return name.getWithStyle(name.getStyle().withColor(0xff6b97)).get(0); + } + + @Override + public void appendTooltip(ItemStack stack, @Nullable World world, List tooltip, TooltipContext context) { + tooltip.add(Text.literal("§f§oFor The Worthy")); + tooltip.add(Text.literal("§osprig of endless nourishment").styled(style -> style.withColor(0xff6b97))); + tooltip.add(Text.literal("§ofoxes love it!").styled(style -> style.withColor(0xff6b97))); + super.appendTooltip(stack, world, tooltip, context); + } + + @Override + public ItemStack finishUsing(ItemStack stack, World world, LivingEntity user) { + user.eatFood(world, stack.copy()); + return stack; + } +} diff --git a/src/main/java/net/id/incubus_core/misc/item/FoxEffigyItem.java b/src/main/java/net/id/incubus_core/misc/item/FoxEffigyItem.java new file mode 100644 index 0000000..ff7c59f --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/item/FoxEffigyItem.java @@ -0,0 +1,91 @@ +package net.id.incubus_core.misc.item; + +import net.id.incubus_core.misc.IncubusSounds; +import net.id.incubus_core.misc.WorthinessChecker; +import net.id.incubus_core.util.FoxDuck; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.entity.passive.FoxEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUsageContext; +import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Optional; + +public class FoxEffigyItem extends Item { + + public FoxEffigyItem(Settings settings) { + super(settings); + } + + @Override + public ActionResult useOnBlock(ItemUsageContext context) { + var user = context.getPlayer(); + var world = context.getWorld(); + var pos = context.getBlockPos().up(); + + if (user == null) + return ActionResult.FAIL; + + if (!WorthinessChecker.isPlayerWorthy(user.getUuid(), Optional.of(user))) { + WorthinessChecker.smite(user); + return ActionResult.FAIL; + } + + var fox = new FoxEntity(EntityType.FOX, world); + fox.setPosition(Vec3d.ofCenter(pos)); + ((FoxDuck) fox).setFoxColor(FoxEntity.Type.SNOW); + ((FoxDuck) fox).addTrustedUUID(user.getUuid()); + fox.equipStack(EquipmentSlot.MAINHAND, new ItemStack(IncubusCoreItems.BERRY_BRANCH)); + + fox.addStatusEffect(new StatusEffectInstance(StatusEffects.HEALTH_BOOST, Integer.MAX_VALUE, 9, false, false, false)); + fox.addStatusEffect(new StatusEffectInstance(StatusEffects.RESISTANCE, Integer.MAX_VALUE, 9, false, false, false)); + fox.addStatusEffect(new StatusEffectInstance(StatusEffects.REGENERATION, Integer.MAX_VALUE, 9, false, false, false)); + fox.addStatusEffect(new StatusEffectInstance(StatusEffects.WEAKNESS, Integer.MAX_VALUE, 9, false, false, false)); + + fox.heal(300F); + + fox.setCustomName(Text.translatable("Divine Fox").styled(style -> style.withColor(0xedf6ff))); + world.spawnEntity(fox); + + fox.playSound(IncubusSounds.AHH, 0.5F, 2); + + return ActionResult.CONSUME; + } + + public Text getName() { + var name = super.getName(); + return name.getWithStyle(name.getStyle().withColor(0xedf6ff)).get(0); + } + + @Override + public Text getName(ItemStack stack) { + var name = super.getName(stack); + return name.getWithStyle(name.getStyle().withColor(0xedf6ff)).get(0); + } + + @Override + public void appendTooltip(ItemStack stack, @Nullable World world, List tooltip, TooltipContext context) { + tooltip.add(Text.translatable("§f§oFor The Worthy")); + tooltip.add(Text.translatable("§oa thieving spirit").styled(style -> style.withColor(0xedf6ff))); + tooltip.add(Text.translatable("§ocast from the sacred grove").styled(style -> style.withColor(0xedf6ff))); + tooltip.add(Text.translatable("§otook a sprig of the foxthorn").styled(style -> style.withColor(0xedf6ff))); + tooltip.add(Text.translatable("§ovenerable mother of all berries").styled(style -> style.withColor(0xedf6ff))); + tooltip.add(Text.translatable("§oa relic is not given up easily").styled(style -> style.withColor(0xedf6ff))); + tooltip.add(Text.translatable("§oan equal trade may be accepted").styled(style -> style.withColor(0xedf6ff))); + tooltip.add(Text.translatable("§osomething magical").styled(style -> style.withColor(0xedf6ff))); + tooltip.add(Text.translatable("§oshattering the chains of life").styled(style -> style.withColor(0xedf6ff))); + super.appendTooltip(stack, world, tooltip, context); + } +} \ No newline at end of file diff --git a/src/main/java/net/id/incubus_core/misc/item/IncubusCoreItems.java b/src/main/java/net/id/incubus_core/misc/item/IncubusCoreItems.java new file mode 100644 index 0000000..2cdaebf --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/item/IncubusCoreItems.java @@ -0,0 +1,40 @@ +package net.id.incubus_core.misc.item; + +import net.fabricmc.fabric.api.item.v1.FabricItemSettings; +import net.id.incubus_core.misc.IncubusSounds; +import net.id.incubus_core.misc.IncubusToolMaterials; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.item.Item; +import net.minecraft.item.Items; +import net.minecraft.util.Rarity; + +import static net.id.incubus_core.IncubusCore.registerItem; + +public class IncubusCoreItems { + + + public static final LunarianSaberItem LUNARIAN_SABER_ITEM = new LunarianSaberItem(IncubusToolMaterials.LUNARIAN, 0, -2.25F, new FabricItemSettings().fireproof()); + public static final AzzysFlagItem AZZYS_ELEMENTAL_FLAG_ITEM = new AzzysFlagItem(IncubusToolMaterials.LUNARIAN, -2, -1F, new FabricItemSettings()); + public static final LongSpatulaItem LONG_SPATULA = new LongSpatulaItem(IncubusToolMaterials.MILD_STEEL, 2, -2.5F, new FabricItemSettings().fireproof()); + + public static final FoxEffigyItem FOX_EFFIGY = new FoxEffigyItem(new FabricItemSettings().maxCount(1)); + public static final BerryBranchItem BERRY_BRANCH = new BerryBranchItem(new FabricItemSettings().maxCount(1).food(IncubusFoodComponents.BERRY_BRANCH)); + + public static final Item MOBILK_1 = new Item(new FabricItemSettings().rarity(Rarity.UNCOMMON).food(IncubusFoodComponents.MOBILK_1)); + public static final Item LEAN = new Item(new FabricItemSettings().rarity(Rarity.UNCOMMON).food(IncubusFoodComponents.LEAN)); + public static final Item RAT_POISON = new Item(new FabricItemSettings().rarity(Rarity.UNCOMMON).food(IncubusFoodComponents.RAT_POISON)); + + public static final Item DEBUG_FLAME_ITEM = new Item(new FabricItemSettings().fireproof().rarity(Rarity.EPIC).maxCount(1).equipmentSlot(stack -> EquipmentSlot.HEAD)); + + public static void init() { + registerItem("lunarian_saber", LUNARIAN_SABER_ITEM); + registerItem("lord_azzys_elemental_flag", AZZYS_ELEMENTAL_FLAG_ITEM); + registerItem("long_spatula", LONG_SPATULA); + registerItem("fox_effigy", FOX_EFFIGY); + registerItem("everfruiting_berry_branch", BERRY_BRANCH); + registerItem("debug_flame", DEBUG_FLAME_ITEM); + registerItem("mobilk-1", MOBILK_1); + registerItem("lean", LEAN); + registerItem("rat_poison", RAT_POISON); + } +} diff --git a/src/main/java/net/id/incubus_core/misc/item/IncubusFoodComponents.java b/src/main/java/net/id/incubus_core/misc/item/IncubusFoodComponents.java new file mode 100644 index 0000000..081f565 --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/item/IncubusFoodComponents.java @@ -0,0 +1,38 @@ +package net.id.incubus_core.misc.item; + +import net.id.incubus_core.IncubusCore; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.item.FoodComponent; + +public class IncubusFoodComponents { + + public static final FoodComponent BERRY_BRANCH = new FoodComponent.Builder() + .hunger(2).saturationModifier(0.6F).snack() + .statusEffect(new StatusEffectInstance(StatusEffects.REGENERATION, 100, 1, false, false, true), 1.0F) + .statusEffect(new StatusEffectInstance(StatusEffects.INSTANT_HEALTH, 1, 0, false, false, true), 0.1F) + .build(); + + public static final FoodComponent MOBILK_1 = new FoodComponent.Builder() + .alwaysEdible().hunger(200).saturationModifier(10F) + .statusEffect(new StatusEffectInstance(StatusEffects.LUCK, 1200, 0), 1) + .statusEffect(new StatusEffectInstance(StatusEffects.POISON, 1200, 0), 1) + .statusEffect(new StatusEffectInstance(StatusEffects.NAUSEA, 1200, 0), 1) + .statusEffect(new StatusEffectInstance(StatusEffects.WEAKNESS, 1200, 0), 1) + .statusEffect(new StatusEffectInstance(StatusEffects.SLOWNESS, 1200, 0), 1) + .build(); + + public static final FoodComponent LEAN = new FoodComponent.Builder() + .alwaysEdible().hunger(1).saturationModifier(1F) + .statusEffect(new StatusEffectInstance(StatusEffects.SATURATION, 600, 0), 1) + .statusEffect(new StatusEffectInstance(StatusEffects.HUNGER, 600, 159), 1) + .statusEffect(new StatusEffectInstance(StatusEffects.LEVITATION, 600, 0), 1) + .statusEffect(new StatusEffectInstance(StatusEffects.GLOWING, 1200, 0), 1) + .build(); + + public static final FoodComponent RAT_POISON = new FoodComponent.Builder() + .alwaysEdible().hunger(1).saturationModifier(0F) + .statusEffect(new StatusEffectInstance(StatusEffects.SLOWNESS, 1200, 1), 1) + .statusEffect(new StatusEffectInstance(IncubusCore.ZONKED, 1200, 0), 1) + .build(); +} diff --git a/src/main/java/net/id/incubus_core/misc/item/IncubusMusicDiscItem.java b/src/main/java/net/id/incubus_core/misc/item/IncubusMusicDiscItem.java new file mode 100644 index 0000000..7a637b6 --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/item/IncubusMusicDiscItem.java @@ -0,0 +1,10 @@ +package net.id.incubus_core.misc.item; + +import net.minecraft.item.MusicDiscItem; +import net.minecraft.sound.SoundEvent; + +public class IncubusMusicDiscItem extends MusicDiscItem { + public IncubusMusicDiscItem(int comparatorOutput, SoundEvent sound, Settings settings, int length) { + super(comparatorOutput, sound, settings, length); + } +} diff --git a/src/main/java/net/id/incubus_core/misc/item/LongSpatulaItem.java b/src/main/java/net/id/incubus_core/misc/item/LongSpatulaItem.java new file mode 100644 index 0000000..ece5568 --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/item/LongSpatulaItem.java @@ -0,0 +1,159 @@ +package net.id.incubus_core.misc.item; + +import net.id.incubus_core.misc.IncubusDamageSources; +import net.id.incubus_core.misc.IncubusPlayerData; +import net.id.incubus_core.misc.IncubusSounds; +import net.id.incubus_core.misc.WorthinessChecker; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.item.ShovelItem; +import net.minecraft.item.ToolMaterial; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; +import java.util.List; +import java.util.Optional; + +public class LongSpatulaItem extends ShovelItem { + + public LongSpatulaItem(ToolMaterial material, float attackDamage, float attackSpeed, Settings settings) { + super(material, attackDamage, attackSpeed, settings); + } + + @Override + public boolean postHit(ItemStack stack, LivingEntity target, LivingEntity attacker) { + var random = attacker.getRandom(); + + if (target.getType() != EntityType.ENDERMAN && random.nextInt(5) == 0) { + target.setOnFireFor(4); + target.playSound(IncubusSounds.BLAST, 0.75F, 0.9F + random.nextFloat() * 0.2F); + } + return super.postHit(stack, target, attacker); + } + + @Override + public ActionResult useOnEntity(ItemStack stack, PlayerEntity user, LivingEntity entity, Hand hand) { + if (user.getItemCooldownManager().isCoolingDown(this)) + return ActionResult.FAIL; + + if (!WorthinessChecker.isPlayerWorthy(user.getUuid(), Optional.of(user))) + user.sendMessage(Text.translatable("you'll never be grillin").styled(style -> style.withColor(0xffc591)), true); + + if (!entity.isOnGround() && entity.hasStatusEffect(StatusEffects.WEAKNESS)) { + applyRiposteEffects(entity, user); + user.getItemCooldownManager().set(this, 18); + } + else { + if (entity instanceof PlayerEntity player) { + IncubusPlayerData.get(player).setParryStateTicks(6); + user.getItemCooldownManager().set(this, 18); + } + else if (entity.getPos().distanceTo(user.getPos()) < entity.getBoundingBox().getAverageSideLength() + 1) { + applyParryEffects(entity, user); + user.getItemCooldownManager().set(this, 18); + } + } + + return ActionResult.success(user.getWorld().isClient()); + } + + public static void applyRiposteEffects(LivingEntity entity, PlayerEntity attacker) { + var world = entity.getWorld(); + var random = entity.getRandom(); + + entity.clearStatusEffects(); + entity.addVelocity(0, -5 + random.nextFloat() * 5, 0); + + entity.damage(IncubusDamageSources.grillin(attacker), WorthinessChecker.isPlayerWorthy(attacker.getUuid(), Optional.of(attacker)) ? entity.getMaxHealth() / 3F + 15F : 10F); + + attacker.addStatusEffect(new StatusEffectInstance(StatusEffects.JUMP_BOOST, 40, 13)); + entity.playSound(IncubusSounds.SLAM, 1, 1); + if (!world.isClient()) { + Box bounds = entity.getBoundingBox(); + for (int j = 0; j < Math.pow(bounds.getAverageSideLength() * 10, 2); j++) { + ((ServerWorld) world).spawnParticles(random.nextBoolean() ? ParticleTypes.SMALL_FLAME : ParticleTypes.FLAME, entity.getX() + (random.nextDouble() * bounds.getXLength() - bounds.getXLength() / 2), entity.getY() + (random.nextDouble() * bounds.getYLength()), entity.getZ() + (random.nextDouble() * bounds.getZLength() - bounds.getZLength() / 2), random.nextInt(4), 0, 0, 0, random.nextFloat() * 0.25F); + if (random.nextBoolean() && random.nextBoolean()) + ((ServerWorld) world).spawnParticles(ParticleTypes.CAMPFIRE_COSY_SMOKE, entity.getX() + (random.nextDouble() * bounds.getXLength() - bounds.getXLength() / 2), entity.getY() + (random.nextDouble() * bounds.getYLength()), entity.getZ() + (random.nextDouble() * bounds.getZLength() - bounds.getZLength() / 2), random.nextInt(4), 0, 0, 0, random.nextFloat() * 0.125F); + } + } + + var drops = random.nextInt(13) + 4; + + if(entity.getType() == EntityType.COW) { + for (int i = 0; i < drops; i++) { + entity.dropStack(new ItemStack(Items.COOKED_BEEF)); + } + } + else if(entity.getType() == EntityType.PIG) { + for (int i = 0; i < drops; i++) { + entity.dropStack(new ItemStack(Items.COOKED_PORKCHOP)); + } + } + else if(entity.getType() == EntityType.SHEEP) { + for (int i = 0; i < drops; i++) { + entity.dropStack(new ItemStack(Items.COOKED_MUTTON)); + } + } + else if(entity.getType() == EntityType.CHICKEN) { + for (int i = 0; i < drops * 1.5; i++) { + entity.dropStack(new ItemStack(Items.COOKED_CHICKEN)); + } + } + else if(entity.getType() == EntityType.RABBIT) { + for (int i = 0; i < drops * 2; i++) { + entity.dropStack(new ItemStack(Items.COOKED_RABBIT)); + } + } + else if(entity.getType() == EntityType.PILLAGER) { + for (int i = 0; i < drops; i++) { + entity.dropStack(new ItemStack(Items.COOKED_PORKCHOP)); + } + } + } + + @Override + public Text getName() { + var name = super.getName(); + return name.getWithStyle(name.getStyle().withColor(0xffc591)).get(0); + } + + @Override + public Text getName(ItemStack stack) { + var name = super.getName(stack); + return name.getWithStyle(name.getStyle().withColor(0xffc591)).get(0); + } + + @Override + public void appendTooltip(ItemStack stack, @Nullable World world, List tooltip, TooltipContext context) { + tooltip.add(Text.literal("§f§oFor The Worthy")); + tooltip.add(Text.literal("§ojust wanna grill, man").styled(style -> style.withColor(0xffc591))); + super.appendTooltip(stack, world, tooltip, context); + } + + public static void applyParryEffects(LivingEntity entity, PlayerEntity user) { + entity.setFireTicks(30); + + entity.addStatusEffect(new StatusEffectInstance(StatusEffects.SLOWNESS, 15, 4)); + entity.addStatusEffect(new StatusEffectInstance(StatusEffects.MINING_FATIGUE, 15, 4)); + entity.addStatusEffect(new StatusEffectInstance(StatusEffects.LEVITATION, 6, 35)); + entity.addStatusEffect(new StatusEffectInstance(StatusEffects.WEAKNESS, 30, 9)); + + entity.takeKnockback(0.45, MathHelper.sin(user.getYaw() * 0.017453292F), -MathHelper.cos(user.getYaw() * 0.017453292F)); + user.addStatusEffect(new StatusEffectInstance(StatusEffects.JUMP_BOOST, 20, 13)); + + entity.playSound(IncubusSounds.PARRY, 1, 1); + entity.timeUntilRegen = 0; + } +} diff --git a/src/main/java/net/id/incubus_core/misc/item/LunarianSaberItem.java b/src/main/java/net/id/incubus_core/misc/item/LunarianSaberItem.java new file mode 100644 index 0000000..97f732d --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/item/LunarianSaberItem.java @@ -0,0 +1,116 @@ +package net.id.incubus_core.misc.item; + +import net.id.incubus_core.misc.WorthinessChecker; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.SwordItem; +import net.minecraft.item.ToolMaterial; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.random.Random; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Optional; + +public class LunarianSaberItem extends SwordItem { + + public LunarianSaberItem(ToolMaterial toolMaterial, int attackDamage, float attackSpeed, Settings settings) { + super(toolMaterial, attackDamage, attackSpeed, settings); + } + + @Override + public boolean postHit(ItemStack stack, LivingEntity target, LivingEntity attacker) { + + World world = target.world; + Random random = target.getRandom(); + + if(!WorthinessChecker.isPlayerWorthy(attacker.getUuid(), attacker instanceof PlayerEntity player ? Optional.of(player) : Optional.empty())) { + WorthinessChecker.smite(attacker); + } + + if(target.isUndead()) { + target.addStatusEffect(new StatusEffectInstance(StatusEffects.INSTANT_HEALTH, 1, 1), attacker); + } + else { + target.addStatusEffect(new StatusEffectInstance(StatusEffects.INSTANT_DAMAGE, 1, 1), attacker); + } + + target.addStatusEffect(new StatusEffectInstance(StatusEffects.WEAKNESS, 60, 1), attacker); + target.addStatusEffect(new StatusEffectInstance(StatusEffects.SLOWNESS, 60, 2), attacker); + + Box bounds = target.getBoundingBox(target.getPose()); + + if(attacker.getRandom().nextFloat() <= (target.getHealth() / target.getMaxHealth()) / 2) { + StatusEffectInstance effect = attacker.getStatusEffect(StatusEffects.HEALTH_BOOST); + if(effect != null) { + if(effect.getAmplifier() < 4) { + attacker.addStatusEffect(new StatusEffectInstance(StatusEffects.HEALTH_BOOST, 1200, effect.getAmplifier() + 1), attacker); + } + } + else { + attacker.addStatusEffect(new StatusEffectInstance(StatusEffects.HEALTH_BOOST, 1200, 0), attacker); + } + + attacker.addStatusEffect(new StatusEffectInstance(StatusEffects.REGENERATION, 20, 4)); + + target.damage(DamageSource.GENERIC, target.getMaxHealth() / 10 + 1); + world.playSoundFromEntity(null, attacker, SoundEvents.BLOCK_RESPAWN_ANCHOR_CHARGE, SoundCategory.PLAYERS, 2F, 2F); + + if(!world.isClient()) { + for (int i = 0; i < Math.pow(bounds.getAverageSideLength() * 2, 2); i++) { + ((ServerWorld) world).spawnParticles(ParticleTypes.END_ROD, target.getX() + (random.nextDouble() * bounds.getXLength() - bounds.getXLength() / 2), target.getY() + (random.nextDouble() * bounds.getYLength()), target.getZ() + (random.nextDouble() * bounds.getZLength() - bounds.getZLength() / 2), random.nextInt(4), 0, 0, 0, 0.085); + } + } + } + + target.timeUntilRegen = 0; + + return true; + } + + @Override + public ActionResult useOnEntity(ItemStack stack, PlayerEntity user, LivingEntity target, Hand hand) { + Box bounds = target.getBoundingBox(target.getPose()); + Random random = target.getRandom(); + World world = target.world; + + target.clearStatusEffects(); + + target.addStatusEffect(new StatusEffectInstance(StatusEffects.REGENERATION, 100, 0)); + + if(!world.isClient()) { + for (int i = 0; i < Math.pow(bounds.getAverageSideLength() * 2, 1.5); i++) { + ((ServerWorld) world).spawnParticles(ParticleTypes.END_ROD, target.getX(), target.getY() + bounds.getYLength() / 2, target.getZ(), random.nextInt(4) + 2, 0, 0, 0, 0.145); + } + } + + world.playSoundFromEntity(null, target, SoundEvents.BLOCK_BEACON_ACTIVATE, SoundCategory.PLAYERS, 1.5F, 2F); + + return ActionResult.success(user.world.isClient()); + } + + @Override + public boolean hasGlint(ItemStack stack) { + return false; + } + + @Override + public void appendTooltip(ItemStack stack, @Nullable World world, List tooltip, TooltipContext context) { + tooltip.add(Text.literal("§f§oFor The Worthy")); + tooltip.add(Text.literal("§b§olook to la luna")); + super.appendTooltip(stack, world, tooltip, context); + } +} diff --git a/src/main/java/net/id/incubus_core/misc/item/PathStateAccessor.java b/src/main/java/net/id/incubus_core/misc/item/PathStateAccessor.java new file mode 100644 index 0000000..9376632 --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/item/PathStateAccessor.java @@ -0,0 +1,19 @@ +package net.id.incubus_core.misc.item; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.item.ShovelItem; +import net.minecraft.item.ToolMaterial; + +import java.util.Map; + +public abstract class PathStateAccessor extends ShovelItem { + + private PathStateAccessor(ToolMaterial material, float attackDamage, float attackSpeed, Settings settings) { + super(material, attackDamage, attackSpeed, settings); + } + + public static Map getPathStates() { + return PATH_STATES; + } +} diff --git a/src/main/java/net/id/incubus_core/misc/playerdata/PlayerData.java b/src/main/java/net/id/incubus_core/misc/playerdata/PlayerData.java new file mode 100644 index 0000000..9c124e9 --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/playerdata/PlayerData.java @@ -0,0 +1,100 @@ +package net.id.incubus_core.misc.playerdata; + +import dev.onyxstudios.cca.api.v3.component.sync.AutoSyncedComponent; +import dev.onyxstudios.cca.api.v3.component.tick.ServerTickingComponent; +import dev.onyxstudios.cca.api.v3.entity.PlayerComponent; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.nbt.NbtCompound; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public class PlayerData implements AutoSyncedComponent, ServerTickingComponent, PlayerComponent { + + private final PlayerEntity player; + + private boolean firstSpawn = true; + private boolean giveSpawnItems = true; + private boolean deemedWorthy = false; + private boolean blockRendering = false; + + private int parryStateTicks = 0; + + public PlayerData(PlayerEntity player) { + this.player = player; + } + + @Override + public void serverTick() { + if (parryStateTicks > 0) + parryStateTicks--; + + if (giveSpawnItems) { + giveSpawnItems = false; + + SpawnItemRegistry.getSpawnItems() + .stream() + .filter(entry -> !entry.firstSpawnOnly() || firstSpawn) + .filter(entry -> entry.additionalChecks().test(player)) + .map(entry -> entry.stack().copy()) + .forEach(player::giveItemStack); + + if (firstSpawn) { + firstSpawn = false; + } + } + } + + @Override + public void readFromNbt(NbtCompound tag) { + firstSpawn = tag.getBoolean("firstSpawn"); + giveSpawnItems = tag.getBoolean("giveSpawnItems"); + deemedWorthy = tag.getBoolean("deemedWorthy;"); + blockRendering = tag.getBoolean("blockRendering"); + parryStateTicks = tag.getInt("parryStateTicks"); + } + + @Override + public void writeToNbt(NbtCompound tag) { + tag.putBoolean("firstSpawn", firstSpawn); + tag.putBoolean("giveSpawnItems", giveSpawnItems); + tag.putBoolean("deemedWorthy", deemedWorthy); + tag.putBoolean("blockRendering", blockRendering); + tag.putInt("parryStateTicks", parryStateTicks); + } + + public boolean shouldSkipRender() { + return blockRendering; + } + + public void setBlockRendering(boolean blockRendering) { + this.blockRendering = blockRendering; + } + + public boolean isDeemedWorthy() { + return deemedWorthy; + } + + public void deemWorthy(boolean worthy) { + this.deemedWorthy = worthy; + } + + public int getParryStateTicks() { + return parryStateTicks; + } + + public void setParryStateTicks(int ticks) { + parryStateTicks = ticks; + } + + @Override + public boolean shouldCopyForRespawn(boolean lossless, boolean keepInventory, boolean sameCharacter) { + return true; + } + + @Override + public void copyForRespawn(PlayerData original, boolean lossless, boolean keepInventory, boolean sameCharacter) { + PlayerComponent.super.copyForRespawn(original, lossless, keepInventory, sameCharacter); + giveSpawnItems = !lossless && !keepInventory; + parryStateTicks = !lossless ? 0 : original.parryStateTicks; + } +} diff --git a/src/main/java/net/id/incubus_core/misc/playerdata/SpawnItemRegistry.java b/src/main/java/net/id/incubus_core/misc/playerdata/SpawnItemRegistry.java new file mode 100644 index 0000000..cdee0f9 --- /dev/null +++ b/src/main/java/net/id/incubus_core/misc/playerdata/SpawnItemRegistry.java @@ -0,0 +1,27 @@ +package net.id.incubus_core.misc.playerdata; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +public class SpawnItemRegistry { + + private static final List SPAWN_ITEMS = new ArrayList<>(); + + public static void add(ItemStack stack, boolean firstSpawnOnly) { + SPAWN_ITEMS.add(new SpawnItemEntry(stack, firstSpawnOnly, playerEntity -> true)); + } + + public static void add(ItemStack stack, boolean firstSpawnOnly, Predicate additionalChecks) { + SPAWN_ITEMS.add(new SpawnItemEntry(stack, firstSpawnOnly, additionalChecks)); + } + + static List getSpawnItems() { + return SPAWN_ITEMS; + } + + public record SpawnItemEntry(ItemStack stack, boolean firstSpawnOnly, Predicate additionalChecks) {} +} diff --git a/src/main/java/net/id/incubus_core/mixin/Plugin.java b/src/main/java/net/id/incubus_core/mixin/Plugin.java new file mode 100644 index 0000000..c16ba88 --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/Plugin.java @@ -0,0 +1,48 @@ +package net.id.incubus_core.mixin; + +import net.fabricmc.loader.api.FabricLoader; +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; + +import java.util.List; +import java.util.Set; + +public final class Plugin implements IMixinConfigPlugin { + + @Override + public void onLoad(String mixinPackage) { + } + + @Override + public String getRefMapperConfig() { + return null; + } + + @Override + public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { + if (mixinClassName.equals("net.id.incubus_core.mixin.client.InterpFixMixin")) { + return !FabricLoader.getInstance().isModLoaded("thorium"); + } else { + return true; + } + } + + @Override + public void acceptTargets(Set myTargets, Set otherTargets) { + } + + @Override + public List getMixins() { + return List.of(); + } + + @Override + public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + } + + @Override + public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + } + +} \ No newline at end of file diff --git a/src/main/java/net/id/incubus_core/mixin/blocklikeentities/ServerWorldMixin.java b/src/main/java/net/id/incubus_core/mixin/blocklikeentities/ServerWorldMixin.java new file mode 100644 index 0000000..6260201 --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/blocklikeentities/ServerWorldMixin.java @@ -0,0 +1,39 @@ +package net.id.incubus_core.mixin.blocklikeentities; + +import net.id.incubus_core.IncubusCore; +import net.id.incubus_core.blocklikeentities.api.BlockLikeEntity; +import net.id.incubus_core.blocklikeentities.api.BlockLikeSet; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.EntityList; +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.function.BooleanSupplier; + +@Mixin(ServerWorld.class) +public abstract class ServerWorldMixin { + + @Shadow + @Final + EntityList entityList; + @Shadow + private int idleTimeout; + + @Inject(method = "tick", at = @At(value = "RETURN")) + void postEntityTick(BooleanSupplier shouldKeepTicking, CallbackInfo ci) { + if (this.idleTimeout < 300) { + entityList.forEach(entityObj -> { + if (entityObj instanceof BlockLikeEntity entity) { + entity.postTick(); + } else if (entityObj == null) { + IncubusCore.LOG.error("Started checking null entities in ServerWorldMixin::postEntityTick"); + } + }); + BlockLikeSet.getAllSets().forEachRemaining(BlockLikeSet::postTick); + } + } +} diff --git a/src/main/java/net/id/incubus_core/mixin/blocklikeentities/client/ClientPlayerEntityMixin.java b/src/main/java/net/id/incubus_core/mixin/blocklikeentities/client/ClientPlayerEntityMixin.java new file mode 100644 index 0000000..f296308 --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/blocklikeentities/client/ClientPlayerEntityMixin.java @@ -0,0 +1,34 @@ +package net.id.incubus_core.mixin.blocklikeentities.client; + +import net.id.incubus_core.blocklikeentities.util.PostTickEntity; +import net.minecraft.client.network.ClientPlayerEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +// This gets disabled if another mod is present, if you add something to this mixin make sure you account for that. +@Mixin(ClientPlayerEntity.class) +public abstract class ClientPlayerEntityMixin implements PostTickEntity { + @Shadow protected abstract void sendMovementPackets(); + + @Unique boolean incubus_core$sendMovement = false; + + /** + * Since the player can be moved by FloatingBlockEntity after ClientPlayerEntity.tick() + * the call to sendMovementPackets() needs to be delayed till after all FloatingBlockEntities have ticked + */ + @Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;sendMovementPackets()V")) + void redirectSendMovementPackets(ClientPlayerEntity clientPlayerEntity) { + incubus_core$sendMovement = true; + } + + @Override + public void postTick() { + if (incubus_core$sendMovement) { + sendMovementPackets(); + incubus_core$sendMovement = false; + } + } +} diff --git a/src/main/java/net/id/incubus_core/mixin/blocklikeentities/client/ClientWorldMixin.java b/src/main/java/net/id/incubus_core/mixin/blocklikeentities/client/ClientWorldMixin.java new file mode 100644 index 0000000..3bd101d --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/blocklikeentities/client/ClientWorldMixin.java @@ -0,0 +1,30 @@ +package net.id.incubus_core.mixin.blocklikeentities.client; + +import net.id.incubus_core.blocklikeentities.api.BlockLikeSet; +import net.id.incubus_core.blocklikeentities.util.PostTickEntity; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.world.EntityList; +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(ClientWorld.class) +public abstract class ClientWorldMixin { + + @Shadow + @Final + EntityList entityList; + + @Inject(method = "tickEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/world/ClientWorld;tickBlockEntities()V")) + void postEntityTick(CallbackInfo ci) { + entityList.forEach(entity -> { + if (entity instanceof PostTickEntity postTickEntity) { + postTickEntity.postTick(); + } + }); + BlockLikeSet.getAllSets().forEachRemaining(BlockLikeSet::postTick); + } +} diff --git a/src/main/java/net/id/incubus_core/mixin/client/AnimationAccessor.java b/src/main/java/net/id/incubus_core/mixin/client/AnimationAccessor.java new file mode 100644 index 0000000..92d0725 --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/client/AnimationAccessor.java @@ -0,0 +1,14 @@ +package net.id.incubus_core.mixin.client; + +import net.minecraft.client.texture.Sprite; +import net.minecraft.client.texture.SpriteContents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; + +@Mixin(SpriteContents.Animation.class) +public interface AnimationAccessor { + @Accessor + List getFrames(); +} diff --git a/src/main/java/net/id/incubus_core/mixin/client/AnimationFrameAccessor.java b/src/main/java/net/id/incubus_core/mixin/client/AnimationFrameAccessor.java new file mode 100644 index 0000000..7f0aa3d --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/client/AnimationFrameAccessor.java @@ -0,0 +1,14 @@ +package net.id.incubus_core.mixin.client; + +import net.minecraft.client.texture.SpriteContents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(SpriteContents.AnimationFrame.class) +public interface AnimationFrameAccessor { + @Accessor + int getTime(); + + @Accessor + int getIndex(); +} diff --git a/src/main/java/net/id/incubus_core/mixin/client/CapeMixin.java b/src/main/java/net/id/incubus_core/mixin/client/CapeMixin.java new file mode 100644 index 0000000..0786836 --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/client/CapeMixin.java @@ -0,0 +1,32 @@ +package net.id.incubus_core.mixin.client; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.id.incubus_core.misc.WorthinessChecker; +import net.id.incubus_core.misc.WorthinessChecker.CapeType; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.entity.Entity; +import net.minecraft.util.Identifier; +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; + + +@SuppressWarnings("ConstantConditions") +@Environment(EnvType.CLIENT) +@Mixin(AbstractClientPlayerEntity.class) +public abstract class CapeMixin { + @Inject( + method = "getCapeTexture", + at = @At("HEAD"), + cancellable = true + ) + private void getCapeTexture(CallbackInfoReturnable cir){ + var cape = WorthinessChecker.getCapeType(((Entity) (Object) (this)).getUuid()); + if(cape.render) { + cir.setReturnValue(cape.capePath); + cir.cancel(); + } + } +} diff --git a/src/main/java/net/id/incubus_core/mixin/client/InGameHudMixin.java b/src/main/java/net/id/incubus_core/mixin/client/InGameHudMixin.java new file mode 100644 index 0000000..5331c18 --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/client/InGameHudMixin.java @@ -0,0 +1,39 @@ +package net.id.incubus_core.mixin.client; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.id.incubus_core.render.OverlayRegistrar; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.hud.InGameHud; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.util.Identifier; +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; + +@Mixin(InGameHud.class) +@Environment(EnvType.CLIENT) +public abstract class InGameHudMixin { + + @Shadow + protected abstract void renderOverlay(Identifier texture, float opacity); + + @Inject(method = "renderHotbar", at = @At("HEAD")) + public void renderOverlay(float tickDelta, MatrixStack matrices, CallbackInfo ci) { + List overlays = OverlayRegistrar.getOverlays(); + Entity entity = MinecraftClient.getInstance().cameraEntity; + if (entity instanceof LivingEntity player) { + overlays.forEach(overlay -> { + if (overlay.renderPredicate().test(player)) { + renderOverlay(overlay.path(), overlay.opacityProvider().apply(player)); + } + }); + } + } +} diff --git a/src/main/java/net/id/incubus_core/mixin/client/InterpFixMixin.java b/src/main/java/net/id/incubus_core/mixin/client/InterpFixMixin.java new file mode 100644 index 0000000..da62a52 --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/client/InterpFixMixin.java @@ -0,0 +1,63 @@ +package net.id.incubus_core.mixin.client; + +import net.minecraft.client.texture.NativeImage; +import net.minecraft.client.texture.SpriteContents; +import org.spongepowered.asm.mixin.*; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(SpriteContents.Interpolation.class) +public abstract class InterpFixMixin { + + @Shadow protected abstract int getPixelColor(SpriteContents.Animation animation, int frameIndex, int layer, int x, int y); + + @Shadow protected abstract int lerp(double delta, int to, int from); + + @Shadow @Final private NativeImage[] images; + + @Unique + SpriteContents parent$this; + + @Inject(method = "", at = @At("RETURN")) + public void assignParent(SpriteContents spriteContents, CallbackInfo ci) { + this.parent$this = spriteContents; + } + + // replace this with an inject or redirect, so we don't need to accesswiden Sprite.Animation and Sprite.AnimationFrame + /** + * @author Azzy + * @reason My nuts itch, also translucent interp is borked. + */ + @Overwrite + public void apply(int x, int y, SpriteContents.AnimatorImpl animator) { + var accessor = (AnimatorAccessor) animator; + var animation = ((AnimationAccessor) accessor.getAnimation()); + + var animationFrame = animation.getFrames().get(accessor.getFrame()); + double d = 1.0D - accessor.getCurrentTime() / (double)((AnimationFrameAccessor) animationFrame).getTime(); + int i = ((AnimationFrameAccessor) animationFrame).getIndex(); + int j = ((AnimationFrameAccessor) animation.getFrames().get((accessor.getFrame() + 1) % animation.getFrames().size())).getIndex(); + if (i != j) { + for(int k = 0; k < this.images.length; ++k) { + int l = parent$this.getWidth() >> k; + int m = parent$this.getHeight() >> k; + + for(int n = 0; n < m; ++n) { + for(int o = 0; o < l; ++o) { + int p = this.getPixelColor(accessor.getAnimation(), i, k, o, n); + int q = this.getPixelColor(accessor.getAnimation(), j, k, o, n); + int a = this.lerp(d, p >> 24 & 255, q >> 24 & 255); + int r = this.lerp(d, p >> 16 & 255, q >> 16 & 255); + int g = this.lerp(d, p >> 8 & 255, q >> 8 & 255); + int b = this.lerp(d, p & 255, q & 255); + this.images[k].setColor(o, n, (a << 24) | (r << 16) | (g << 8) | b); + } + } + } + + ((SpriteContentsAccessor) parent$this).callUpload(x, y, 0, 0, this.images); + } + + } +} diff --git a/src/main/java/net/id/incubus_core/mixin/client/PlayerRendererMixin.java b/src/main/java/net/id/incubus_core/mixin/client/PlayerRendererMixin.java new file mode 100644 index 0000000..f0047b6 --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/client/PlayerRendererMixin.java @@ -0,0 +1,21 @@ +package net.id.incubus_core.mixin.client; + +import net.id.incubus_core.misc.IncubusPlayerData; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.PlayerEntityRenderer; +import net.minecraft.client.util.math.MatrixStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(PlayerEntityRenderer.class) +public abstract class PlayerRendererMixin { + + @Inject(method = "render(Lnet/minecraft/client/network/AbstractClientPlayerEntity;FFLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At("HEAD"), cancellable = true) + public void blockRendering(AbstractClientPlayerEntity abstractClientPlayerEntity, float f, float g, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, CallbackInfo ci) { + if (IncubusPlayerData.get(abstractClientPlayerEntity).shouldSkipRender()) + ci.cancel(); + } +} diff --git a/src/main/java/net/id/incubus_core/mixin/client/RenderLayerAccessor.java b/src/main/java/net/id/incubus_core/mixin/client/RenderLayerAccessor.java new file mode 100644 index 0000000..2c3e0f3 --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/client/RenderLayerAccessor.java @@ -0,0 +1,35 @@ +/* + * Satin + * Copyright (C) 2019-2022 Ladysnake + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; If not, see . + */ +package net.id.incubus_core.mixin.client; + +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.VertexFormat; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(RenderLayer.class) +public interface RenderLayerAccessor { + @Accessor + boolean isTranslucent(); + + @Invoker("of") + static RenderLayer.MultiPhase incubus$of(@SuppressWarnings("unused") String name, @SuppressWarnings("unused") VertexFormat vertexFormat, @SuppressWarnings("unused") VertexFormat.DrawMode drawMode, @SuppressWarnings("unused") int expectedBufferSize, @SuppressWarnings("unused") boolean hasCrumbling, @SuppressWarnings("unused") boolean translucent, @SuppressWarnings("unused") RenderLayer.MultiPhaseParameters phases) { + throw new IllegalStateException("Mixin not transformed"); + } +} diff --git a/src/main/java/net/id/incubus_core/mixin/client/SpriteContentsAccessor.java b/src/main/java/net/id/incubus_core/mixin/client/SpriteContentsAccessor.java new file mode 100644 index 0000000..36981db --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/client/SpriteContentsAccessor.java @@ -0,0 +1,13 @@ +package net.id.incubus_core.mixin.client; + +import net.minecraft.client.texture.NativeImage; +import net.minecraft.client.texture.SpriteContents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(SpriteContents.class) +public interface SpriteContentsAccessor { + + @Invoker("upload") + void callUpload(int x, int y, int unpackSkipPixels, int unpackSkipRows, NativeImage[] images); +} diff --git a/src/main/java/net/id/incubus_core/mixin/entity/BlockEntityMixin.java b/src/main/java/net/id/incubus_core/mixin/entity/BlockEntityMixin.java new file mode 100644 index 0000000..6963e3c --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/entity/BlockEntityMixin.java @@ -0,0 +1,13 @@ +package net.id.incubus_core.mixin.entity; + +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.util.math.BlockPos; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(BlockEntity.class) +public interface BlockEntityMixin { + + @Accessor + void setPos(BlockPos pos); +} diff --git a/src/main/java/net/id/incubus_core/mixin/entity/EntityDamageSourceMixin.java b/src/main/java/net/id/incubus_core/mixin/entity/EntityDamageSourceMixin.java new file mode 100644 index 0000000..efb2d7a --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/entity/EntityDamageSourceMixin.java @@ -0,0 +1,69 @@ +package net.id.incubus_core.mixin.entity; + +import java.util.Optional; +import net.id.incubus_core.misc.CustomDeathMessageProvider; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.damage.EntityDamageSource; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +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 org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +/** + * Current mixin features: + * - Hooks death message for easy item-specific messages + * + * @author gudenau + */ +@Mixin(EntityDamageSource.class) +public abstract class EntityDamageSourceMixin extends DamageSource { + /** + * Never called. + * + * @hidden + */ + private EntityDamageSourceMixin() { + super(null); + } + + /** + * Hook for the custom death message. + * + * @param entity The killed entity + * @param cir Mixin callback information + * @param stack The item stack used to attack + */ + @Inject( + method = "getDeathMessage", + at = @At( + value = "FIELD", + target = "Lnet/minecraft/entity/damage/EntityDamageSource;name:Ljava/lang/String;" + ), + cancellable = true, + locals = LocalCapture.CAPTURE_FAILHARD + ) + private void getDeathMessage(LivingEntity entity, CallbackInfoReturnable cir, ItemStack stack) { + if (stack.getItem() instanceof CustomDeathMessageProvider.EntityDamage customItem) { + var customText = customItem.getDeathMessage( + (EntityDamageSource) (Object) this, + entity, + new CustomDeathMessageProvider.EntityDamage.Data( + stack, + switch (name) { + case "sting" -> CustomDeathMessageProvider.EntityDamageType.STING; + case "mob" -> CustomDeathMessageProvider.EntityDamageType.MOB; + case "player" -> CustomDeathMessageProvider.EntityDamageType.PLAYER; + case "thorns" -> CustomDeathMessageProvider.EntityDamageType.THORNS; + case "explosion.player" -> CustomDeathMessageProvider.EntityDamageType.PLAYER_EXPLOSION; + default -> CustomDeathMessageProvider.EntityDamageType.OTHER; + } + ) + ); + customText.ifPresent(cir::setReturnValue); + } + } +} diff --git a/src/main/java/net/id/incubus_core/mixin/entity/FoxEditorMixin.java b/src/main/java/net/id/incubus_core/mixin/entity/FoxEditorMixin.java new file mode 100644 index 0000000..2c5febd --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/entity/FoxEditorMixin.java @@ -0,0 +1,62 @@ +package net.id.incubus_core.mixin.entity; + +import net.id.incubus_core.misc.item.IncubusCoreItems; +import net.id.incubus_core.util.FoxDuck; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.passive.AnimalEntity; +import net.minecraft.entity.passive.FoxEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; +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.CallbackInfoReturnable; + +import java.util.UUID; + +@Mixin(FoxEntity.class) +public abstract class FoxEditorMixin extends AnimalEntity implements FoxDuck { + + protected FoxEditorMixin(EntityType entityType, World world) { + super(entityType, world); + } + + @Inject(method = "canPickupItem", at = @At("HEAD"), cancellable = true) + public void allowDroppingBerryBranch(ItemStack stack, CallbackInfoReturnable cir) { + var heldStack = getEquippedStack(EquipmentSlot.MAINHAND); + if (heldStack.isOf(IncubusCoreItems.BERRY_BRANCH)) { + if (stack.isOf(Items.TOTEM_OF_UNDYING) || stack.isOf(Items.ELYTRA) || stack.isOf(Items.ENCHANTED_GOLDEN_APPLE) || stack.isOf(Items.ENDER_EYE)) { + cir.setReturnValue(true); + cir.cancel(); + } + } + } + + @Shadow public abstract FoxEntity.Type getVariant(); + + @Shadow + public abstract void setVariant(FoxEntity.Type type); + + @Shadow abstract void addTrustedUuid(@Nullable UUID uuid); + + @Shadow public abstract void setVariant(Object par1); + + @Override + public FoxEntity.Type getFoxColor() { + return getVariant(); + } + + @Override + public void setFoxColor(FoxEntity.Type type) { + setVariant(type); + } + + @Override + public void addTrustedUUID(UUID id) { + addTrustedUuid(id); + } +} diff --git a/src/main/java/net/id/incubus_core/mixin/player/PlayerDropMixin.java b/src/main/java/net/id/incubus_core/mixin/player/PlayerDropMixin.java new file mode 100644 index 0000000..5960e2f --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/player/PlayerDropMixin.java @@ -0,0 +1,40 @@ +package net.id.incubus_core.mixin.player; + +import net.minecraft.entity.EntityType; +import net.minecraft.entity.ItemEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; +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 static net.id.incubus_core.misc.Players.AZZY; +import static net.id.incubus_core.misc.Players.DAF; + +import java.util.UUID; + +@Mixin(PlayerEntity.class) +public abstract class PlayerDropMixin extends LivingEntity { + + @Shadow @Nullable public abstract ItemEntity dropItem(ItemStack stack, boolean throwRandomly, boolean retainOwnership); + + protected PlayerDropMixin(EntityType entityType, World world) { + super(entityType, world); + } + + @Inject(method = "dropInventory", at = @At("HEAD")) + public void incubusCore$dropInventory(CallbackInfo ci) { + var uuid = getUuid(); + if(uuid.equals(AZZY)) { + dropItem(Items.SWEET_BERRIES.getDefaultStack(), false, false); + } else if(uuid.equals(DAF)) { + dropItem(Items.AMETHYST_SHARD.getDefaultStack(), false, false); + } + } +} diff --git a/src/main/java/net/id/incubus_core/mixin/player/PlayerEntityMixin.java b/src/main/java/net/id/incubus_core/mixin/player/PlayerEntityMixin.java new file mode 100644 index 0000000..6462fc1 --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/player/PlayerEntityMixin.java @@ -0,0 +1,30 @@ +package net.id.incubus_core.mixin.player; + +import net.id.incubus_core.misc.IncubusPlayerData; +import net.id.incubus_core.misc.item.LongSpatulaItem; +import net.id.incubus_core.misc.playerdata.PlayerData; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.player.PlayerEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(PlayerEntity.class) +public abstract class PlayerEntityMixin { + + @Inject(method = "damage", at = @At("HEAD"), cancellable = true) + public void parry(DamageSource source, float amount, CallbackInfoReturnable cir) { + if (source.getAttacker() instanceof PlayerEntity player) { + var playerData = IncubusPlayerData.get(player); + + if (playerData.getParryStateTicks() > 0) { + playerData.setParryStateTicks(0); + + LongSpatulaItem.applyParryEffects(player, PlayerEntity.class.cast(this)); + + cir.cancel(); + } + } + } +} diff --git a/src/main/java/net/id/incubus_core/mixin/player/PlayerManagerMixin.java b/src/main/java/net/id/incubus_core/mixin/player/PlayerManagerMixin.java new file mode 100644 index 0000000..56aaa4e --- /dev/null +++ b/src/main/java/net/id/incubus_core/mixin/player/PlayerManagerMixin.java @@ -0,0 +1,37 @@ +package net.id.incubus_core.mixin.player; + +import net.minecraft.network.ClientConnection; +import net.minecraft.server.PlayerManager; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyArgs; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.invoke.arg.Args; + +import static net.id.incubus_core.misc.Players.AZZY; + +@Mixin(PlayerManager.class) +public abstract class PlayerManagerMixin { + + @Unique + private final MutableText MARKER = (MutableText) Text.of(""); + + @ModifyArgs(method = "onPlayerConnect", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V")) + public void markJoinMessage(Args args, ClientConnection connection, ServerPlayerEntity player) { + if (player.getUuid().equals(AZZY)) + args.set(0, MARKER); + } + + @Inject(method = "broadcast(Lnet/minecraft/text/Text;Z)V", at = @At("HEAD"), cancellable = true) + public void cancelJoinMessage(Text message, boolean overlay, CallbackInfo ci) { + if (message == MARKER) { + ci.cancel(); + } + } + +} diff --git a/src/main/java/net/id/incubus_core/networking/C2SPacket.java b/src/main/java/net/id/incubus_core/networking/C2SPacket.java new file mode 100644 index 0000000..f6ca514 --- /dev/null +++ b/src/main/java/net/id/incubus_core/networking/C2SPacket.java @@ -0,0 +1,46 @@ +package net.id.incubus_core.networking; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; + +/** + * Client to Server Abstract packet + */ +public abstract class C2SPacket implements Packet, ServerPlayNetworking.PlayChannelHandler { + + @Environment(EnvType.CLIENT) + public void send() { + var id = this.getId(); + var packetByteBuf = PacketByteBufs.create(); + this.write(packetByteBuf); + ClientPlayNetworking.send(id, packetByteBuf); + } + + @Override + public void receive(MinecraftServer server, ServerPlayerEntity player, ServerPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { + this.read(buf); + server.execute(() -> execute(server, player, handler, buf, responseSender)); // Switch from the network thread and execute on the server + } + + /** + * Executed after a packet is received on the server + */ + abstract void execute(MinecraftServer server, ServerPlayerEntity player, ServerPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender); + + /** + * Registers a Client to Server Packet + * + * @param id Packet identifier + * @param packetObj A reference to the packet class you want to register + */ + public static void register(Identifier id, T packetObj) { ServerPlayNetworking.registerGlobalReceiver(id, packetObj); } +} diff --git a/src/main/java/net/id/incubus_core/networking/Packet.java b/src/main/java/net/id/incubus_core/networking/Packet.java new file mode 100644 index 0000000..a3f8b4b --- /dev/null +++ b/src/main/java/net/id/incubus_core/networking/Packet.java @@ -0,0 +1,15 @@ +package net.id.incubus_core.networking; + +import net.minecraft.network.PacketByteBuf; +import net.minecraft.util.Identifier; + +public interface Packet { + + void write(PacketByteBuf buf); + + void read(PacketByteBuf buf); + + void send(); + + Identifier getId(); +} diff --git a/src/main/java/net/id/incubus_core/networking/S2CPacket.java b/src/main/java/net/id/incubus_core/networking/S2CPacket.java new file mode 100644 index 0000000..7598e47 --- /dev/null +++ b/src/main/java/net/id/incubus_core/networking/S2CPacket.java @@ -0,0 +1,55 @@ +package net.id.incubus_core.networking; + +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; + +import java.util.List; + +/** + * Server to Client Abstract packet + */ +public abstract class S2CPacket implements Packet { + + private List recipients; // List of recipients to send the packet to + + public S2CPacket() {} // Default constructor, used when registering the packet + + public S2CPacket(List recipients) { + this.recipients = recipients; + } + + @Override + public void send() { + var id = getId(); + var packetByteBuf = PacketByteBufs.create(); + this.write(packetByteBuf); + + for(var player : recipients) + ServerPlayNetworking.send(player, id, packetByteBuf); + } + + public void receive(MinecraftClient client, ClientPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { + this.read(buf); + client.execute(() -> execute(client, handler, buf, responseSender)); // Switch from networking thread to client thread + } + + /** + * Executed after a packet is received on the client + */ + abstract void execute(MinecraftClient client, ClientPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender); + + /** + * Registers a Server to Client Packet + * + * @param id Packet identifier + * @param packetObj A reference to the packet class you want to register + */ + public static void register(Identifier id, T packetObj) { ClientPlayNetworking.registerGlobalReceiver(id, packetObj::receive); } +} diff --git a/src/main/java/net/id/incubus_core/recipe/IncubusRecipe.java b/src/main/java/net/id/incubus_core/recipe/IncubusRecipe.java new file mode 100644 index 0000000..89a622b --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/IncubusRecipe.java @@ -0,0 +1,29 @@ +package net.id.incubus_core.recipe; + +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; +import net.minecraft.recipe.Recipe; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import java.util.Collections; +import java.util.List; + +@SuppressWarnings("unused") +public interface IncubusRecipe extends Recipe { + + boolean isEmpty(); + + @Override + IncubusRecipeType getType(); + + List getInputs(); + + @Override + default String getGroup() { + return Registries.ITEM.getId(getOutput().getItem()).getPath(); + } + + default List getOutputs() { + return Collections.singletonList(getOutput()); + } +} diff --git a/src/main/java/net/id/incubus_core/recipe/IncubusRecipeType.java b/src/main/java/net/id/incubus_core/recipe/IncubusRecipeType.java new file mode 100644 index 0000000..d57f442 --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/IncubusRecipeType.java @@ -0,0 +1,24 @@ +package net.id.incubus_core.recipe; + +import net.minecraft.recipe.Recipe; +import net.minecraft.recipe.RecipeType; +import net.minecraft.util.Identifier; + +@SuppressWarnings("unused") +public abstract class IncubusRecipeType> implements RecipeType { + + private final Identifier id; + + public IncubusRecipeType(Identifier id) { + this.id = id; + } + + @Override + public String toString() { + return id.toString(); + } + + public Identifier getId() { + return id; + } +} diff --git a/src/main/java/net/id/incubus_core/recipe/IncubusRecipeTypes.java b/src/main/java/net/id/incubus_core/recipe/IncubusRecipeTypes.java new file mode 100644 index 0000000..ec16f7d --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/IncubusRecipeTypes.java @@ -0,0 +1,13 @@ +package net.id.incubus_core.recipe; + +import static net.id.incubus_core.IncubusCore.locate; + +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; + +public class IncubusRecipeTypes { + + public static void init() { + Registry.register(Registries.RECIPE_SERIALIZER, locate("item_damaging"), ItemDamagingRecipe.Serializer.INSTANCE); + } +} diff --git a/src/main/java/net/id/incubus_core/recipe/IngredientStack.java b/src/main/java/net/id/incubus_core/recipe/IngredientStack.java new file mode 100644 index 0000000..6ae7381 --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/IngredientStack.java @@ -0,0 +1,168 @@ +package net.id.incubus_core.recipe; + +import net.id.incubus_core.recipe.matchbook.Matchbook; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemConvertible; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.recipe.Ingredient; +import net.minecraft.util.collection.DefaultedList; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +@SuppressWarnings("unused") +public final class IngredientStack { + + public static final IngredientStack EMPTY = new IngredientStack(Ingredient.EMPTY, Matchbook.empty(), Optional.empty(), 0); + private final Ingredient ingredient; + private final Matchbook matchbook; + private final Optional recipeViewNbt; + private final int count; + + private IngredientStack(@NotNull Ingredient ingredient, @NotNull Matchbook matchbook, Optional recipeViewNbt, int count) { + this.ingredient = ingredient; + this.matchbook = matchbook; + this.recipeViewNbt = recipeViewNbt; + this.count = count; + } + + public static IngredientStack of(@NotNull Ingredient ingredient, @NotNull Matchbook matchbook, @Nullable NbtCompound recipeViewNbt, int count) { + if(ingredient.isEmpty()) { + return EMPTY; + } + return new IngredientStack(ingredient, matchbook, Optional.ofNullable(recipeViewNbt), count); + } + + public static IngredientStack of(Ingredient ingredient) { + return of(ingredient, Matchbook.empty(), null, 1); + } + + public static IngredientStack ofItems(ItemConvertible... items) { + return of(Ingredient.ofItems(items), Matchbook.empty(), null, 1); + } + + public static IngredientStack ofItems(int count, ItemConvertible... items) { + return of(Ingredient.ofItems(items), Matchbook.empty(), null, count); + } + + public static IngredientStack ofStacks(ItemStack... stacks) { + return of(Ingredient.ofStacks(stacks), Matchbook.empty(), null, 1); + } + + public static IngredientStack ofStacks(int count, ItemStack... stacks) { + return of(Ingredient.ofStacks(stacks), Matchbook.empty(), null, count); + } + + public boolean test(ItemStack stack) { + return ingredient.test(stack) && stack.getCount() >= count && matchbook.test(stack); + } + + public boolean testStrict(ItemStack stack) { + return ingredient.test(stack) && stack.getCount() == count && matchbook.test(stack); + } + + public void write(PacketByteBuf buf) { + ingredient.write(buf); + matchbook.write(buf); + buf.writeBoolean(recipeViewNbt.isPresent()); + recipeViewNbt.ifPresent(buf::writeNbt); + buf.writeInt(count); + } + + public static IngredientStack fromByteBuf(PacketByteBuf buf) { + return new IngredientStack(Ingredient.fromPacket(buf), Matchbook.fromByteBuf(buf), buf.readBoolean() ? Optional.ofNullable(buf.readNbt()) : Optional.empty(), buf.readInt()); + } + + public static List decodeByteBuf(PacketByteBuf buf, int size) { + List ingredients = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + ingredients.add(fromByteBuf(buf)); + } + return ingredients; + } + + @SuppressWarnings("all") + public List getStacks() { + var stacks = ingredient.getMatchingStacks(); + + if (stacks == null) + return new ArrayList<>(); + + return Arrays.stream(stacks) + .peek(stack -> stack.setCount(count)) + .peek(stack -> recipeViewNbt.ifPresent(nbt -> stack.setNbt(nbt))) + .collect(Collectors.toList()); + } + + public Ingredient getIngredient() { + return ingredient; + } + + public int getCount() { + return count; + } + + public boolean isEmpty() { + return this == EMPTY || ingredient.isEmpty(); + } + + public static DefaultedList listIngredients(List ingredients) { + DefaultedList preview = DefaultedList.ofSize(ingredients.size(), Ingredient.EMPTY); + for (int i = 0; i < ingredients.size(); i++) { + preview.set(i, ingredients.get(i).getIngredient()); + } + return preview; + } + + + public static boolean matchInvExclusively(Inventory inv, List ingredients, int size, int offset) { + List invStacks = new ArrayList<>(size); + for (int i = offset; i < size + offset; i++) { + invStacks.add(inv.getStack(i)); + } + AtomicInteger matches = new AtomicInteger(); + ingredients.forEach(ingredient -> { + for (int i = 0; i < invStacks.size(); i++) { + if(ingredient.isEmpty()) { + matches.getAndIncrement(); + break; + } + ItemStack stack = invStacks.get(i); + if(ingredient.test(stack)) { + matches.getAndIncrement(); + invStacks.remove(i); + break; + } + } + }); + return matches.get() == size; + } + + public static void decrementExclusively(Inventory inv, List ingredients, int size, int offset) { + List invStacks = new ArrayList<>(size); + for (int i = offset; i < size + offset; i++) { + invStacks.add(inv.getStack(i)); + } + ingredients.forEach(ingredient -> { + for (int i = 0; i < invStacks.size(); i++) { + if(ingredient.isEmpty()) { + break; + } + ItemStack stack = invStacks.get(i); + if(ingredient.test(stack)) { + stack.decrement(ingredient.count); + invStacks.remove(i); + break; + } + } + }); + } +} diff --git a/src/main/java/net/id/incubus_core/recipe/ItemDamagingRecipe.java b/src/main/java/net/id/incubus_core/recipe/ItemDamagingRecipe.java new file mode 100644 index 0000000..b5bb25d --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/ItemDamagingRecipe.java @@ -0,0 +1,54 @@ +package net.id.incubus_core.recipe; + +import com.google.gson.JsonObject; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.recipe.ShapelessRecipe; +import net.minecraft.util.Identifier; +import net.minecraft.util.collection.DefaultedList; + +public class ItemDamagingRecipe extends ShapelessRecipe { + + public ItemDamagingRecipe(ShapelessRecipe parent) { + super(parent.getId(), parent.getGroup(), parent.getCategory(), parent.getOutput(), parent.getIngredients()); + } + + @Override + public DefaultedList getRemainder(CraftingInventory inventory) { + DefaultedList defaultedList = DefaultedList.ofSize(inventory.size(), ItemStack.EMPTY); + for (int i = 0; i < defaultedList.size(); ++i) { + ItemStack item = inventory.getStack(i); + if (item.getItem().isDamageable() && item.getDamage() + 1 < item.getMaxDamage()) { // Override damageable, fallback onto remainders + item = item.copy(); + item.setDamage(item.getDamage() + 1); // Damage item by one + defaultedList.set(i, item); + } else if (item.getItem().hasRecipeRemainder()) { + defaultedList.set(i, new ItemStack(item.getItem().getRecipeRemainder())); + } + } + return defaultedList; + } + + @Override + public RecipeSerializer getSerializer() { + return net.id.incubus_core.recipe.ItemDamagingRecipe.Serializer.INSTANCE; + } + + public static class Serializer extends ShapelessRecipe.Serializer { + + public static final ItemDamagingRecipe.Serializer INSTANCE = new net.id.incubus_core.recipe.ItemDamagingRecipe.Serializer(); + + @Override + public ShapelessRecipe read(Identifier identifier, JsonObject jsonObject) { + return new ItemDamagingRecipe(super.read(identifier, jsonObject)); + } + + @Override + public ShapelessRecipe read(Identifier identifier, PacketByteBuf packetByteBuf) { + return new ItemDamagingRecipe(super.read(identifier, packetByteBuf)); + } + } +} + diff --git a/src/main/java/net/id/incubus_core/recipe/OptionalStack.java b/src/main/java/net/id/incubus_core/recipe/OptionalStack.java new file mode 100644 index 0000000..72a5696 --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/OptionalStack.java @@ -0,0 +1,123 @@ +package net.id.incubus_core.recipe; +import net.id.incubus_core.IncubusCore; +import net.id.incubus_core.util.RegistryHelper; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.tag.TagKey; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@SuppressWarnings("unused") +public class OptionalStack { + + public static final TagKey EMPTY_FOLLY = TagKey.of(Registries.ITEM.getKey(), IncubusCore.locate("empty_item_folly")); + public static final OptionalStack EMPTY = new OptionalStack(EMPTY_FOLLY, 0); + + @NotNull + private final Optional> tag; + @NotNull + private final ItemStack stack; + private final int count; + private static final Registry REGISTRY = Registries.ITEM; + + + private List cachedStacks = null; + + public OptionalStack(@NotNull TagKey tag, int count) { + this.tag = Optional.of(tag); + this.stack = ItemStack.EMPTY; + this.count = count; + } + + public OptionalStack(@NotNull ItemStack stack, int count) { + this.stack = stack; + this.tag = Optional.empty(); + this.count = count; + } + + public void write(PacketByteBuf buf) { + getStacks(); + buf.writeInt(cachedStacks.size()); + for (ItemStack cachedStack : cachedStacks) { + buf.writeItemStack(cachedStack); + } + buf.writeInt(count); + } + + public static OptionalStack fromByteBuf(PacketByteBuf buf) { + List stacks = new ArrayList<>(); + int size = buf.readInt(); + for (int i = 0; i < size; i++) { + stacks.add(buf.readItemStack()); + } + OptionalStack folly = new OptionalStack(ItemStack.EMPTY, buf.readInt()); + folly.cachedStacks = stacks; + return folly; + } + + public static List decodeByteBuf(PacketByteBuf buf, int size) { + List stacks = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + stacks.add(fromByteBuf(buf)); + } + return stacks; + } + + public boolean isEmpty() { + return this == EMPTY || (tag.map(RegistryHelper::isTagEmpty).orElse(true) && stack.isEmpty() && cachedStacks.isEmpty()); + } + + public List getStacks() { + assert !isEmpty() : "Can't access an empty OptionalStack! Did you check if it was empty first?"; + if(cachedStacks == null) { + if((tag.map(RegistryHelper::isTagEmpty).orElse(true))) { + cachedStacks = Collections.singletonList(stack); + } else { + var entries = RegistryHelper.getEntries(tag.get()); + entries.ifPresent(registryEntries -> cachedStacks = registryEntries.stream().map(RegistryEntry::value).map(item -> new ItemStack(item, count)).collect(Collectors.toList())); + } + } + return cachedStacks; + } + + public int getCount() { + return count; + } + + public @Nullable ItemStack getFirstStack() { + if(cachedStacks == null) + return getStacks().get(0); + else + return cachedStacks.get(0); + } + + public boolean itemMatch(ItemStack stack) { + if(cachedStacks == null) + getStacks(); + if(cachedStacks.isEmpty()) { + return false; + } + else + return cachedStacks.stream().anyMatch(testStack -> testStack.isItemEqual(stack)); + } + + public boolean contains(ItemStack stack) { + if(cachedStacks == null) + getStacks(); + if(cachedStacks.isEmpty()) { + return false; + } + else + return cachedStacks.stream().anyMatch(testStack -> testStack.isItemEqual(stack) && stack.getCount() >= testStack.getCount()); + } +} diff --git a/src/main/java/net/id/incubus_core/recipe/matchbook/BooleanMatch.java b/src/main/java/net/id/incubus_core/recipe/matchbook/BooleanMatch.java new file mode 100644 index 0000000..376ff04 --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/matchbook/BooleanMatch.java @@ -0,0 +1,65 @@ +package net.id.incubus_core.recipe.matchbook; + +import com.google.gson.JsonObject; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.PacketByteBuf; + +public class BooleanMatch extends Match { + + private boolean booleanValue; + + public BooleanMatch(String name, String key) { + super(name, key); + } + + @Override + boolean matches(ItemStack stack) { + var nbt = stack.getOrCreateNbt(); + + if(nbt.contains(key)) { + return nbt.getBoolean(key) == booleanValue; + } + + return false; + } + + @Override + void configure(JsonObject json) { + booleanValue = json.get("value").getAsBoolean(); + } + + @Override + void configure(PacketByteBuf buf) { + booleanValue = buf.readBoolean(); + } + + @Override + void write(PacketByteBuf buf) { + buf.writeBoolean(booleanValue); + } + + public static class Factory extends MatchFactory { + + public Factory() { + super("boolean"); + } + + @Override + public BooleanMatch create(String key, JsonObject object) { + var match = new BooleanMatch(name, key); + match.configure(object); + + return match; + } + + @Override + public BooleanMatch fromPacket(PacketByteBuf buf) { + var match = new BooleanMatch(name, buf.readString()); + match.configure(buf); + + return match; + } + } + +} diff --git a/src/main/java/net/id/incubus_core/recipe/matchbook/ByteMatch.java b/src/main/java/net/id/incubus_core/recipe/matchbook/ByteMatch.java new file mode 100644 index 0000000..412cdb6 --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/matchbook/ByteMatch.java @@ -0,0 +1,65 @@ +package net.id.incubus_core.recipe.matchbook; + +import com.google.gson.JsonObject; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.PacketByteBuf; + +public class ByteMatch extends Match { + + private byte targetByte; + + public ByteMatch(String name, String key) { + super(name, key); + } + + @Override + boolean matches(ItemStack stack) { + var nbt = stack.getOrCreateNbt(); + + if(nbt.contains(key)) { + return nbt.getByte(key) == targetByte; + } + + return false; + } + + @Override + void configure(JsonObject json) { + targetByte = json.get("target").getAsByte(); + } + + @Override + void configure(PacketByteBuf buf) { + targetByte = buf.readByte(); + } + + @Override + void write(PacketByteBuf buf) { + buf.writeByte(targetByte); + } + + public static class Factory extends MatchFactory { + + public Factory() { + super("byte"); + } + + @Override + public ByteMatch create(String key, JsonObject object) { + var match = new ByteMatch(name, key); + match.configure(object); + + return match; + } + + @Override + public ByteMatch fromPacket(PacketByteBuf buf) { + var match = new ByteMatch(name, buf.readString()); + match.configure(buf); + + return match; + } + } + +} diff --git a/src/main/java/net/id/incubus_core/recipe/matchbook/EnchantmentMatch.java b/src/main/java/net/id/incubus_core/recipe/matchbook/EnchantmentMatch.java new file mode 100644 index 0000000..965bbb5 --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/matchbook/EnchantmentMatch.java @@ -0,0 +1,103 @@ +package net.id.incubus_core.recipe.matchbook; + +import com.google.gson.JsonObject; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.PacketByteBuf; + +public class EnchantmentMatch extends Match { + + private boolean singular; + private int minLevel; + private int maxLevel; + private String enchantmentId; + + public EnchantmentMatch(String name, String key) { + super(name, key); + } + + @Override + boolean matches(ItemStack stack) { + var nbt = stack.getOrCreateNbt(); + + if(nbt.contains(key)) { + if (singular) { + + return testEnchantment(nbt); + + } + else { + + var success = false; + var enchants = nbt.getList(key, 10); + for (int i = 0; i < enchants.size(); i++) { + + success = testEnchantment(enchants.getCompound(i)); + + if(success) { + break; + } + } + return success; + + } + } + + return false; + } + + boolean testEnchantment(NbtCompound nbt) { + var level = nbt.getShort("lvl"); + var id = nbt.getString("id"); + + return id.equals(enchantmentId) && maxLevel >= level && level >= minLevel; + } + + @Override + void configure(JsonObject json) { + singular = json.has("singular") && json.get("singular").getAsBoolean(); + minLevel = json.get("minLevel").getAsInt(); + maxLevel = json.get("maxLevel").getAsInt(); + enchantmentId = json.get("enchantmentId").getAsString(); + } + + @Override + void configure(PacketByteBuf buf) { + singular = buf.readBoolean(); + minLevel = buf.readInt(); + maxLevel = buf.readInt(); + enchantmentId = buf.readString(); + } + + @Override + void write(PacketByteBuf buf) { + buf.writeBoolean(singular); + buf.writeInt(minLevel); + buf.writeInt(maxLevel); + buf.writeString(enchantmentId); + } + + public static class Factory extends MatchFactory { + + public Factory() { + super("enchantment"); + } + + @Override + public EnchantmentMatch create(String key, JsonObject object) { + var match = new EnchantmentMatch(name, key); + match.configure(object); + + return match; + } + + @Override + public EnchantmentMatch fromPacket(PacketByteBuf buf) { + var match = new EnchantmentMatch(name, buf.readString()); + match.configure(buf); + + return match; + } + } + +} diff --git a/src/main/java/net/id/incubus_core/recipe/matchbook/FloatMatch.java b/src/main/java/net/id/incubus_core/recipe/matchbook/FloatMatch.java new file mode 100644 index 0000000..5180535 --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/matchbook/FloatMatch.java @@ -0,0 +1,72 @@ +package net.id.incubus_core.recipe.matchbook; + +import com.google.gson.JsonObject; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; + +/** + * Exclusive. + */ +public class FloatMatch extends Match { + + private float min; + private float max; + + public FloatMatch(String name, String key) { + super(name, key); + } + + @Override + boolean matches(ItemStack stack) { + var nbt = stack.getOrCreateNbt(); + + if(nbt.contains(key)) { + var testFloat = nbt.getFloat(key); + return max > testFloat && testFloat > min; + } + + return false; + } + + @Override + void configure(JsonObject json) { + min = json.get("min").getAsFloat(); + min = json.get("max").getAsFloat(); + } + + @Override + void configure(PacketByteBuf buf) { + min = buf.readFloat(); + max = buf.readFloat(); + } + + @Override + void write(PacketByteBuf buf) { + buf.writeFloat(min); + buf.writeFloat(max); + } + + public static class Factory extends MatchFactory { + + public Factory() { + super("float"); + } + + @Override + public FloatMatch create(String key, JsonObject object) { + var match = new FloatMatch(name, key); + match.configure(object); + + return match; + } + + @Override + public FloatMatch fromPacket(PacketByteBuf buf) { + var match = new FloatMatch(name, buf.readString()); + match.configure(buf); + + return match; + } + } + +} diff --git a/src/main/java/net/id/incubus_core/recipe/matchbook/IncubusMatches.java b/src/main/java/net/id/incubus_core/recipe/matchbook/IncubusMatches.java new file mode 100644 index 0000000..6a42726 --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/matchbook/IncubusMatches.java @@ -0,0 +1,16 @@ +package net.id.incubus_core.recipe.matchbook; + +public class IncubusMatches { + + public static void init() { + MatchRegistry.registerInternal("int", new IntMatch.Factory()); + MatchRegistry.registerInternal("intRange", new IntRangeMatch.Factory()); + MatchRegistry.registerInternal("float", new FloatMatch.Factory()); + MatchRegistry.registerInternal("long", new LongMatch.Factory()); + MatchRegistry.registerInternal("short", new ShortMatch.Factory()); + MatchRegistry.registerInternal("byte", new ByteMatch.Factory()); + MatchRegistry.registerInternal("string", new StringMatch.Factory()); + MatchRegistry.registerInternal("boolean", new BooleanMatch.Factory()); + MatchRegistry.registerInternal("enchantment", new EnchantmentMatch.Factory()); + } +} diff --git a/src/main/java/net/id/incubus_core/recipe/matchbook/IntMatch.java b/src/main/java/net/id/incubus_core/recipe/matchbook/IntMatch.java new file mode 100644 index 0000000..0cc428b --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/matchbook/IntMatch.java @@ -0,0 +1,65 @@ +package net.id.incubus_core.recipe.matchbook; + +import com.google.gson.JsonObject; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.PacketByteBuf; + +public class IntMatch extends Match { + + private int targetInt; + + public IntMatch(String name, String key) { + super(name, key); + } + + @Override + boolean matches(ItemStack stack) { + var nbt = stack.getOrCreateNbt(); + + if(nbt.contains(key)) { + return nbt.getInt(key) == targetInt; + } + + return false; + } + + @Override + void configure(JsonObject json) { + targetInt = json.get("target").getAsInt(); + } + + @Override + void configure(PacketByteBuf buf) { + targetInt = buf.readInt(); + } + + @Override + void write(PacketByteBuf buf) { + buf.writeInt(targetInt); + } + + public static class Factory extends MatchFactory { + + public Factory() { + super("int"); + } + + @Override + public IntMatch create(String key, JsonObject object) { + var match = new IntMatch(name, key); + match.configure(object); + + return match; + } + + @Override + public IntMatch fromPacket(PacketByteBuf buf) { + var match = new IntMatch(name, buf.readString()); + match.configure(buf); + + return match; + } + } + +} diff --git a/src/main/java/net/id/incubus_core/recipe/matchbook/IntRangeMatch.java b/src/main/java/net/id/incubus_core/recipe/matchbook/IntRangeMatch.java new file mode 100644 index 0000000..4b83bf1 --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/matchbook/IntRangeMatch.java @@ -0,0 +1,72 @@ +package net.id.incubus_core.recipe.matchbook; + +import com.google.gson.JsonObject; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; + +/** + * Inclusive. + */ +public class IntRangeMatch extends Match { + + private int min; + private int max; + + public IntRangeMatch(String name, String key) { + super(name, key); + } + + @Override + boolean matches(ItemStack stack) { + var nbt = stack.getOrCreateNbt(); + + if(nbt.contains(key)) { + var testInt = nbt.getInt(key); + return max >= testInt && testInt >= min; + } + + return false; + } + + @Override + void configure(JsonObject json) { + min = json.get("min").getAsInt(); + min = json.get("max").getAsInt(); + } + + @Override + void configure(PacketByteBuf buf) { + min = buf.readInt(); + max = buf.readInt(); + } + + @Override + void write(PacketByteBuf buf) { + buf.writeInt(min); + buf.writeInt(max); + } + + public static class Factory extends MatchFactory { + + public Factory() { + super("intRange"); + } + + @Override + public IntRangeMatch create(String key, JsonObject object) { + var match = new IntRangeMatch(name, key); + match.configure(object); + + return match; + } + + @Override + public IntRangeMatch fromPacket(PacketByteBuf buf) { + var match = new IntRangeMatch(name, buf.readString()); + match.configure(buf); + + return match; + } + } + +} diff --git a/src/main/java/net/id/incubus_core/recipe/matchbook/LongMatch.java b/src/main/java/net/id/incubus_core/recipe/matchbook/LongMatch.java new file mode 100644 index 0000000..88cc11f --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/matchbook/LongMatch.java @@ -0,0 +1,65 @@ +package net.id.incubus_core.recipe.matchbook; + +import com.google.gson.JsonObject; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.PacketByteBuf; + +public class LongMatch extends Match { + + private long targetLong; + + public LongMatch(String name, String key) { + super(name, key); + } + + @Override + boolean matches(ItemStack stack) { + var nbt = stack.getOrCreateNbt(); + + if(nbt.contains(key)) { + return nbt.getLong(key) == targetLong; + } + + return false; + } + + @Override + void configure(JsonObject json) { + targetLong = json.get("target").getAsLong(); + } + + @Override + void configure(PacketByteBuf buf) { + targetLong = buf.readLong(); + } + + @Override + void write(PacketByteBuf buf) { + buf.writeLong(targetLong); + } + + public static class Factory extends MatchFactory { + + public Factory() { + super("long"); + } + + @Override + public LongMatch create(String key, JsonObject object) { + var match = new LongMatch(name, key); + match.configure(object); + + return match; + } + + @Override + public LongMatch fromPacket(PacketByteBuf buf) { + var match = new LongMatch(name, buf.readString()); + match.configure(buf); + + return match; + } + } + +} diff --git a/src/main/java/net/id/incubus_core/recipe/matchbook/Match.java b/src/main/java/net/id/incubus_core/recipe/matchbook/Match.java new file mode 100644 index 0000000..7a2cf3f --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/matchbook/Match.java @@ -0,0 +1,38 @@ +package net.id.incubus_core.recipe.matchbook; + +import com.google.gson.JsonObject; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; + +/** + * Tests ItemStack nbt against a set of rules. Defined by json, must be able to be loaded from a bytebuf. + */ +public abstract class Match { + + protected final String name; + protected final String key; + + protected Match(String name, String key) { + this.name = name; + this.key = key; + } + + public String getName() { + return name; + } + + abstract boolean matches(ItemStack stack); + + abstract void configure(JsonObject json); + + abstract void configure(PacketByteBuf buf); + + public void writeInternal(PacketByteBuf buf) { + buf.writeString(name); + buf.writeString(key); + write(buf); + } + + abstract void write(PacketByteBuf buf); + +} diff --git a/src/main/java/net/id/incubus_core/recipe/matchbook/MatchFactory.java b/src/main/java/net/id/incubus_core/recipe/matchbook/MatchFactory.java new file mode 100644 index 0000000..15f99c6 --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/matchbook/MatchFactory.java @@ -0,0 +1,28 @@ +package net.id.incubus_core.recipe.matchbook; + +import com.google.gson.JsonObject; +import net.minecraft.network.PacketByteBuf; + +import java.util.Optional; + +/** + * A factory, tasked with both creating and configuring Matches, and also building them from packets. + * Ensure the name matches the registry id. + */ +public abstract class MatchFactory { + + protected final String name; + + protected MatchFactory(String name) { + this.name = name; + } + + public abstract T create(String key, JsonObject object); + + public abstract T fromPacket(PacketByteBuf buf); + + public static Optional> getForPacket(String name) { + return MatchRegistry.getOptional(name); + } + +} diff --git a/src/main/java/net/id/incubus_core/recipe/matchbook/MatchRegistry.java b/src/main/java/net/id/incubus_core/recipe/matchbook/MatchRegistry.java new file mode 100644 index 0000000..03cf09f --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/matchbook/MatchRegistry.java @@ -0,0 +1,41 @@ +package net.id.incubus_core.recipe.matchbook; + +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; +import net.minecraft.util.Identifier; + +import java.util.Optional; + +public class MatchRegistry { + + public static final Object2ObjectArrayMap> REGISTRY = new Object2ObjectArrayMap<>(); + + /** + * Registers a Match factory. + * @param id ensure this matches the name of the factory. + */ + public static void register(Identifier id, MatchFactory factory) { + var name = id.toString(); + + if(!name.equals(factory.name)) { + throw new IllegalStateException("id and name of " + factory.name + " do not match!"); + } + + REGISTRY.put(name, factory); + } + + /** + * This is not for you. + */ + static void registerInternal(String id, MatchFactory factory) { + REGISTRY.put(id, factory); + REGISTRY.put("incubus:" + id, factory); + } + + public static Optional> getOptional(String id) { + return Optional.ofNullable(REGISTRY.get(id)); + } + + public static > Optional getOptional(String id, Class clazz) { + return Optional.ofNullable(clazz.cast(REGISTRY.get(id))); + } +} diff --git a/src/main/java/net/id/incubus_core/recipe/matchbook/Matchbook.java b/src/main/java/net/id/incubus_core/recipe/matchbook/Matchbook.java new file mode 100644 index 0000000..c0fe9e8 --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/matchbook/Matchbook.java @@ -0,0 +1,91 @@ +package net.id.incubus_core.recipe.matchbook; + +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Mode defines how nbt is filtered. + * AND demands all matches return true to be considered a match. + * OR demands only one match return true for such to happen. + */ +public class Matchbook { + + private static final Matchbook EMPTY = new Matchbook(new ArrayList<>(), Mode.PASS); + private final List matches; + private final Mode mode; + + private Matchbook(List matches, Mode mode) { + this.matches = Collections.unmodifiableList(matches); + this.mode = mode; + } + + public boolean test(ItemStack stack) { + return switch (mode) { + case AND -> matches.stream().allMatch(match -> match.matches(stack)); + case OR -> matches.stream().anyMatch(match -> match.matches(stack)); + case PASS -> true; + }; + } + + public boolean isEmpty() { + return this == EMPTY || matches.isEmpty(); + } + + public static Matchbook empty() { + return EMPTY; + } + + public void write(PacketByteBuf buf) { + if(isEmpty()) { + buf.writeBoolean(true); + } else { + buf.writeBoolean(false); + buf.writeInt(mode.ordinal()); + buf.writeInt(matches.size()); + matches.forEach(match -> match.writeInternal(buf)); + } + } + + public static Matchbook fromByteBuf(PacketByteBuf buf) { + if(buf.readBoolean()) { + return empty(); + } + + var mode = Mode.values()[buf.readInt()]; + var size = buf.readInt(); + var list = new ArrayList(); + + for (int i = 0; i < size; i++) { + var name = buf.readString(); + + var factoryOptional = MatchFactory.getForPacket(name); + factoryOptional.ifPresent(factory -> list.add(factory.fromPacket(buf))); + } + + return new Matchbook(list, mode); + } + + public static class Builder { + + private final ArrayList matchHolder = new ArrayList<>(); + + public void add(Match match) { + matchHolder.add(match); + } + + public Matchbook build(Mode mode) { + return new Matchbook(matchHolder, mode); + } + + } + + public enum Mode { + AND, + OR, + PASS + } + +} diff --git a/src/main/java/net/id/incubus_core/recipe/matchbook/ShortMatch.java b/src/main/java/net/id/incubus_core/recipe/matchbook/ShortMatch.java new file mode 100644 index 0000000..05ffee9 --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/matchbook/ShortMatch.java @@ -0,0 +1,65 @@ +package net.id.incubus_core.recipe.matchbook; + +import com.google.gson.JsonObject; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.PacketByteBuf; + +public class ShortMatch extends Match { + + private short targetShort; + + public ShortMatch(String name, String key) { + super(name, key); + } + + @Override + boolean matches(ItemStack stack) { + var nbt = stack.getOrCreateNbt(); + + if(nbt.contains(key)) { + return nbt.getShort(key) == targetShort; + } + + return false; + } + + @Override + void configure(JsonObject json) { + targetShort = json.get("target").getAsShort(); + } + + @Override + void configure(PacketByteBuf buf) { + targetShort = buf.readShort(); + } + + @Override + void write(PacketByteBuf buf) { + buf.writeShort(targetShort); + } + + public static class Factory extends MatchFactory { + + public Factory() { + super("short"); + } + + @Override + public ShortMatch create(String key, JsonObject object) { + var match = new ShortMatch(name, key); + match.configure(object); + + return match; + } + + @Override + public ShortMatch fromPacket(PacketByteBuf buf) { + var match = new ShortMatch(name, buf.readString()); + match.configure(buf); + + return match; + } + } + +} diff --git a/src/main/java/net/id/incubus_core/recipe/matchbook/StringMatch.java b/src/main/java/net/id/incubus_core/recipe/matchbook/StringMatch.java new file mode 100644 index 0000000..7260527 --- /dev/null +++ b/src/main/java/net/id/incubus_core/recipe/matchbook/StringMatch.java @@ -0,0 +1,65 @@ +package net.id.incubus_core.recipe.matchbook; + +import com.google.gson.JsonObject; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.PacketByteBuf; + +public class StringMatch extends Match { + + private String targetString; + + public StringMatch(String name, String key) { + super(name, key); + } + + @Override + boolean matches(ItemStack stack) { + var nbt = stack.getOrCreateNbt(); + + if(nbt.contains(key)) { + return nbt.getString(key).equals(targetString); + } + + return false; + } + + @Override + void configure(JsonObject json) { + targetString = json.get("target").getAsString(); + } + + @Override + void configure(PacketByteBuf buf) { + targetString = buf.readString(); + } + + @Override + void write(PacketByteBuf buf) { + buf.writeString(targetString); + } + + public static class Factory extends MatchFactory { + + public Factory() { + super("string"); + } + + @Override + public StringMatch create(String key, JsonObject object) { + var match = new StringMatch(name, key); + match.configure(object); + + return match; + } + + @Override + public StringMatch fromPacket(PacketByteBuf buf) { + var match = new StringMatch(name, buf.readString()); + match.configure(buf); + + return match; + } + } + +} diff --git a/src/main/java/net/id/incubus_core/render/ColorUtils.java b/src/main/java/net/id/incubus_core/render/ColorUtils.java new file mode 100644 index 0000000..8b052e7 --- /dev/null +++ b/src/main/java/net/id/incubus_core/render/ColorUtils.java @@ -0,0 +1,19 @@ +package net.id.incubus_core.render; + +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3i; + +public class ColorUtils { + + public static int toHex(Vec3i color) { + return MathHelper.packRgb(color.getX(), color.getY(), color.getZ()); + } + + public static int toHexWithAlpha(int r, int g, int b, int a) { + return MathHelper.packRgb(r, g, b) | (a << 24); + } + + public static Vec3i toRGB(int hex) { + return new Vec3i((hex & 0xFF0000) >> 16, (hex & 0xFF00) >> 8, (hex & 0xFF)); + } +} diff --git a/src/main/java/net/id/incubus_core/render/IncubusRenderLayers.java b/src/main/java/net/id/incubus_core/render/IncubusRenderLayers.java new file mode 100644 index 0000000..ab8db4a --- /dev/null +++ b/src/main/java/net/id/incubus_core/render/IncubusRenderLayers.java @@ -0,0 +1,36 @@ +package net.id.incubus_core.render; + +import net.id.incubus_core.mixin.client.RenderLayerAccessor; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.RenderPhase; +import net.minecraft.client.render.VertexFormat; +import net.minecraft.client.render.VertexFormats; + +import static net.id.incubus_core.IncubusCore.locate; + +public class IncubusRenderLayers extends RenderLayer { + + public static final RenderLayer EMISSIVE = RenderLayerAccessor.incubus$of( + "bloom_base", + VertexFormats.POSITION_COLOR, + VertexFormat.DrawMode.QUADS, + 256, + false, + true, + RenderLayer.MultiPhaseParameters.builder() + .texture(new RenderPhase.Texture(locate("textures/special/blank.png"), false, false)) + .program(RenderPhase.LIGHTNING_PROGRAM) + .transparency(RenderPhase.LIGHTNING_TRANSPARENCY) + .target(RenderPhase.WEATHER_TARGET) + .lightmap(DISABLE_LIGHTMAP) + .build(true) + ); + + /** + * Do not. + */ + public IncubusRenderLayers(String name, VertexFormat vertexFormat, VertexFormat.DrawMode drawMode, int expectedBufferSize, boolean hasCrumbling, boolean translucent, Runnable startAction, Runnable endAction) { + super(name, vertexFormat, drawMode, expectedBufferSize, hasCrumbling, translucent, startAction, endAction); + throw new IllegalAccessError("why would you try this"); + } +} diff --git a/src/main/java/net/id/incubus_core/render/OverlayRegistrar.java b/src/main/java/net/id/incubus_core/render/OverlayRegistrar.java new file mode 100644 index 0000000..a08554c --- /dev/null +++ b/src/main/java/net/id/incubus_core/render/OverlayRegistrar.java @@ -0,0 +1,27 @@ +package net.id.incubus_core.render; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.entity.LivingEntity; +import net.minecraft.util.Identifier; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +@Environment(EnvType.CLIENT) +public class OverlayRegistrar { + + private static final List OVERLAYS = new ArrayList<>(); + + public static void register(Overlay overlay) { + OVERLAYS.add(overlay); + } + + public static List getOverlays() { + return OVERLAYS; + } + + public record Overlay(Identifier path, Predicate renderPredicate, Function opacityProvider) {} +} diff --git a/src/main/java/net/id/incubus_core/render/RenderHelper.java b/src/main/java/net/id/incubus_core/render/RenderHelper.java new file mode 100644 index 0000000..f288742 --- /dev/null +++ b/src/main/java/net/id/incubus_core/render/RenderHelper.java @@ -0,0 +1,93 @@ +package net.id.incubus_core.render; + +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.RotationAxis; +import net.minecraft.util.math.Vec3i; +import org.joml.Vector3f; + + +public class RenderHelper { + + public static void drawLaser(VertexConsumerProvider vertexConsumers, MatrixStack matrices, Vector3f color, float trans, float length, float scale, float offset, Direction direction) { + matrices.translate(offset, offset, offset); + directionalMatrixMultiply(matrices, direction); + matrices.translate(0F, 0F, offset); + matrices.translate(-scale / 2F, -scale / 2F, 0F); + matrices.scale(scale, scale, length); + RenderHelper.drawEmissiveCube(vertexConsumers, matrices, color, trans); + } + + public static void drawEmissiveCube(VertexConsumerProvider vertexConsumers, MatrixStack matrices, Vector3f color, float trans) { + MatrixStack.Entry matrix = matrices.peek(); + VertexConsumer consumer = vertexConsumers.getBuffer(IncubusRenderLayers.EMISSIVE); + + + //north + consumer.vertex(matrix.getPositionMatrix(), 0, 0, 0).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 0, 1, 0).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 1, 1, 0).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 1, 0, 0).color(color.x, color.y, color.z, trans).next(); + + //east + consumer.vertex(matrix.getPositionMatrix(), 1, 0, 0).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 1, 1, 0).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 1, 1, 1).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 1, 0, 1).color(color.x, color.y, color.z, trans).next(); + + //south + consumer.vertex(matrix.getPositionMatrix(), 1, 0, 1).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 1, 1, 1).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 0, 1, 1).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 0, 0, 1).color(color.x, color.y, color.z, trans).next(); + + //west + consumer.vertex(matrix.getPositionMatrix(), 0, 0, 1).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 0, 1, 1).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 0, 1, 0).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 0, 0, 0).color(color.x, color.y, color.z, trans).next(); + + //down + consumer.vertex(matrix.getPositionMatrix(), 0, 0, 0).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 1, 0, 0).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 1, 0, 1).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 0, 0, 1).color(color.x, color.y, color.z, trans).next(); + + //up + consumer.vertex(matrix.getPositionMatrix(), 0, 1, 1).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 1, 1, 1).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 1, 1, 0).color(color.x, color.y, color.z, trans).next(); + consumer.vertex(matrix.getPositionMatrix(), 0, 1, 0).color(color.x, color.y, color.z, trans).next(); + } + + public static void directionalMatrixOffset(MatrixStack matrices, Direction direction, float offset) { + Vec3i vector = direction.getVector(); + matrices.translate(vector.getX() * offset, vector.getY() * offset, vector.getZ() * offset); + } + + public static void directionalMatrixMultiply(MatrixStack matrices, Direction direction) { + switch (direction) { + case NORTH -> matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(180)); + case SOUTH -> {} + case EAST -> matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(90)); + case WEST -> matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(270)); + case UP -> matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(270)); + case DOWN -> matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(90)); + } + } + + public static float rfh(int hex) { + return (hex & 0xFF0000) >> 16; + + } + + public static float gfh(int hex) { + return (hex & 0xFF00) >> 8; + } + + public static float bfh(int hex) { + return (hex & 0xFF); + } +} diff --git a/src/main/java/net/id/incubus_core/resource_conditions/IncubusCoreResourceConditions.java b/src/main/java/net/id/incubus_core/resource_conditions/IncubusCoreResourceConditions.java new file mode 100644 index 0000000..380b0fa --- /dev/null +++ b/src/main/java/net/id/incubus_core/resource_conditions/IncubusCoreResourceConditions.java @@ -0,0 +1,24 @@ +package net.id.incubus_core.resource_conditions; + +import com.google.gson.JsonObject; +import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditions; +import net.fabricmc.loader.api.FabricLoader; +import net.id.incubus_core.IncubusCore; +import net.minecraft.util.Identifier; + +public class IncubusCoreResourceConditions { + + private static final Identifier DEV_ENV = IncubusCore.locate("is_dev_env"); + + /** + * Creates a condition that returns true if running in a development environment + */ + public static boolean isDevEnv(JsonObject object) { + return FabricLoader.getInstance().isDevelopmentEnvironment(); + } + + public static void init() { + ResourceConditions.register(DEV_ENV, IncubusCoreResourceConditions::isDevEnv); + } + +} diff --git a/src/main/java/net/id/incubus_core/status_effects/ZonkedEffect.java b/src/main/java/net/id/incubus_core/status_effects/ZonkedEffect.java new file mode 100644 index 0000000..785acde --- /dev/null +++ b/src/main/java/net/id/incubus_core/status_effects/ZonkedEffect.java @@ -0,0 +1,11 @@ +package net.id.incubus_core.status_effects; + +import net.minecraft.entity.effect.StatusEffect; +import net.minecraft.entity.effect.StatusEffectCategory; + +public class ZonkedEffect extends StatusEffect { + + public ZonkedEffect() { + super(StatusEffectCategory.BENEFICIAL, 0xffa6a7); + } +} diff --git a/src/main/java/net/id/incubus_core/systems/DefaultMaterials.java b/src/main/java/net/id/incubus_core/systems/DefaultMaterials.java new file mode 100644 index 0000000..ec333fe --- /dev/null +++ b/src/main/java/net/id/incubus_core/systems/DefaultMaterials.java @@ -0,0 +1,28 @@ +package net.id.incubus_core.systems; + +import net.id.incubus_core.IncubusCore; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; + +@SuppressWarnings("unused") +public class DefaultMaterials { + + public static void init() {} + + //public static final Material MYRCIL = register("myrcil", new Material(475, 0.03, 0, 0, 27500)); + public static final Material AIR = register("air", new Material(15000000, 0.25, 1, 50, 0, 0, 0, 0, 0)); + public static final Material IRON = register("iron", new Material(900, 9.5, 96, 0.25, 2048, 512, 85000, 512, 1024, new Identifier("c", "iron_ingots"))); + public static final Material COPPER = register("copper", new Material(550, 21.5, 256, 0.1, 256, 128, 22500, 2048, 512, new Identifier("c", "copper_ingots"))); + public static final Material GOLD = register("gold", new Material(500, 15, 196, 0.1, 256, 64, 15000, 32768, 1028, new Identifier("c", "gold_ingots"))); + public static final Material SILVER = register("silver", new Material(450, 17.5, 1024, 0.015, 128, 256, 31000, 8192, 4096, new Identifier("c", "silver_ingots"))); + public static final Material DIAMOND = register("diamond", new Material(175, 42.75, 16, 0.05, 16384, 128, 137000, 65536, 64, new Identifier("c", "diamonds"))); + public static final Material AMETHYST = register("amethyst", new Material(950, 13.5, 24, 0.25, 4096, 256, 47000, 32, 32768, new Identifier("c", "amethyst"))); + public static final Material QUARTZ = register("quartz", new Material(1500, 12, 128, 0.5, 4096, 256, 47000, 32, 32768, new Identifier("c", "quartz"))); + public static final Material FLESH = register("flesh", new Material(100, 31.25, 2.125, 1, 1024, 32, 8500, 32768, 2, new Identifier("c", "raw_meat"))); + //public static final Material HSLA_STEEL = register("hsla_steel", new Material(1600, 0.25, 1024, 2048, 150000)); + //public static final Material UNOBTANIUM = register("unobtanium", new Material(Double.POSITIVE_INFINITY, 1, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY)); + + private static Material register(String id, Material material) { + return Registry.register(RegistryRegistry.MATERIAL, IncubusCore.locate(id), material); + } +} diff --git a/src/main/java/net/id/incubus_core/systems/HeatHelper.java b/src/main/java/net/id/incubus_core/systems/HeatHelper.java new file mode 100644 index 0000000..f89399d --- /dev/null +++ b/src/main/java/net/id/incubus_core/systems/HeatHelper.java @@ -0,0 +1,76 @@ +package net.id.incubus_core.systems; + +import net.id.incubus_core.util.RandomShim; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; +import net.minecraft.world.biome.Biome; + +import java.util.Collections; +import java.util.List; + +public class HeatHelper { + + public static double exchangeHeat(Material medium, double tempA, double tempB, double exchangeArea) { + return medium.heatConductivity() * exchangeArea * (tempA - tempB); + } + + public static double playerAmbientHeat(Material medium, double playerTemp, BlockPos playerPos, Biome biome, boolean night, boolean rain) { + return -exchangeHeat(medium, playerTemp, translateBiomeHeat(playerPos, biome, night, rain), 4.5); + } + + public static double simulateAmbientHeating(HeatIo io, World world, BlockPos pos, Simulation simulation) { + var validExchangeDirs = io.getValidDirections(); + + if(!validExchangeDirs.isEmpty()) { + Collections.shuffle(validExchangeDirs, new RandomShim(world.getRandom())); + var exchangeDir = io.getPreferredDirection().orElse(validExchangeDirs.get(0)); + + if(validExchangeDirs.contains(exchangeDir)) { + double transfer = exchangeHeat(DefaultMaterials.AIR, io.getTemperature(), translateBiomeHeat(pos, world.getBiome(pos).value(), world.isNight(), world.isRaining()), io.getExchangeArea(exchangeDir)); + if(simulation == Simulation.ACT) { + io.cool(transfer); + } + return transfer; + } + } + return 0; + } + + public static double translateBiomeHeat(BlockPos pos, Biome biome, boolean night, boolean rain) { + + double baseTemp = biome.getTemperature(); + /*FIXME They remove Biome categories from what I can tell. This is just to make it compile for now. + Biome.Category category = ((BiomeAccessor) (Object) biome).getCategory(); + + double temp = switch (category) { + case NETHER -> baseTemp + 1; + case THEEND -> baseTemp - 1.5; + case DESERT -> baseTemp - 0.25; + case OCEAN -> baseTemp + 0.1; + default -> baseTemp; + }; + */ + double temp = baseTemp; + + temp *= 26.25; + + if(night) { + if(/*category == Biome.Category.DESERT || category == Biome.Category.MESA*/ false) { + temp -= temp * 0.75; + } else if(biome.getPrecipitation() == Biome.Precipitation.SNOW) { + if(/*category == Biome.Category.ICY*/ false && temp < 0) + temp += temp * 1.25; + else + temp -= 20; + + } else { + temp -= (biome.getPrecipitation() == Biome.Precipitation.NONE) ? 12 : 4; + } + } + if(rain) { + temp -= biome.getPrecipitation() == Biome.Precipitation.SNOW || biome.getPrecipitation() == Biome.Precipitation.RAIN && pos.getY() >= 100 ? 10 : 3; + } + return temp; + } +} diff --git a/src/main/java/net/id/incubus_core/systems/HeatIo.java b/src/main/java/net/id/incubus_core/systems/HeatIo.java new file mode 100644 index 0000000..37e0e96 --- /dev/null +++ b/src/main/java/net/id/incubus_core/systems/HeatIo.java @@ -0,0 +1,71 @@ +package net.id.incubus_core.systems; + +import net.minecraft.util.math.Direction; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * An object that can hold and optionally take and provide heat energy. + */ +@SuppressWarnings("unused") +public interface HeatIo extends MaterialProvider { + + /** + * Get the amount of heat stored. + */ + double getTemperature(); + + /** + * Return false if this object does not support insertion at all, meaning that insertion will always return the passed amount, + * and insert-only cables should not connect. + */ + default boolean supportsTransfer() { + return true; + } + + /** + * Insert heat into this inventory. + * + * @param amount The amount of heat to insert + */ + void heat(double amount); + + /** + * Remove heat from this inventory. + * + * @param amount The amount of heat to insert + */ + void cool(double amount); + + /** + * Get the preferred direction for ambient heat exchange, null for random. + */ + @NotNull default Optional getPreferredDirection() { + return Optional.empty(); + } + + /** + * Get the valid directions for ambient heat exchange. + */ + @NotNull default List getValidDirections() { + return Collections.emptyList(); + } + + /** + * Get the area available for heat exchange. + * @param face the face of which the are is being requested. + */ + default double getExchangeArea(@NotNull Direction face) { + return 1; + } + + /** + * Should this object be able to exchange heat with the surrounding air? + */ + default boolean allowAmbientHeatExchange() { + return true; + } +} diff --git a/src/main/java/net/id/incubus_core/systems/KineticIo.java b/src/main/java/net/id/incubus_core/systems/KineticIo.java new file mode 100644 index 0000000..9feb3e7 --- /dev/null +++ b/src/main/java/net/id/incubus_core/systems/KineticIo.java @@ -0,0 +1,34 @@ +package net.id.incubus_core.systems; + +/** + * An object that can hold and optionally take and provide kinetic energy. + * Kinetic energy is pull-only. + */ +@SuppressWarnings("unused") +public interface KineticIo { + + /** + * Get the total amount of kinetic energy stored. + */ + default long getRotationalEnergy() { + return getTorque() * getRotationalSpeed(); + } + + /** + * @return the Newton-Meter (Nm) component of energy. + */ + long getTorque(); + + /** + * @return the Radians per second (rad/s) component of energy. + */ + long getRotationalSpeed(); + + /** + * Return false if this object does not support transfer at all, meaning that transfer will always return the passed amount, + * and devices should not connect. + */ + default boolean supportsTransfer() { + return true; + } +} diff --git a/src/main/java/net/id/incubus_core/systems/Lookups.java b/src/main/java/net/id/incubus_core/systems/Lookups.java new file mode 100644 index 0000000..02accc9 --- /dev/null +++ b/src/main/java/net/id/incubus_core/systems/Lookups.java @@ -0,0 +1,58 @@ +package net.id.incubus_core.systems; + +import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; +import net.fabricmc.fabric.api.lookup.v1.item.ItemApiLookup; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.util.Hand; +import net.minecraft.util.math.Direction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static net.id.incubus_core.IncubusCore.locate; + +@SuppressWarnings("unused") +public class Lookups { + public static final BlockApiLookup HEAT = + BlockApiLookup.get(locate("heat_system_b"), HeatIo.class, Direction.class); + + public static final BlockApiLookup KINETIC = + BlockApiLookup.get(locate("kinetic_system_b"), KineticIo.class, Direction.class); + + public static final BlockApiLookup PRESSURE = + BlockApiLookup.get(locate("pressure_system_b"), PressureIo.class, Direction.class); + + public static final BlockApiLookup MATERIAL = + BlockApiLookup.get(locate("material_lookup_b"), MaterialProvider.class, Void.class); + + + public static final ItemApiLookup ITEM_HEAT = + ItemApiLookup.get(locate("heat_system_i"), HeatIo.class, Void.class); + + public static final ItemApiLookup ITEM_PRESSURE = + ItemApiLookup.get(locate("pressure_system_i"), PressureIo.class, Void.class); + + public static final ItemApiLookup ITEM_MATERIAL = + ItemApiLookup.get(locate("material_lookup_i"), MaterialProvider.class, Void.class); + + + public static @Nullable T ofPlayerHand(ItemApiLookup lookup, PlayerEntity player, Hand hand) { + return lookup.find(player.getStackInHand(hand), null); + } + + public static @Nullable T ofPlayerCursor(ItemApiLookup lookup, ScreenHandler screenHandler) { + return lookup.find(screenHandler.getCursorStack(), null); + } + + + public static long pressureInverse(double pressure) { + if(pressure <= 0) { + return 0; + } + return (long) Math.max(0, (Math.sqrt(pressure) * 36586.544243)); + } + + public static double accelerationFromPressureGradient(double self, double other) { + return Math.sqrt(Math.abs(self - other)) * ((self < other) ? -1 : 1); + } +} diff --git a/src/main/java/net/id/incubus_core/systems/Material.java b/src/main/java/net/id/incubus_core/systems/Material.java new file mode 100644 index 0000000..49e067d --- /dev/null +++ b/src/main/java/net/id/incubus_core/systems/Material.java @@ -0,0 +1,32 @@ +package net.id.incubus_core.systems; + +import net.id.incubus_core.util.TagSuperset; +import net.minecraft.item.Item; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; + + +public record Material(double maxTemperature, double heatConductivity, double electricalConductivity, double resistance, long maxRads, long maxNm, double maxPressure, long maxFrequency, long maxInductance, TagSuperset components) { + + /** + * + * Notes: + * Pixel width is a measure of the cross section of a material by how many pixels there are from edge to edge + * Heat conductivity is divided by 100 to make it into a percentage, then divided again by 20 to account for tick timeframes + * + * @param maxTemperature The failure point of the material, in most cases this is the melting point. + * @param heatConductivity The percentage of an object's stored heat that this material transfers each tick. + * @param electricalConductivity The maximum amount of energy that this material can transfer each tick per pixel width. + * @param resistance What percentage of energy is converted to heat (or generally lost) per block traveled. + * @param maxRads The maximum rotational speed this material can tolerate before failure. + * @param maxNm The maximum torque this material can tolerate before failure. + * @param maxPressure The maximum pressure this material can tolerate before failure. + * @param maxFrequency The maximum frequency of pulses this material can transfer without decoherence. + * @param maxInductance The maximum inductance of pulses this material can tolerate before failure. + * @param components Identifiers pointing to item tags that this material is associated with. + */ + public Material(double maxTemperature, double heatConductivity, double electricalConductivity, double resistance, long maxRads, long maxNm, double maxPressure, long maxFrequency, long maxInductance, Identifier ... components) { + this(maxTemperature, heatConductivity / 2000, electricalConductivity, resistance, maxRads, maxNm, maxPressure, maxFrequency, maxInductance, new TagSuperset<>(Registries.ITEM, components)); + } +} diff --git a/src/main/java/net/id/incubus_core/systems/MaterialProvider.java b/src/main/java/net/id/incubus_core/systems/MaterialProvider.java new file mode 100644 index 0000000..19221b8 --- /dev/null +++ b/src/main/java/net/id/incubus_core/systems/MaterialProvider.java @@ -0,0 +1,10 @@ +package net.id.incubus_core.systems; + +import net.minecraft.util.math.Direction; +import org.jetbrains.annotations.Nullable; + +@SuppressWarnings("unused") +public interface MaterialProvider { + + Material getMaterial(@Nullable Direction direction); +} diff --git a/src/main/java/net/id/incubus_core/systems/PressureIo.java b/src/main/java/net/id/incubus_core/systems/PressureIo.java new file mode 100644 index 0000000..8345348 --- /dev/null +++ b/src/main/java/net/id/incubus_core/systems/PressureIo.java @@ -0,0 +1,35 @@ +package net.id.incubus_core.systems; + +/** + * An object that can hold and optionally take and provide kinetic energy. + * Pressure energy is push-only. + */ +@SuppressWarnings("unused") +public interface PressureIo { + + /** + * @return the amount of pressure being exerted. + */ + long getPressure(); + + /** + * Pressurize this inventory, and return the amount of leftover pressure. + * + *

If simulation is {@link Simulation#SIMULATE}, the result of the operation must be returned, but the underlying state of the object must not change. + * + * @param amount The amount of pressure to exert + * @param simulation If {@link Simulation#SIMULATE}, do not mutate this object + * @return the amount of pressure that could not be exerted + */ + default long press(long amount, Simulation simulation) { + return amount; + } + + /** + * Return false if this object does not support transfer at all, meaning that transfer will always return the passed amount, + * and devices should not connect. + */ + default boolean supportsTransfer() { + return true; + } +} diff --git a/src/main/java/net/id/incubus_core/systems/RegistryRegistry.java b/src/main/java/net/id/incubus_core/systems/RegistryRegistry.java new file mode 100644 index 0000000..b301060 --- /dev/null +++ b/src/main/java/net/id/incubus_core/systems/RegistryRegistry.java @@ -0,0 +1,16 @@ +package net.id.incubus_core.systems; + +import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder; +import net.id.incubus_core.IncubusCore; +import net.minecraft.registry.Registry; + +public final class RegistryRegistry { + + public static void init() {} + + // TODO THIS MATERIAL REGISTRY WAS CHANGED FOR THE 1.18.2 UPDATE. MIGHT BE WRONG. + // public static final RegistryKey> MATERIAL_REGISTRY_KEY = RegistryKey.ofRegistry(new Identifier(IncubusCore.MODID, "material")); + public static final Registry MATERIAL = FabricRegistryBuilder.createSimple(Material.class, IncubusCore.locate("material")).buildAndRegister(); + // public static final Registry MATERIAL = (Registry) ((MutableRegistry) Registry.REGISTRIES).add(MATERIAL_REGISTRY_KEY, new SimpleRegistry<>(MATERIAL_REGISTRY_KEY, Lifecycle.experimental(), null), Lifecycle.experimental()); + +} diff --git a/src/main/java/net/id/incubus_core/systems/Simulation.java b/src/main/java/net/id/incubus_core/systems/Simulation.java new file mode 100644 index 0000000..fd598c8 --- /dev/null +++ b/src/main/java/net/id/incubus_core/systems/Simulation.java @@ -0,0 +1,14 @@ +package net.id.incubus_core.systems; + +public enum Simulation { + SIMULATE, + ACT; + + public boolean isSimulating() { + return this == SIMULATE; + } + + public boolean isActing() { + return this == ACT; + } +} diff --git a/src/main/java/net/id/incubus_core/task/AbstractTask.java b/src/main/java/net/id/incubus_core/task/AbstractTask.java new file mode 100644 index 0000000..c958554 --- /dev/null +++ b/src/main/java/net/id/incubus_core/task/AbstractTask.java @@ -0,0 +1,31 @@ +package net.id.incubus_core.task; + +import net.minecraft.util.Identifier; + +public abstract class AbstractTask implements TaskInfo { + + protected final Identifier taskId; + protected final Runnable taskRunnable, cancelRunnable; + + /** + * Abstract task foundation class. Used primarily to allow storing and retrieving different + * types of tasks from a single place + * + * @param taskId Unique task ID, used for storing and retrieval + * @param taskRunnable Task to run on completion + * @param cancelRunnable Task to run in the case of a soft fail + */ + public AbstractTask(Identifier taskId, Runnable taskRunnable, Runnable cancelRunnable) { + this.taskId = taskId; + this.taskRunnable = taskRunnable; + this.cancelRunnable = cancelRunnable; + } + + /** + * Cancel the existing running task if one exists + * + * @param failHard A hard fail interrupts the task and doesn't trigger the cancel runnable task + * @return Whether the task was cancelled successfully + */ + public abstract boolean cancel(boolean failHard); +} diff --git a/src/main/java/net/id/incubus_core/task/AsyncTask.java b/src/main/java/net/id/incubus_core/task/AsyncTask.java new file mode 100644 index 0000000..3413d3a --- /dev/null +++ b/src/main/java/net/id/incubus_core/task/AsyncTask.java @@ -0,0 +1,43 @@ +package net.id.incubus_core.task; + +import net.minecraft.util.Identifier; + +import javax.annotation.Nullable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +public class AsyncTask extends AbstractTask { + + @Nullable + protected Future future; + + public AsyncTask(Identifier taskId, Runnable taskRunnable, Runnable cancelRunnable) { + super(taskId, taskRunnable, cancelRunnable); + } + + public void submit(ExecutorService scheduler) { + if(scheduler != null) + future = scheduler.submit(taskRunnable); + } + + @Override + public boolean cancel(boolean failHard) { + if(!failHard) + cancelRunnable.run(); + if(future != null) + return future.cancel(true); + return false; + } + + @Override + public boolean isActive() { + if(future != null) + return !future.isDone(); + return false; + } + + @Override + public boolean isAsync() { + return true; + } +} diff --git a/src/main/java/net/id/incubus_core/task/ScheduledTask.java b/src/main/java/net/id/incubus_core/task/ScheduledTask.java new file mode 100644 index 0000000..d91a244 --- /dev/null +++ b/src/main/java/net/id/incubus_core/task/ScheduledTask.java @@ -0,0 +1,29 @@ +package net.id.incubus_core.task; + +import net.minecraft.util.Identifier; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class ScheduledTask extends AsyncTask { + + private final TimeUnit timeUnit; + private final long duration; + private final boolean repeating; + + public ScheduledTask(Identifier id, long duration, TimeUnit timeUnit, Runnable taskRunnable, Runnable cancelRunnable, boolean repeating) { + super(id, taskRunnable, cancelRunnable); + this.duration = duration; + this.timeUnit = timeUnit; + this.repeating = repeating; + } + + public void schedule(ScheduledExecutorService scheduler) { + if(scheduler != null) { + if (repeating) + future = scheduler.scheduleAtFixedRate(taskRunnable, duration, duration, timeUnit); + else + future = scheduler.schedule(taskRunnable, duration, timeUnit); + } + } +} diff --git a/src/main/java/net/id/incubus_core/task/TaskInfo.java b/src/main/java/net/id/incubus_core/task/TaskInfo.java new file mode 100644 index 0000000..b0c1560 --- /dev/null +++ b/src/main/java/net/id/incubus_core/task/TaskInfo.java @@ -0,0 +1,8 @@ +package net.id.incubus_core.task; + +public interface TaskInfo { + + boolean isActive(); + + boolean isAsync(); +} diff --git a/src/main/java/net/id/incubus_core/task/Tasks.java b/src/main/java/net/id/incubus_core/task/Tasks.java new file mode 100644 index 0000000..620c152 --- /dev/null +++ b/src/main/java/net/id/incubus_core/task/Tasks.java @@ -0,0 +1,56 @@ +package net.id.incubus_core.task; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Identifier; + +import javax.annotation.Nullable; +import java.util.UUID; +import java.util.WeakHashMap; + +public class Tasks { + + private static final WeakHashMap GAME_TASKS = new WeakHashMap<>(); + private static final WeakHashMap> PLAYER_TASKS = new WeakHashMap<>(); + + public static boolean store(AbstractTask task, @Nullable PlayerEntity associatedPlayer) { + if(associatedPlayer == null) { + if (!GAME_TASKS.containsKey(task.taskId) || !GAME_TASKS.get(task.taskId).isActive()) { + GAME_TASKS.put(task.taskId, task); + return true; + } + } + else { + var tasksSet = new WeakHashMap(); + + if(PLAYER_TASKS.containsKey(associatedPlayer.getUuid())) + tasksSet = PLAYER_TASKS.get(associatedPlayer.getUuid()); + + if(!tasksSet.containsKey(task.taskId) || !tasksSet.get(task.taskId).isActive()) { + tasksSet.put(task.taskId, task); + PLAYER_TASKS.put(associatedPlayer.getUuid(), tasksSet); + return true; + } + } + return false; + } + + @Nullable + public static AbstractTask remove(Identifier taskId, @Nullable PlayerEntity associatedPlayer) { + if(associatedPlayer == null) + return GAME_TASKS.remove(taskId); + else + if(PLAYER_TASKS.containsKey(associatedPlayer.getUuid())) + return PLAYER_TASKS.get(associatedPlayer.getUuid()).remove(taskId); + return null; + } + + @Nullable + public static TaskInfo getInfo(Identifier taskId, @Nullable PlayerEntity associatedPlayer) { + if(associatedPlayer == null) + return GAME_TASKS.get(taskId); + else + if(PLAYER_TASKS.containsKey(associatedPlayer.getUuid())) + return PLAYER_TASKS.get(associatedPlayer.getUuid()).get(taskId); + return null; + } +} diff --git a/src/main/java/net/id/incubus_core/util/Config.java b/src/main/java/net/id/incubus_core/util/Config.java new file mode 100644 index 0000000..d1fdfcd --- /dev/null +++ b/src/main/java/net/id/incubus_core/util/Config.java @@ -0,0 +1,59 @@ +package net.id.incubus_core.util; + +import net.fabricmc.loader.api.FabricLoader; +import net.id.incubus_core.IncubusCore; +import net.minecraft.util.Identifier; + +import java.util.function.Function; + +/** + * Call my methods in order to use these useful config features. + */ +@SuppressWarnings("unused") +public final class Config { + private Config(){} + + /** + * @return Whether the mod with the specified id is loaded. + */ + public static boolean isLoaded(String id){ + return FabricLoader.getInstance().isModLoaded(id); + } + + /** + * Gets a boolean system property. + */ + public static boolean getBoolean(Identifier key, boolean defaultValue){ + return get(key, Boolean::parseBoolean, defaultValue); + } + + /** + * Gets a string system property. + */ + public static String getString(Identifier key, String defaultValue){ + return get(key, String::toString, defaultValue); + } + + /** + * Gets an identifier system property. + */ + public static Identifier getIdentifier(Identifier key, Identifier defaultValue) { + return get(key, Identifier::tryParse, defaultValue); + } + + /** + * Gets a system property, parsed by the specified function. + */ + public static T get(Identifier key, Function parser, T defaultValue){ + String value = System.getProperty(key.getNamespace() + "." + key.getPath()); + if(value != null && !value.isBlank()){ + try{ + return parser.apply(value); + } catch (Throwable t) { + IncubusCore.LOG.error("Failed to parse {}.{}: {}. using default value {}.", key.getNamespace(), key.getPath(), value, defaultValue); + t.printStackTrace(); + } + } + return defaultValue; + } +} diff --git a/src/main/java/net/id/incubus_core/util/EnumExtender.java b/src/main/java/net/id/incubus_core/util/EnumExtender.java new file mode 100644 index 0000000..c8d0546 --- /dev/null +++ b/src/main/java/net/id/incubus_core/util/EnumExtender.java @@ -0,0 +1,59 @@ +package net.id.incubus_core.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiFunction; + +public class EnumExtender { + private static final Map>, BiFunction>> extensibles = new HashMap<>(); + + public static > void register(Class extensible, BiFunction callback) { + extensibles.put(extensible, callback); + } + + @SuppressWarnings("unchecked") + public static > T add(Class to, String name, Object... arguments) { + if (extensibles.containsKey(to)) { + try { + return (T) extensibles.get(to).apply(name, arguments); + } catch (ClassCastException | IndexOutOfBoundsException e) { + throw new IllegalArgumentException("Invalid arguments for entry " + name + " of enum " + to + ". Arguments must match the enum constructor."); + } + } else { + throw new UnsupportedOperationException("Attempted to extend inextensible enum " + to + ". Create a mixin for it from the template in EnumExtender."); + } + } +} + + +/* + * Enum mixin template. + * + * Replace TARGET with the enum class. + * Replace VALUEARRAY with the synthetic field storing an array of TARGETs. Find the name via the bytecode. + * Replace ARG0TYPE/NAME, ARG1TYPE/NAME, etc. with the enum argument types/names, adjusting the number of arguments to match the enum constructor. + * + * + +@Mixin(TARGET.class) +public class TARGETMixin { + @SuppressWarnings("ShadowTarget") + @Shadow + @Mutable + @Final + private static TARGET[] VALUEARRAY; + + private TARGETMixin(String valueName, int ordinal, ARG0TYPE ARG0NAME, ARG1TTYPE ARG1NAME, ARGNTYPE ARGNNAME) { + throw new AssertionError(); + } + + static { + EnumExtender.register(TARGET.class, (name, args) -> { + TARGET entry = (TARGET) (Object) new TARGETMixin(name, VALUEARRAY.length, (ARG0TYPE) args[0], (ARG1TYPE) args[1], ARGNTYPE args[n]); + VALUEARRAY = Arrays.copyOf(VALUEARRAY, VALUEARRAY.length + 1); + return VALUEARRAY[VALUEARRAY.length - 1] = entry; + }); + } +} + + */ \ No newline at end of file diff --git a/src/main/java/net/id/incubus_core/util/FoxDuck.java b/src/main/java/net/id/incubus_core/util/FoxDuck.java new file mode 100644 index 0000000..8db2eb4 --- /dev/null +++ b/src/main/java/net/id/incubus_core/util/FoxDuck.java @@ -0,0 +1,14 @@ +package net.id.incubus_core.util; + +import net.minecraft.entity.passive.FoxEntity; + +import java.util.UUID; + +public interface FoxDuck { + + FoxEntity.Type getFoxColor(); + + void setFoxColor(FoxEntity.Type type); + + void addTrustedUUID(UUID id); +} diff --git a/src/main/java/net/id/incubus_core/util/IncubusHoliday.java b/src/main/java/net/id/incubus_core/util/IncubusHoliday.java new file mode 100644 index 0000000..3881c81 --- /dev/null +++ b/src/main/java/net/id/incubus_core/util/IncubusHoliday.java @@ -0,0 +1,108 @@ +package net.id.incubus_core.util; + +import net.id.incubus_core.IncubusCore; +import java.util.Calendar; +import java.util.function.Predicate; + +/** + * A simple date-based holiday enumeration. A central place to add holiday related events. + *

+ * The order of this enum is important, the first holiday (minus none) in this enum is picked first. + *

+ * You can set the property incubus_core.holiday_override to a valid holiday to override it. + */ +public enum IncubusHoliday { + /** + * No holiday, the most mundane state. :-( + */ + NONE("normal", (calendar) -> false), + + /** + * Christmas, from December 24 to December 26 (inclusive). + *

+ * Minecraft uses this for chests. + */ + CHRISTMAS("christmas", (calendar) -> { + int date = calendar.get(Calendar.DATE); + return calendar.get(Calendar.MONTH) == Calendar.DECEMBER && date >= 24 && date <= 26; + }), + + /** + * Halloween, October 31st + *

+ * Minecraft uses this to spawn bipeds with carved pumpkin headgear. + */ + HALLOWEEN("holoween", (calendar) -> + calendar.get(Calendar.MONTH) == Calendar.OCTOBER && calendar.get(Calendar.DATE) == 31 + ), + + /** + * Near Halloween, October 20 to November 3 (inclusive). + *

+ * Minecraft uses this to spawn in more bats than normal. + */ + NEAR_HALLOWEEN("near_halloween", (calendar) -> { + var date = calendar.get(Calendar.DATE); + var month = calendar.get(Calendar.MONTH); + return (month == Calendar.OCTOBER && date >= 20) || + (month == Calendar.NOVEMBER && date <= 3); + }), + ; + + /** + * Allows for overriding the holiday. + */ + public static final String HOLIDAY_OVERRIDE = Config.getString(IncubusCore.locate("holiday_override"), null); + + private final String name; + private final Predicate predicate; + + IncubusHoliday(String prefix, Predicate predicate) { + this.name = prefix; + this.predicate = predicate; + } + + private static final IncubusHoliday HOLIDAY; + + static { + IncubusHoliday holiday = NONE; + if (HOLIDAY_OVERRIDE != null) { + for (var value : values()) { + if (value.getName().equals(HOLIDAY_OVERRIDE)) { + holiday = value; + break; + } + } + } + + if (holiday == NONE) { + Calendar calendar = Calendar.getInstance(); + for (var value : values()) { + if (value.predicate.test(calendar)) { + holiday = value; + break; + } + } + } + + HOLIDAY = holiday; + } + + /** + * Gets the current holiday. + * + * @return The current holiday + */ + public static IncubusHoliday get() { + return HOLIDAY; + } + + /** + * Gets the name of this holiday in a form that is suitable for identifiers. + * + * @return Gets the name of this holiday + */ + public String getName() { + return name; + } +} diff --git a/src/main/java/net/id/incubus_core/util/InventoryWrapper.java b/src/main/java/net/id/incubus_core/util/InventoryWrapper.java new file mode 100644 index 0000000..35bfc72 --- /dev/null +++ b/src/main/java/net/id/incubus_core/util/InventoryWrapper.java @@ -0,0 +1,125 @@ +package net.id.incubus_core.util; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.Inventories; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; +import net.minecraft.util.collection.DefaultedList; + +/** + * A simple {@code Inventory} implementation with only default methods + an item list getter. + *

+ * Originally by Juuz + */ +public interface InventoryWrapper extends Inventory { + /** + * Creates an inventory from the item list. + */ + static InventoryWrapper of(DefaultedList items) { + return () -> items; + } + + /** + * Creates a new inventory with the size. + */ + static InventoryWrapper ofSize(int size) { + return of(DefaultedList.ofSize(size, ItemStack.EMPTY)); + } + + /** + * Gets the item list of this inventory. + * Must return the same instance every time it's called. + */ + DefaultedList getItems(); + + /** + * Returns the inventory size. + */ + @Override + default int size() { + return getItems().size(); + } + + /** + * @return true if this inventory has only empty stacks, false otherwise + */ + @Override + default boolean isEmpty() { + for (int i = 0; i < size(); i++) { + ItemStack stack = getStack(i); + if (!stack.isEmpty()) { + return false; + } + } + return true; + } + + /** + * Gets the item in the slot. + */ + @Override + default ItemStack getStack(int slot) { + return getItems().get(slot); + } + + default int getItemSlot(ItemStack stack) { + DefaultedList inv = getItems(); + for (int i = 0; i < inv.size(); i++) { + ItemStack item = inv.get(i); + if (item.getMaxCount() > item.getCount() && item.isItemEqual(stack)) { + return i; + } + } + return -1; + } + + /** + * Takes a stack of the size from the slot. + *

(default implementation) If there are less items in the slot than what are requested, + * takes all items in that slot. + */ + @Override + default ItemStack removeStack(int slot, int count) { + ItemStack result = Inventories.splitStack(getItems(), slot, count); + if (!result.isEmpty()) { + markDirty(); + } + return result; + } + + /** + * Removes the current stack in the {@code slot} and returns it. + */ + @Override + default ItemStack removeStack(int slot) { + return Inventories.removeStack(getItems(), slot); + } + + @Override + default void setStack(int slot, ItemStack stack) { + getItems().set(slot, stack); + if (stack.getCount() > getMaxCountPerStack()) { + stack.setCount(getMaxCountPerStack()); + } + } + + /** + * Clears {@linkplain #getItems() the item list}}. + */ + @Override + default void clear() { + getItems().clear(); + } + + @Override + default void markDirty() { + // Override if you want behavior. + } + + @Override + default boolean canPlayerUse(PlayerEntity player) { + return true; + } +} + + diff --git a/src/main/java/net/id/incubus_core/util/RandomShim.java b/src/main/java/net/id/incubus_core/util/RandomShim.java new file mode 100644 index 0000000..ec542a6 --- /dev/null +++ b/src/main/java/net/id/incubus_core/util/RandomShim.java @@ -0,0 +1,61 @@ +package net.id.incubus_core.util; + +import java.util.Random; + +public class RandomShim extends Random { + private final net.minecraft.util.math.random.Random random; + + public RandomShim(net.minecraft.util.math.random.Random random) { + this.random = random; + } + + @Override + public synchronized void setSeed(long seed) { + random.setSeed(seed); + } + + @Override + protected int next(int bits) { + return random.nextInt(bits); + } + + @Override + public int nextInt() { + return random.nextInt(); + } + + @Override + public int nextInt(int bound) { + return random.nextInt(bound); + } + + @Override + public long nextLong() { + return random.nextLong(); + } + + @Override + public boolean nextBoolean() { + return random.nextBoolean(); + } + + @Override + public float nextFloat() { + return random.nextFloat(); + } + + @Override + public double nextDouble() { + return random.nextDouble(); + } + + @Override + public synchronized double nextGaussian() { + return random.nextGaussian(); + } + + @Override + public int nextInt(int origin, int bound) { + return random.nextBetweenExclusive(origin, bound); + } +} diff --git a/src/main/java/net/id/incubus_core/util/RegistryHelper.java b/src/main/java/net/id/incubus_core/util/RegistryHelper.java new file mode 100644 index 0000000..c468a24 --- /dev/null +++ b/src/main/java/net/id/incubus_core/util/RegistryHelper.java @@ -0,0 +1,44 @@ +package net.id.incubus_core.util; + +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.entry.RegistryEntryList; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public class RegistryHelper { + + public static Optional> tryGetTagKey(Registry registry, Identifier id) { + return registry.streamTags().filter(tagKey -> tagKey.id().equals(id)).findFirst(); + } + + public static Optional> getEntries(TagKey tagKey) { + return getRegistryOf(tagKey).getEntryList(tagKey); + } + + public static Optional> tryGetEntry(Registry registry, T object) { + return registry.getKey(object).map(registry::entryOf); + } + + public static boolean isObjectInTag(Registry registry, Identifier tagId, T object) { + return tryGetTagKey(registry, tagId).map(tagKey -> isObjectInTag(registry, tagKey, object)).orElse(false); + } + + public static boolean isObjectInTag(Registry registry, TagKey tag, T object) { + var entry = tryGetEntry(registry, object); + return entry.map(tRegistryEntry -> tRegistryEntry.isIn(tag)).orElse(false); + } + + public static boolean isTagEmpty(TagKey tag) { + return getEntries(tag).map(RegistryEntryList::size).orElse(0) == 0; + } + + @SuppressWarnings("unchecked") + public static Registry getRegistryOf(@NotNull TagKey key) { + return (Registry) Registries.REGISTRIES.get(key.registry().getValue()); + } +} diff --git a/src/main/java/net/id/incubus_core/util/RegistryQueue.java b/src/main/java/net/id/incubus_core/util/RegistryQueue.java new file mode 100644 index 0000000..31546c9 --- /dev/null +++ b/src/main/java/net/id/incubus_core/util/RegistryQueue.java @@ -0,0 +1,129 @@ +package net.id.incubus_core.util; + +import net.fabricmc.api.EnvType; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +/** + * The purpose of this class is to allow you to register + * objects while performing actions on them at the same time. + * It also allows you to register said objects in one fell swoop + * (All at the same time). + *
Example usage: + *

{@code
+ * private static RegistryQueue ITEM = new RegistryQueue(Registry.ITEM);
+ *
+ * private static Action compostable = (id, item) -> CompostingChanceRegistry.INSTANCE.add(item, 30);
+ *
+ * public static Item CANDY_CANE = ITEM.add("candy_cane", new Item(...), compostable);
+ * public static Item GINGERBREAD_MAN = ITEM.add("ginger_bread_man", new Item(...), compostable);
+ * }
+ * More complex example usage: + *
{@code
+ * private static Action fuel(int ticks) {
+ *     return (id, item) -> FuelRegistry.INSTANCE.add(item, ticks);
+ * }
+ *
+ * public static Item GIANT_STICK = ITEM.add("giant_stick", new Item(...), fuel(1000));
+ * public static Item STRAW_BOWL = ITEM.add("straw_bowl", new Item(...), fuel(500));
+ * }
+ * Example of registration: + *
{@code
+ * public static void init() {
+ *     ITEM.register();
+ * }
+ * }
+ * ~ Jack + * @param What type this RegistryQueue registers + */ +@SuppressWarnings("unused") +public final class RegistryQueue { + private static final boolean CLIENT = FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT; + private final Registry registry; + private final Map> entries; + + /** + * Creates a {@link RegistryQueue}. This constructor is recommended for mods + * that might add blocks regularly and/or don't want to bother dealing with + * the other constructor. + */ + public RegistryQueue(Registry registry) { + this.registry = registry; + this.entries = new LinkedHashMap<>(16); + } + + /** + * Creates a {@link RegistryQueue} with an initial capacity for minor speed and memory improvements, + * if you're into that sort of thing. Make sure to be diligent when choosing the initial capacity. + * @param initialCapacity The initial capacity of this {@link RegistryQueue}'s map. This should + * ideally be equal to the amount of things you intend to register. + */ + public RegistryQueue(Registry registry, int initialCapacity) { + this.registry = registry; + /* + A load factor of 0.10F is chosen because people will forget + to update this value when they add a few new blocks. + The default load factor is 0.75F, which is ridiculous for this use-case. + */ + this.entries = new LinkedHashMap<>(initialCapacity, 0.10F); + } + + /** + * Makes the action only act when the {@link EnvType} is {@link EnvType#CLIENT}. + * @return This action, modified to only occur in a client environment. + */ + public static Action onClient(Action action) { + return CLIENT ? action : (id, value) -> {}; + } + + /** + * Adds an entry to the {@link RegistryQueue}. If the id is a duplicate, the entry in this RegistryQueue is replaced. + * @param id The identifier of the object + * @param value The object to be associated with the identifier + * @param additionalActions Any additional actions to perform on the object + * @param The type or a subtype of this {@link RegistryQueue}'s type T. + * @return The {@code value} inputted into this method + */ + @SafeVarargs + public final V add(Identifier id, V value, BiConsumer... additionalActions) { + this.entries.put(id, new Entry<>(value, additionalActions)); + return value; + } + + /** + * Registers all of the entries in this {@link RegistryQueue} and clears + * this RegistryQueue's data from memory. All actions assigned to objects + * in this RegistryQueue will be performed at this time. + */ + public void register() { + this.entries.forEach(this::register); + this.entries.clear(); + } + + private void register(Identifier id, Entry pair) { + Registry.register(this.registry, id, pair.value); + for (var action : pair.actions) { + action.accept(id, pair.value); + } + } + + /** + * An interface to represent an action to be performed on an object. + * Made for ease of use. + * @param The type of the object this Action acts upon. + */ + @FunctionalInterface + public interface Action extends BiConsumer { + } + + /** + * A record to represent an object and its actions to be performed on it. + * @param The type of object + */ + private static record Entry(V value, BiConsumer[] actions) { + } +} diff --git a/src/main/java/net/id/incubus_core/util/SeedSupplier.java b/src/main/java/net/id/incubus_core/util/SeedSupplier.java new file mode 100644 index 0000000..38c8ed2 --- /dev/null +++ b/src/main/java/net/id/incubus_core/util/SeedSupplier.java @@ -0,0 +1,9 @@ +package net.id.incubus_core.util; + +public class SeedSupplier { + public static final long MARKER = -1; + + public static long getSeed() { + return MARKER; + } +} diff --git a/src/main/java/net/id/incubus_core/util/TagSuperset.java b/src/main/java/net/id/incubus_core/util/TagSuperset.java new file mode 100644 index 0000000..0a610d9 --- /dev/null +++ b/src/main/java/net/id/incubus_core/util/TagSuperset.java @@ -0,0 +1,56 @@ +package net.id.incubus_core.util; + +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; +import net.minecraft.util.Pair; +import java.util.List; + +public class TagSuperset { + + private final List identifiableTags; + private final Object2ObjectMap> additionalDataMap = new Object2ObjectOpenHashMap<>(); + private final Registry registry; + + public TagSuperset(Registry registry, Identifier ... ids) { + this.registry = registry; + this.identifiableTags = List.of(ids); + } + + @SafeVarargs + public TagSuperset(Registry registry, Identifier[] ids, Pair> ... additionalData) { + this.registry = registry; + this.identifiableTags = List.of(ids); + for (Pair> entry : additionalData) { + String key = entry.getLeft(); + List data = entry.getRight(); + + if(data.size() != identifiableTags.size()) { + throw new IllegalArgumentException("Data mismatch found under " + key + ", Additional data must form a one to one match with tags"); + } + + additionalDataMap.put(key, data); + } + } + + public boolean contains(Identifier item) { + for (Identifier tagId : identifiableTags) { + if(RegistryHelper.isObjectInTag(registry, tagId, registry.get(item))) + return true; + } + return false; + } + + public Identifier getTagId(int i) { + return identifiableTags.get(i); + } + + public K getAdditionalData(Class clazz, String key, int i) { + return clazz.cast(additionalDataMap.get(key).get(i)); + } + + public int getIndex(Identifier tagId) { + return identifiableTags.indexOf(tagId); + } +} diff --git a/src/main/java/net/id/incubus_core/util/TickCounter.java b/src/main/java/net/id/incubus_core/util/TickCounter.java new file mode 100644 index 0000000..52f0a56 --- /dev/null +++ b/src/main/java/net/id/incubus_core/util/TickCounter.java @@ -0,0 +1,49 @@ +package net.id.incubus_core.util; + +public class TickCounter { + + private final int TICK_THRESHOLD; + private final int MAX_VALUE; + + private int counter = 0; + + /** + * Placed in tickable methods acts as a basic counter + * + * @param tickThreshold Number of ticks before test() returns true and the counter is reset + * @param maxValue Maximum number of ticks before the counter is reset. Only called if test() isn't first + */ + public TickCounter(int tickThreshold, int maxValue) { + TICK_THRESHOLD = tickThreshold; + MAX_VALUE = maxValue; + } + + public TickCounter(int tickThreshold) { + this(tickThreshold, Integer.MAX_VALUE); + } + + /** + * @return Whether the counter has passed the tick threshold + */ + public boolean test() { + if(counter >= TICK_THRESHOLD) { + reset(); + return true; + } + return false; + } + + /** + * Increment the tick counter. Required for each iteration of a tickable loop + */ + public void increment() { + if(counter >= MAX_VALUE || counter < 0) reset(); + else counter++; + } + + public void reset() { + counter = 0; + } + + public int value() { return this.counter; } +} diff --git a/src/main/java/net/id/incubus_core/util/UuidHelper.java b/src/main/java/net/id/incubus_core/util/UuidHelper.java new file mode 100644 index 0000000..eabed3f --- /dev/null +++ b/src/main/java/net/id/incubus_core/util/UuidHelper.java @@ -0,0 +1,67 @@ +package net.id.incubus_core.util; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import org.apache.http.client.HttpResponseException; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Scanner; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +public class UuidHelper { + + private static final String MOJANG_UUID_API = "https://api.mojang.com/users/profiles/minecraft/"; + private static CompletableFuture uuidFuture; + + /** + * Takes a users name and finds a matching UUID + * + * @param playerName Name of the player we want to search for + * @param finish lambda function to run after the UUID is found + */ + @Environment(EnvType.CLIENT) + public static void findUuid(String playerName, Consumer finish) { + // Asynchronously search for a users UUID + uuidFuture = CompletableFuture.supplyAsync(() -> UuidHelper.getUuidFromPlayer(playerName)); + uuidFuture.thenAccept(finish); + } + + private static UUID getUuidFromPlayer(String playerName) { + UUID uuid = null; + try { + URL uuidGetRequest = new URL(MOJANG_UUID_API + playerName); + var httpURLConnection = (HttpURLConnection) uuidGetRequest.openConnection(); + httpURLConnection.setRequestMethod("GET"); + + int responseCode = httpURLConnection.getResponseCode(); + if(responseCode != 200) + uuidFuture.completeExceptionally(new HttpResponseException(responseCode, "Failed to get a valid UUID for the player")); + + var stringBuilder = new StringBuilder(); + var scanner = new Scanner(uuidGetRequest.openStream()); + while(scanner.hasNext()) + stringBuilder.append(scanner.nextLine()); + scanner.close(); + + var uuidObject = (JsonObject) JsonParser.parseString(stringBuilder.toString()); + String id = uuidObject.get("id").getAsString(); + + // Format into a proper uuid (accepted) uuid format complete with '-' characters + String idFormatted = id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" + id.substring(20, 32); + + uuid = UUID.fromString(idFormatted); + + } + catch(IOException ioe) { + uuidFuture.completeExceptionally(new IOException()); + } + return uuid; + } +}